web-dev-qa-db-ja.com

Rails同じモデルに複数の関連付けがある多態的な関連付け

私の質問は基本的にこれと同じです: 同じモデルに複数の関連付けがある多態的な関連付け

ただし、提案または承認されたソリューションは機能しません。

アプリ全体で使用されるPhotoクラスがあります。投稿には1枚の写真を含めることができます。ただし、2次的な写真を追加するために、多態的な関係を再利用したいと思います。

前:

class Photo 
   belongs_to :attachable, :polymorphic => true
end

class Post
   has_one :photo, :as => :attachable, :dependent => :destroy
end

望ましい:

class Photo 
   belongs_to :attachable, :polymorphic => true
end

class Post
   has_one :photo,           :as => :attachable, :dependent => :destroy
   has_one :secondary_photo, :as => :attachable, :dependent => :destroy
end

ただし、クラス「SecondaryPhoto」が見つからないため、これは失敗します。その他のスレッドから私が知ることができることに基づいて、私はしたいと思います:

   has_one :secondary_photo, :as => :attachable, :class_name => "Photo", :dependent => :destroy

Post#secondary_photoを呼び出すことを除いて、Photoアソシエーションを介して添付された同じ写真を単に返します。 Post#photo === Post#secondary_photo。 SQLを見ると、 "SecondaryPhoto"の代わりにWHERE type = "Photo"を実行します。

考え?ありがとう!

56
Matt Rogish

私は自分のプロジェクトでそれを行いました。

トリックは、写真がプライマリ写真とセカンダリ写真を区別するためにhas_one条件で使用される列を必要とすることです。ここで:conditionsで何が起こっているかに注意してください。

has_one :photo, :as => 'attachable', 
        :conditions => {:photo_type => 'primary_photo'}, :dependent => :destroy

has_one :secondary_photo, :class_name => 'Photo', :as => 'attachable',
        :conditions => {:photo_type => 'secondary_photo'}, :dependent => :destroy

このアプローチの優れている点は、@post.build_photoを使用して写真を作成すると、photo_typeに「primary_photo」などの対応するタイプが自動的に事前入力されることです。 ActiveRecordはそれを行うのに十分スマートです。

69
Max Chernyak

Rails 4.2以降

class Photo
   belongs_to :attachable, :polymorphic => true
end

class Post
   has_one :photo, :as => :attachable, :dependent => :destroy
   has_one :secondary_photo, -> { where attachable_type: "SecondaryPhoto"},
     class_name: Photo, foreign_key: :attachable_id,
     foreign_type: :attachable_type, dependent: :destroy
end

.... able'nessまたはRailsは写真テーブルのpost_id列を要求します。Attachable_type列はRails magic as SecondaryPhoto

21
Artem Aminov

この投稿をチェックする人のための将来のリファレンス

これは、次のコードを使用して実現できます...

Rails 3:

has_one :banner_image, conditions: { attachable_type: 'ThemeBannerAttachment' }, class_name: 'Attachment', foreign_key: 'attachable_id', dependent: :destroy

Rails 4:

has_one :banner_image, -> { where attachable_type: 'ThemeBannerAttachment'}, class_name: 'Attachment', dependent: :destroy

理由は不明ですが、Rails 3では、条件とclass_nameに加えてforeign_key値を指定する必要があります。「as::attachable」を使用しないでください。これにより、呼び出しクラス名が自動的に使用されます。多相型を設定します。

上記はhas_manyにも適用されます。

5
JellyFishBoy

クエリには次のようなものが機能しましたが、ユーザーからアドレスへの割り当てが機能しませんでした

ユーザークラス

has_many :addresses, as: :address_holder
has_many :delivery_addresses, -> { where :address_holder_type => "UserDelivery" },
       class_name: "Address", foreign_key: "address_holder_id"

アドレスクラス

belongs_to :address_holder, polymorphic: true
5
sangyongjung

以前の回答のどれも私がこの問題を解決するのに役立たなかったので、誰かがこれに遭遇した場合に備えて、ここにこれを置きます。 Rails 4.2 +。

移行を作成します(すでにAddressesテーブルがあると仮定します)。

class AddPolymorphicColumnsToAddress < ActiveRecord::Migration
  def change
    add_column :addresses, :addressable_type, :string, index: true
    add_column :addresses, :addressable_id, :integer, index: true
    add_column :addresses, :addressable_scope, :string, index: true
  end
end

多態的な関連付けを設定します。

class Address < ActiveRecord::Base
  belongs_to :addressable, polymorphic: true
end

関連付けが呼び出されるクラスを設定します。

class Order < ActiveRecord::Base
  has_one :bill_address, -> { where(addressable_scope: :bill_address) }, as: :addressable,  class_name: "Address", dependent: :destroy
  accepts_nested_attributes_for :bill_address, allow_destroy: true

  has_one :ship_address, -> { where(addressable_scope: :ship_address) }, as: :addressable, class_name: "Address", dependent: :destroy
  accepts_nested_attributes_for :ship_address, allow_destroy: true
end

