programing

동일한 모델에서 여러 연결이있는 Rails Polymorphic Association

goodcopy 2021. 1. 18. 22:06
반응형

동일한 모델에서 여러 연결이있는 Rails Polymorphic Association


내 질문은 본질적으로 다음과 동일 합니다. 동일한 모델에 여러 연결이있는 다형성 연결

그러나 제안 / 수락 된 솔루션은 나중에 설명자가 설명하는 것처럼 작동하지 않습니다.

내 앱 전체에서 사용되는 Photo 클래스가 있습니다. 게시물에는 하나의 사진이있을 수 있습니다. 그러나 보조 사진을 추가하기 위해 다형성 관계를 다시 사용하고 싶습니다.

전에:

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를 호출하는 것을 제외하고는 단순히 사진 연결을 통해 첨부 된 동일한 사진을 반환합니다 (예 : Post # photo === Post # secondary_photo). SQL을 보면 내가 원하는대로 "SecondaryPhoto"대신 WHERE type = "Photo"를 수행합니다.

생각? 감사!


내 프로젝트에서 그렇게했습니다.

트릭은 사진에 기본 사진과 보조 사진을 구분하기 위해 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는 그렇게 할만큼 똑똑합니다.


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에 따라 foreign_key를 제공해야합니다. 그렇지 않으면 Rails가 사진 테이블에 post_id 열을 요청합니다. Attachable_type 열은 Rails 매직으로 채워집니다.SecondaryPhoto


이 게시물을 확인하는 사람들을위한 향후 참조

이것은 다음 코드를 사용하여 달성 할 수 있습니다.

레일즈 3 :

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

레일스 4 :

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

이유는 확실하지 않지만 Rails 3에서는 조건 및 class_name과 함께 foreign_key 값을 제공해야합니다. 다형성 유형을 설정할 때 자동으로 호출 클래스 이름을 사용하므로 'as : : attachable'을 사용하지 마십시오.

위의 내용은 has_many에도 적용됩니다.


다음과 같은 것이 쿼리에 작동했지만 사용자에서 주소로 할당이 작동하지 않았습니다.

사용자 클래스

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

이전 답변 중 어느 것도이 문제를 해결하는 데 도움이되지 않았으므로 다른 사람이이 문제에 부딪 힐 경우를 대비하여 여기에 넣겠습니다. 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인스턴스 에서 빌드 메서드를 호출해야한다는 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!

다른 사람에게 도움이되기를 바랍니다.


나는 그것을 사용하지 않았지만 주변을 검색하고 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유형 이름을 얻는 데 사용 됩니다. 내가 아는 한 당신은 그것으로 아무것도 할 수 없습니다.

그래서 내 제안은 사진 모델에 하나의 열을 추가하는 것 photo_type입니다. 첫 번째 사진이면 0으로, 두 번째 사진이면 1로 설정합니다. 연결 에서 각각 :conditions => {:photo_type => 0}및을 추가하십시오 :conditions => {:photo_type => 1}. 당신이 찾고있는 해결책이 아니라는 것을 알고 있지만 더 나은 것을 찾을 수 없습니다. 그건 그렇고, 그냥 has_many연관을 사용하는 것이 더 나을까요?


Your going to have to monkey patch the notion of foreign_type into has_one relationship. This is what i did for has_many. In a new .rb file in your initializers folder i called mine add_foreign_type_support.rb It lets you specify what your attachable_type is to be. Example: 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

For mongoid use this solution

Had tough times after discovering this issue but got cool solution that works

Add to your Gemfile

gem 'mongoid-multiple-polymorphic'

And this works like a charm:

  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

None of these solutions seem to work on Rails 5. For some reason, it looks like the behaviour around the association conditions has changed. When assigning the related object, the conditions don't seem to be used in the insert; only when reading the association.

My solution was to override the setter method for the association:

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

def photo=(photo)
  photo.photo_type = 'primary_photo'
  super
end

Can you add a SecondaryPhoto model like:

class SecondaryPhoto < Photo
end

and then skip the :class_name from the has_one :secondary_photo?

ReferenceURL : https://stackoverflow.com/questions/2494452/rails-polymorphic-association-with-multiple-associations-on-the-same-model

반응형