为rails 3 has_many创建的错误数据库记录:通过关联

我有一个has_many:通过关联。 球员有很多球队,球队有很多球员。 联盟模式Affiliation属于玩家和团队,并且还具有year属性,用于跟踪玩家year的团队归属(或就业)。

我似乎无法找到基于以下规则构建关联的正确方法:

  1. 创建一个新玩家。
  2. 关联可能是新的或现有的团队。 所以找到它或创建它,但只有在保存播放器时才创建它。
  3. 该关联可能包括也可能不包括一年,但只有在保存玩家和团队时才能创建该关联。

播放器模型如下所示:

 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