コツは、Orderインスタンスでbuildメソッドを呼び出さなければならないことです。そうしないと、scope列に値が入力されません。

したがって、これは機能しません:

address = {attr1: "value"... etc...}
order = Order.new(bill_address: address)
order.save!

ただし、これは機能します。

address = {attr1: "value"... etc...}
order = Order.new
order.build_bill_address(address)
order.save!

それが他の誰かを助けることを願っています。

4
PR Whitehead

私はそれを使用しませんでしたが、グーグルでRailsソースを調べました。あなたが探しているのは:foreign_typeだと思います。試してみて、機能するかどうかを確認してください。 )

has_one :secondary_photo, :as => :attachable, :class_name => "Photo", :dependent => :destroy, :foreign_type => 'SecondaryPost'

あなたの質問のタイプはPostではなくPhotoである必要があると思います。それぞれ、SecondaryPostモデルに割り当てられているPostを使用することをお勧めします。

編集:

上記の答えは完全に間違っています。 :foreign_typeは、関連モデルのタイプを含む列の名前を指定するbelongs_toアソシエーションのポリモーフィックモデルで使用できます。

Railsソースを見ると、この行はこのタイプを関連付けに設定します。

dependent_conditions << "#{reflection.options[:as]}_type = '#{base_class.name}'" if reflection.options[:as]

ご覧のとおり、型名を取得するためにbase_class.nameを使用しています。私が知る限り、あなたはそれで何もすることができません。

したがって、私のモデルは、写真モデルに1つの列を追加することです(例:photo_type)。最初の写真の場合は0に設定し、2番目の写真の場合は1に設定します。関連付けに、それぞれ:conditions => {:photo_type => 0}:conditions => {:photo_type => 1}を追加します。あなたが探している解決策ではないことは知っていますが、これ以上の解決策はありません。ちなみに、has_manyアソシエーションを使用する方が良いのではないでしょうか。

3
klew

モンキーがforeign_typeの概念をhas_one関係にパッチする必要があります。これは私がhas_manyのためにやったことです。イニシャライザーフォルダーの新しい.rbファイルで、私はmineを呼び出しましたadd_foreign_type_support.rbこれにより、attachable_typeを指定できます。例:has_many photo、:class_name => "Picture"、:as => attachable、:foreign_type => 'Pic'

module ActiveRecord
  module Associations
    class HasManyAssociation < AssociationCollection #:nodoc:
      protected
        def construct_sql
          case
            when @reflection.options[:Finder_sql]
              @Finder_sql = interpolate_sql(@reflection.options[:Finder_sql])
           when @reflection.options[:as]
              resource_type = @reflection.options[:foreign_type].to_s.camelize || @owner.class.base_class.name.to_s
              @Finder_sql =  "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND "
              @Finder_sql += "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(resource_type)}"
              else
                @Finder_sql += ")"
              end
              @Finder_sql << " AND (#{conditions})" if conditions

            else
              @Finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
              @Finder_sql << " AND (#{conditions})" if conditions
          end

          if @reflection.options[:counter_sql]
            @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
          elsif @reflection.options[:Finder_sql]
            # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
            @reflection.options[:counter_sql] = @reflection.options[:Finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
            @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
          else
            @counter_sql = @Finder_sql
          end
        end
    end
  end
end
# Add foreign_type to options list
module ActiveRecord
  module Associations # :nodoc:
     module ClassMethods
      private
        mattr_accessor :valid_keys_for_has_many_association
        @@valid_keys_for_has_many_association = [
          :class_name, :table_name, :foreign_key, :primary_key, 
          :dependent,
          :select, :conditions, :include, :order, :group, :having, :limit, :offset,
          :as, :foreign_type, :through, :source, :source_type,
          :uniq,
          :Finder_sql, :counter_sql,
          :before_add, :after_add, :before_remove, :after_remove,
          :extend, :readonly,
          :validate, :inverse_of
        ]

    end
  end
2
simonslaw

mongoidの場合、このソリューションを使用します

この問題を発見してから苦労しましたが、機能するクールな解決策を得ました

Gemfileに追加

宝石「mongoid-multiple-polymorphic」

そしてこれは魅力のように機能します:

  class Resource

  has_one :icon, as: :assetable, class_name: 'Asset', dependent: :destroy, autosave: true
  has_one :preview, as: :assetable, class_name: 'Asset', dependent: :destroy, autosave: true

  end
1
comonitos

これらのソリューションはいずれもRails 5.では機能しないようです。何らかの理由で、関連付け条件の周囲の動作が変更されたようです。関連オブジェクトを割り当てると、条件は挿入で使用され、関連付けを読み取る場合のみ。

私の解決策は、関連付けのセッターメソッドをオーバーライドすることでした。

has_one :photo, -> { photo_type: 'primary_photo'},
        as: 'attachable',
        dependent: :destroy

def photo=(photo)
  photo.photo_type = 'primary_photo'
  super
end
1
Chris Edwards

次のようなSecondaryPhotoモデルを追加できますか?

class SecondaryPhoto < Photo
end

そして、has_one:secondary_photoから:class_nameをスキップしますか?

0
Rob Biedenharn