web-dev-qa-db-ja.com

ポリモーフィックな関連付けのタイプ列がSTIのベースモデルを指していない場合、ポリモーフィックな関連付けがSTIで機能しないのはなぜですか?

私はここで多態性関連とSTIの事例を持っています。

# app/models/car.rb
class Car < ActiveRecord::Base
  belongs_to :borrowable, :polymorphic => true
end

# app/models/staff.rb
class Staff < ActiveRecord::Base
  has_one :car, :as => :borrowable, :dependent => :destroy
end

# app/models/guard.rb
class Guard < Staff
end

ポリモーフィックな関連付けが機能するためには、ポリモーフィックな関連付けに関するAPIドキュメントに従って http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations 設定する必要があることborrowable_typeからbase_classof STIモデル、つまり私の場合はStaffです。

質問は、次のとおりです。なぜborrowable_type STIクラスに設定しますか?

それを証明するいくつかのテスト:

# now the test speaks only truth

# test/fixtures/cars.yml
one:
  name: Enzo
  borrowable: staff (Staff)

two:
  name: Mustang
  borrowable: guard (Guard)

# test/fixtures/staffs.yml
staff:
  name: Jullia Gillard

guard:
  name: Joni Bravo
  type: Guard 

# test/units/car_test.rb

require 'test_helper'

class CarTest < ActiveSupport::TestCase
  setup do
    @staff = staffs(:staff)
    @guard = staffs(:guard) 
  end

  test "should be destroyed if an associated staff is destroyed" do
    assert_difference('Car.count', -1) do
      @staff.destroy
    end
  end

  test "should be destroyed if an associated guard is destroyed" do
    assert_difference('Car.count', -1) do
      @guard.destroy
    end
  end

end

しかし、Staffインスタンスでのみ当てはまるようです。結果は次のとおりです。

# Running tests:

F.

Finished tests in 0.146657s, 13.6373 tests/s, 13.6373 assertions/s.

  1) Failure:
test_should_be_destroyed_if_an_associated_guard_is_destroyed(CarTest) [/private/tmp/guineapig/test/unit/car_test.rb:16]:
"Car.count" didn't change by -1.
<1> expected but was
<2>.

ありがとう

42
Trung Lê

良い質問。私はRails 3.1を使用してまったく同じ問題を抱えていました。これは機能しないため、これを行うことができないようです。おそらく意図された動作です。明らかに、単一のテーブルと組み合わせて多態的な関連付けを使用していますRailsの継承(STI)は少し複雑です。

現在のRailsのドキュメントRails 3.2は、 polymorphic associationsとSTI)を組み合わせるためのこのアドバイスを提供します

単一テーブル継承(STI)と組み合わせてポリモーフィックな関連付けを使用するのは少し注意が必要です。関連付けが期待どおりに機能するようにするには、STIモデルの基本モデルを多態性の関連付けのタイプ列に格納してください。

あなたの場合、基本モデルは「スタッフ」になります。つまり、「ガード」ではなく、「borrowable_type」はすべてのアイテムで「スタッフ」になります。 「becomes」を使用して、派生クラスを基本クラスとして表示させることができます:guard.becomes(Staff)。列「borrowable_type」を基本クラス「Staff」に直接設定するか、Railsドキュメントが示唆するように、

class Car < ActiveRecord::Base
  ..
  def borrowable_type=(sType)
     super(sType.to_s.classify.constantize.base_class.to_s)
  end
35
0x4a6f4672

古い質問ですが、Rails 4の問題はまだ残っています。別のオプションは、_typeメソッドを動的に作成/上書きすることです。これは、アプリが複数のSTIとのポリモーフィックな関連付けで、ロジックを1か所に保持したい。

この問題により、すべての多態的な関連付けが取得され、レコードが常に基本クラスを使用して保存されるようになります。

# models/concerns/single_table_polymorphic.rb
module SingleTablePolymorphic
  extend ActiveSupport::Concern

  included do
    self.reflect_on_all_associations.select{|a| a.options[:polymorphic]}.map(&:name).each do |name|
      define_method "#{name.to_s}_type=" do |class_name|
        super(class_name.constantize.base_class.name)
      end
    end
  end
end

次に、それをモデルに含めます。

class Car < ActiveRecord::Base
  belongs_to :borrowable, :polymorphic => true
  include SingleTablePolymorphic
end
17
alkalinecoffee

