【问题标题】:incorrect database records created for rails 3 has_many :through association为 rails 3 has_many 创建的数据库记录不正确:通过关联
【发布时间】:2012-11-29 07:46:26
【问题描述】:

我有一个 has_many :through 关联。球员有很多球队,球队有很多球员。加入模型 Affiliation 属于 Players 和 Teams,并且还有一个 year 属性,用于每年跟踪玩家的团队隶属关系(或就业)。

我似乎无法根据以下规则找出建立关联的正确方法:

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

Player 模型如下所示:

class Player < ActiveRecord::Base
  attr_accessible :name
  
  has_many :affiliations, :dependent => :destroy
  has_many :teams, :through => :affiliations
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, :year
  belongs_to :player
  belongs_to :team
end

我已经成功地在没有加入模型属性的情况下使用 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)
        @player.teams << team_to_associate
      end
    end
    
    @player.save
    respond_with @player
  end
end

使用以下参数创建一个包含两个团队的新玩家后:

{"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 < 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

现在,当使用以下参数创建包含两个团队的新玩家时:

{"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, year: 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。对于 id: 5,缺少年份。

显然这是不正确的。我哪里错了?

谢谢

【问题讨论】:

    标签: ruby-on-rails associations has-many-through


    【解决方案1】:

    编辑

    好的,我想我有更好的解决方案。 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
    

    【讨论】:

    • 我试过了,它确实有效。所以谢谢!但我很好奇,有没有办法建立关联以便@group.save 保存所有内容?
    • 这取决于,我不知道@group 是什么,我只是从你的答案中复制/粘贴它
    • 对不起,这是一个错误。我正在从另一个示例中复制代码,然后将@group 留在了那里。我编辑了我的问题,所以 @group 现在是 @player。希望这可以澄清它。
    • nested attributes,但我认为你不能将它与两个关联级别一起使用。
    • 这是我根据我阅读和尝试的所有内容得出的结论。我对事务的唯一问题是如何处理异常。 create 应该引发异常,但我认为对于Team.find_or_create_by_idteam_to_associate.create,这些异常不会被添加到@player.errors(这是@player.create 的默认行为)。我可以捕获这些调用引发的异常并将它们添加到@player.errors,以便在我的响应中得到正确的异常消息吗?
    【解决方案2】:

    这个解决方案最终对我有用。但是,如果有人将此代码用于他们自己的项目,请知道我没有测试除创建之外的任何其他操作。我敢肯定,一旦我处理了读取、更新和删除,其中一些会发生变化。

    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-20
      • 2013-01-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-01
      相关资源
      最近更新 更多