2012-03-09 20 views
32

ここでは、多型関連とSTIの症例があります。多型assocationが動作するためには多型関連の型の列が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 

、私はSTIモデルのbase_classborrowable_typeを設定する必要が多形Assocation、http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+AssociationsのAPIドキュメントによると、それは私の場合ですが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 

しかし、それだけスタッフインスタンスに真であるように思われます。結果は以下のとおりです。

# 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>. 

おかげ

答えて

26

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

レール3.2の現在のRailsドキュメントはpolymorphic associations and STIを組み合わせるためのこのアドバイスを与えます。 へのアソシエーションが期待通りに機能するように、STI モデルの基本モデルをポリモーフィックアソシエーションのtypeカラムに格納してください。

基本モデルは「スタッフ」、つまり「ガード」ではなく「borrowable_type」が「スタッフ」である必要があります。 "become"を使用すると、派生クラスを基本クラスとして表示させることができます:guard.becomes(Staff)。 Railsのドキュメントが示唆一つは、基本クラス「スタッフ」に直接コラム「borrowable_type」を設定することができ、またはとして、自動的に

class Car < ActiveRecord::Base 
    .. 
    def borrowable_type=(sType) 
    super(sType.to_s.classify.constantize.base_class.to_s) 
    end 
+6

つまり、Carテーブルは常に 'borrowable_type'列が 'Staff'に設定され、Guardには決して送られないため、CarとGuardを関連付けることはできず、 '@ guard.car'で取得することはできません。そしてそれは、STIモデル上の多型関連が完全に役に立たないことを意味します。 – dekeguard

+0

これは私のために働いた可能性があります。うーん...それはボックスの外で動作しないことは奇妙です。私は理由を見ることができません。 – Macario

+0

リンクと説明のための+1 ... –

13

古い質問を使用して、それを変換しますが、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 
+0

ありがとう!非常にうまくいきます! – coderuby

+0

問題ディレクトリをロードパスに追加することが重要です。[Railsのロードパスへのディレクトリの追加](http://stackoverflow.com/a/12826228/1835865)。そうしないと、初期化されていない定数エラーが発生します。 – coderuby

0

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

ように、私は、STIクラスとして基本クラスと顧客としてしっかりと展望を持つモデルを持っている:

class Firm 
end 

class Customer < Firm 
end 

class Prospect < Firm 
end 

は、私もこのようになりますポリモーフィッククラス、機会を持っています私はどちらか

customer.opportunities 

またはような機会を参照したい

class Opportunity 
    belongs_to :opportunistic, polymorphic: true 
end 
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とprospect.opportunitiesを私が望むように得ることができます。

6

Rails 4.2でこの問題が発生しました。問題はRailsがSTI関係のbase_class名前を使用することである

-

:私は解決するには2つの方法を発見しました。

その理由は、他の回答に記載されているが、要点は、コアチームはあなたが多型STIアソシエーションにテーブルではなくクラスを参照することができるはずと感じているようだということです。

私はこのアイデアには同意しますが、Rails Coreチームには属していないので、それを解決するための情報はあまりありません。

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レコードを変更します: -

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

:それを修正する方法は2つあります

データベース。これは非常にうまく動作し、依然として関連の多形性を保持しています。

2)オーバーライドコア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コアメソッドの他の部分の拡張機能が必要ですが、ローカルシステムでは機能するようです。

+0

rails coreの関連コードへの参照:https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb#L16 – Lax

5

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

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

+0

共有のための素晴らしい発見、これは素晴らしい私のプロジェクト – SupaIrish

+0

これは最高の投票回答になるはずです。私は指示どおりの宝石をインストールし、期待どおりのすべての動作。ありがとう! –

関連する問題