[英]incorrect database records created for rails 3 has_many :through association
I have a has_many :through association. 我有一个has_many:through关联。 Players have many Teams and Teams have many Players.
玩家有很多团队,而团队有很多玩家。 The join model, Affiliation, belongs to Players and Teams, and also has a
year
attribute to keep track of a player's team affiliation (or employment) from year to year. 加入模型Affiliation属于“玩家和团队”,并且还具有“
year
属性来跟踪玩家year
的团队隶属关系(或雇用情况)。
I can't seem to figure out the right way to build an association based on the following rules: 我似乎无法找出基于以下规则建立关联的正确方法:
The Player model looks like: 播放器模型如下所示:
class Player < ActiveRecord::Base
attr_accessible :name
has_many :affiliations, :dependent => :destroy
has_many :teams, :through => :affiliations
end
The Team model looks like: 团队模型如下所示:
class Team < ActiveRecord::Base
attr_accessible :city
has_many :affiliations, :dependent => :destroy
has_many :players, :through => :affiliations
end
The Affiliation model looks like: 隶属关系模型如下所示:
class Affiliation < ActiveRecord::Base
attr_accessible :player_id, :team_id, :year
belongs_to :player
belongs_to :team
end
I have been successful at creating the association records without the join model attribute using a create action in the PlayersController that looks like: 我已经成功使用PlayersController中的create动作创建了没有join model属性的关联记录,如下所示:
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
After creating a new player with two teams using params like: 在使用如下参数创建具有两支球队的新球员之后:
{"player"=>{"name"=>"George Baker", "teams"=>[{"city"=>"Buffalo"}, {"city"=>"Detroit"}]}}
the database looks like: 该数据库如下所示:
players 玩家们
id: 1, name: George Baker
id:1,名字:乔治·贝克
teams 团队
id: 1, city: Buffalo
id:1,城市:布法罗
id: 2, city: Seattle
id:2,城市:西雅图
affiliations 隶属关系
id: 1, player_id: 1, team_id: 1, year: null
id:1,player_id:1,team_id:1,年份:null
id: 2, player_id: 1, team_id: 2, year: null
id:2,player_id:1,team_id:2,年份:null
When I try to introduce the year, things fall apart. 当我尝试介绍年份时,事情就崩溃了。 My most recent attempt at the create action in the PlayersController looks like:
我最近在PlayersController中创建操作的尝试如下所示:
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)
// only additional line...
team_to_associate.affiliations.build({:year => team[:year]})
@player.teams << team_to_associate
end
end
@player.save
respond_with @player
end
end
Now, when creating a new player with two teams using params like: 现在,当使用以下参数创建由两支球队组成的新球员时:
{"player"=>{"name"=>"Bill Johnson", "teams"=>[{"id"=>"1"}, {"city"=>"Detroit", "year"=>"1999"}]}}
the database looks like: 该数据库如下所示:
players 玩家们
id: 1, name: George Baker
id:1,名字:乔治·贝克
id: 2, name: Bill Johnson
id:2,名字:比尔·约翰逊
teams 团队
id: 1, city: Buffalo
id:1,城市:布法罗
id: 2, city: Seattle
id:2,城市:西雅图
id: 3, city: Detroit
id:3,城市:底特律
affiliations 隶属关系
id: 1, player_id: 1, team_id: 1, year: null
id:1,player_id:1,team_id:1,年份:null
id: 2, player_id: 1, team_id: 2, year: null
id:2,player_id:1,team_id:2,年份:null
id: 3, player_id: 2, team_id: 1, year: null
id:3,player_id:2,team_id:1,年份:null
id: 4, player_id: null, team_id: 3, year: 1999
id:4,player_id:null,team_id:3,年份:1999
id: 5, player_id: 2, team_id: 3, year: null
id:5,player_id:2,team_id:3,年份:null
So three records were created when only two should have been. 因此,当只有两个记录时,将创建三个记录。 The affiliation record id: 3 is correct.
关联记录ID:3是正确的。 For id: 4, the player_id is missing.
对于ID:4,缺少player_id。 And for id: 5, the year is missing.
而对于ID:5,则缺少年份。
Obviously this is incorrect. 显然这是不正确的。 Where am I going wrong?
我要去哪里错了?
Thanks 谢谢
Edit 编辑
Ok, i think i have a better solution. 好的,我想我有一个更好的解决方案。 AFAIK, you can't use nested attributes on two levels of depth (though you could test it, maybe it works), but nothing prevents us to simulate this behavior :
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
Now, doing this : 现在,这样做:
@player = Player.new(
name: 'Bill Johnson',
affiliations_attributes: [
{year: 1999, team_attributes: {id: 1, city: 'Detroit}},
{team_attributes: {city: 'Somewhere else'}}
]
)
@player.save
should create all the required records, and still rollback everything in case of problems (because the save
itself is already wrapped in a transaction). 应该创建所有必需的记录,并在出现问题时仍回滚所有内容(因为
save
本身已经包装在事务中)。 As a bonus, all the errors will be associated to @player
! 另外,所有错误都将与
@player
关联!
How about this ?
这个怎么样 ?
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
This solution ended up working for me. 该解决方案最终为我工作。 If anyone uses this code for their own project, however, please know that I haven't tested any other actions besides create.
但是,如果有人在自己的项目中使用此代码,请注意,除了create之外,我没有测试过其他任何操作。 I'm certain some of this will change once I deal with read, update and delete.
我敢肯定,一旦我处理了读取,更新和删除操作,其中某些操作将会改变。
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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.