Rails 4.2でこの問題が発生しました。私は解決する2つの方法を見つけました:

-

問題は、RailsがSTI関係のbase_class名を使用することです。

この理由は他の回答に記載されていますが、要点は、コアチームがtableを参照できるはずだと感じているように見えることですポリモーフィックSTIアソシエーションのclassではなく。

私はこの考えに同意しませんが、Railsコアチームの一部ではないので、それを解決するための多くの情報を持っていません。

修正するには2つの方法があります。

-

1)モデルレベルで挿入:

class Association < ActiveRecord::Base

  belongs_to :associatiable, polymorphic: true
  belongs_to :associated, polymorphic: true

  before_validation :set_type

  def set_type
    self.associated_type = associated.class.name
  end
end

これにより、データベースにデータを作成する前に{x}_typeレコードが変更されます。これは非常にうまく機能し、関連付けの多態性を保持しています。

2)Core ActiveRecordメソッドをオーバーライドする

#app/config/initializers/sti_base.rb
require "active_record"
require "active_record_extension"
ActiveRecord::Base.store_base_sti_class = false

#lib/active_record_extension.rb
module ActiveRecordExtension #-> http://stackoverflow.com/questions/2328984/Rails-extending-activerecordbase

  extend ActiveSupport::Concern

  included do
    class_attribute :store_base_sti_class
    self.store_base_sti_class = true
  end
end

# include the extension 
ActiveRecord::Base.send(:include, ActiveRecordExtension)

####

module AddPolymorphic
  extend ActiveSupport::Concern

  included do #-> http://stackoverflow.com/questions/28214874/overriding-methods-in-an-activesupportconcern-module-which-are-defined-by-a-cl
    define_method :replace_keys do |record=nil|
      super(record)
      owner[reflection.foreign_type] = ActiveRecord::Base.store_base_sti_class ? record.class.base_class.name : record.class.name
    end
  end
end

ActiveRecord::Associations::BelongsToPolymorphicAssociation.send(:include, AddPolymorphic)

問題を修正するより体系的な方法は、それを管理するActiveRecordコアメソッドを編集することです。 this gem で参照を使用して、修正またはオーバーライドする必要のある要素を見つけました。

これはテストされておらず、ActiveRecordコアメソッドの他の部分の拡張機能が必要ですが、私のローカルシステムでは機能するようです。

15
Richard Peck

宝石があります。 https://github.com/appfolio/store_base_sti_class

テスト済みで、ARのさまざまなバージョンで動作します。

6
across

ポリモーフィック型のhas_*関連付けのカスタムスコープを構築することもできます。

class Staff < ActiveRecord::Base
  has_one :car, 
          ->(s) { where(cars: { borrowable_type: s.class }, # defaults to base_class
          foreign_key: :borrowable_id,
          :dependent => :destroy
end

ポリモーフィック結合は複合外部キー(* _idおよび* _type)を使用するため、type句に正しい値を指定する必要があります。ただし、_idは、ポリモーフィック関連付けの名前を指定するforeign_key宣言だけで機能するはずです。

ポリモーフィズムの性質上、Railsアプリケーション内の任意のモデルである可能性があるため、whatモデルは借用可能であると知ってイライラすることがあります。この関係は、借用可能オブジェクトのカスケード削除を適用するすべてのモデルで宣言する必要があります。

0
Ben Simpson

これはもっと簡単なはずであるという一般的なコメントに同意します。とはいえ、ここで私のために働いたものです。

次のように、基本クラスとして会社、STIクラスとして顧客とプロスペクトを使用したモデルがあります。

class Firm
end

class Customer < Firm
end

class Prospect < Firm
end

また、次のような多態性クラスOpportunityもあります。

class Opportunity
  belongs_to :opportunistic, polymorphic: true
end

機会をどちらかと言いたい

customer.opportunities

または

prospect.opportunities

そのために、モデルを次のように変更しました。

class Firm
  has_many opportunities, as: :opportunistic
end

class Opportunity
  belongs_to :customer, class_name: 'Firm', foreign_key: :opportunistic_id
  belongs_to :prospect, class_name: 'Firm', foreign_key: :opportunistic_id
end

Opportunistic_typeを 'Firm'(基本クラス)にして、それぞれの顧客または見込み客IDをopportunistic_idとして商談を保存します。

これで、customer.opportunitiesとpromise.opportunitiesを希望どおりに取得できます。

0
Wes