0

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 の create アクションを使用して、結合モデル属性なしで関連付けレコードを作成することに成功しました。

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

次のようなパラメーターを使用して、2 つのチームを持つ新しいプレーヤーを作成した後:

{"player"=>{"name"=>"George Baker", "teams"=>[{"city"=>"Buffalo"}, {"city"=>"Detroit"}]}}

データベースは次のようになります。

プレーヤー

id: 1、名前: ジョージ・ベイカー

チーム

id: 1、都市: バッファロー

id: 2、都市: シアトル

所属

ID: 1、player_id: 1、team_id: 1、年: null

ID: 2、player_id: 1、team_id: 2、年: 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

ここで、次のようなパラメータを使用して 2 つのチームを持つ新しいプレーヤーを作成する場合:

{"player"=>{"name"=>"Bill Johnson", "teams"=>[{"id"=>"1"}, {"city"=>"Detroit", "year"=>"1999"}]}}

データベースは次のようになります。

プレーヤー

id: 1、名前: ジョージ・ベイカー

id: 2、名前: ビル・ジョンソン

チーム

id: 1、都市: バッファロー

id: 2、都市: シアトル

id: 3、都市: デトロイト

所属

ID: 1、player_id: 1、team_id: 1、年: null

ID: 2、player_id: 1、team_id: 2、年: 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、年: null

そのため、2 つのレコードだけが作成されるはずだったのに、3 つのレコードが作成されました。所属レコードID:3は正しいです。id: 4 の場合、player_id がありません。id: 5 の場合、年がありません。

明らかにこれは正しくありません。どこが間違っていますか?

ありがとう

4

2 に答える 2

0

編集

わかりました、私はより良い解決策があると思います。私の知る限り、ネストされた属性を 2 レベルの深さで使用することはできません (ただし、テストすることはできますが、機能する可能性があります) が、この動作をシミュレートすることを妨げるものは何もありません。

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

于 2012-11-29T12:59:27.283 に答える
0

このソリューションは、私にとってはうまくいきました。ただし、誰かがこのコードを自分のプロジェクトに使用している場合は、作成以外のアクションをテストしていないことを知っておいてください。読み取り、更新、削除を処理すると、これの一部が変わると確信しています。

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
于 2012-12-02T05:40:22.257 に答える