为rails 3 has_many创建的错误数据库记录:通过关联
我有一个has_many:通过关联。 球员有很多球队,球队有很多球员。 联盟模式Affiliation属于玩家和团队,并且还具有year
属性,用于跟踪玩家year
的团队归属(或就业)。
我似乎无法找到基于以下规则构建关联的正确方法:
- 创建一个新玩家。
- 关联可能是新的或现有的团队。 所以找到它或创建它,但只有在保存播放器时才创建它。
- 该关联可能包括也可能不包括一年,但只有在保存玩家和团队时才能创建该关联。
播放器模型如下所示:
class Player :destroy has_many :teams, :through => :affiliations end
团队模型如下所示:
class Team :destroy has_many :players, :through => :affiliations end
Affiliation模型如下:
class Affiliation < ActiveRecord::Base attr_accessible :player_id, :team_id, :year belongs_to :player belongs_to :team end
我已成功使用PlayersController中的create动作创建没有join模型属性的关联记录,如下所示:
class PlayersController < ApplicationController def create @player = Player.new(params[:player].except(:teams)) unless params[:player][:teams].blank? params[:player][:teams].each do |team| team_to_associate = Team.find_or_initialize_by_id(team[:id], team.except(:year) @player.teams << team_to_associate end end @player.save respond_with @player end end
在创建了一个有两个团队使用params的新玩家之后:
{"player"=>{"name"=>"George Baker", "teams"=>[{"city"=>"Buffalo"}, {"city"=>"Detroit"}]}}
数据库看起来像:
玩家
id:1,姓名:George Baker
球队
id:1,城市:布法罗
id:2,城市:西雅图
隶属关系
id:1,player_id:1,team_id:1,year:null
id:2,player_id:1,team_id:2,year:null
当我试图介绍这一年时,情况就会崩溃。 我最近在PlayersController中创建动作的尝试看起来像:
class PlayersController team[:year]}) @player.teams << team_to_associate end end @player.save respond_with @player end end
现在,当创建一个有两个团队使用params的新玩家时:
{"player"=>{"name"=>"Bill Johnson", "teams"=>[{"id"=>"1"}, {"city"=>"Detroit", "year"=>"1999"}]}}
数据库看起来像:
玩家
id:1,姓名:George Baker
id:2,姓名:Bill Johnson
球队
id:1,城市:布法罗
id:2,城市:西雅图
id:3,城市:底特律
隶属关系
id:1,player_id:1,team_id:1,year:null
id:2,player_id:1,team_id:2,year:null
id:3,player_id:2,team_id:1,年份:null
id:4,player_id:null,team_id:3,年份:1999
id:5,player_id:2,team_id:3,year:null
因此,当只有两个应该存在时,创建了三个记录。 从属关系记录ID:3是正确的。 对于id:4,缺少player_id。 对于身份证:5,这一年不见了。
显然这是不正确的。 我哪里错了?
谢谢
编辑
好吧,我想我有更好的解决方案。 AFAIK,你不能在两个深度级别上使用嵌套属性(虽然你可以测试它,也许它可以工作),但没有什么能阻止我们模拟这种行为:
class Player < ActiveRecord::Base has_many :affiliations has_many :teams, through: :affiliations accespts_nested_attributes_for :affiliations, allow_destroy: true end class Affiliation < ActiveRecord::Base belongs_to :player belongs_to :team validates :player, presence: true validates :team, presence: true attr_accessor :team_attributes before_validation :link_team_for_nested_assignment def link_team_for_nested_assignment return true unless team.blank? self.team = Team.find_or_create_by_id( team_attributes ) end
现在,这样做:
@player = Player.new( name: 'Bill Johnson', affiliations_attributes: [ {year: 1999, team_attributes: {id: 1, city: 'Detroit}}, {team_attributes: {city: 'Somewhere else'}} ] ) @player.save
应该创建所有必需的记录,并在出现问题时仍然回滚所有内容(因为save
本身已经包含在事务中)。 作为奖励,所有错误都将与@player
相关联!
这个怎么样 ?
class PlayersController < ApplicationController def create ActiveRecord::Base.transaction do @player = Player.new(params[:player].except(:teams)) raise ActiveRecord::Rollback unless @player.save # first check unless params[:player][:teams].blank? @teams = [] params[:player][:teams].each do |team| team_to_associate = Team.find_or_initialize_by_id(team[:id], team.except(:year)) raise ActiveRecord::Rollback unless team_to_associate.save # second check if team[:year] affiliation = team_to_associate.affiliations.build(player: @player, year: team[:year]) raise ActiveRecord::Rollback unless affiliation.save # third check end @teams << team_to_associate # keep the object so we have access to errors end end end flash[:notice] = "ok" rescue ActiveRecord::Rollback => e flash[:alert] = "nope" ensure respond_with @group end end
这个解决方案最终为我工作。 但是,如果有人将此代码用于他们自己的项目,请知道除了create之外我还没有测试过任何其他操作。 一旦我处理了读取,更新和删除,我肯定会有一些改变。
class Player < ActiveRecord::Base attr_accessible :name has_many :affiliations, :dependent => :destroy has_many :teams, :through => :affiliations accepts_nested_attributes_for :affiliations, :allow_destroy => true attr_accessible :affiliations_attributes end class Team < ActiveRecord::Base attr_accessible :city has_many :affiliations, :dependent => :destroy has_many :players, :through => :affiliations end class Affiliation < ActiveRecord::Base attr_accessible :player_id, :team_id, :team_attributes, :year belongs_to :player belongs_to :team accepts_nested_attributes_for :team def team_attributes=(team_attributes) self.team = Team.find_by_id(team_attributes[:id]) self.team = Team.new(team_attributes.except(:id)) if self.team.blank? end end class PlayersController < ApplicationController def create player_params = params[:player].except(:teams) affiliation_params = [] unless params[:player][:teams].blank? params[:player][:teams].each do |team| affiliation = {} affiliation[:year] = team[:year] unless team[:year].blank? affiliation[:team_attributes] = team.except(:year) affiliation_params << affiliation end end player_params[:affiliation_attributes] = affiliation_params unless affiliation_params.blank? @player = Player.new(player_params) @player.save respond_with @player end end