Rails Polymorphic Association with multiple associations on the same model

后端 未结 10 1857
无人及你
无人及你 2020-11-28 03:01

My question is essentially the same as this one: Polymorphic Association with multiple associations on the same model

However, the proposed/accepted solution does no

相关标签:
10条回答
  • 2020-11-28 03:38

    I have done that in my project.

    The trick is that photos need a column that will be used in has_one condition to distinguish between primary and secondary photos. Pay attention to what happens in :conditions here.

    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
    

    The beauty of this approach is that when you create photos using @post.build_photo, the photo_type will automatically be pre-populated with corresponding type, like 'primary_photo'. ActiveRecord is smart enough to do that.

    0 讨论(0)
  • 2020-11-28 03:40

    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
    
    0 讨论(0)
  • 2020-11-28 03:42

    I didn't use it, but I googled around and looked into Rails sources and I think that what you're looking for is :foreign_type. Try it and tell if it works :)

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

    I think that type in your question should be Post instead of Photo and, respectively, it would be better to use SecondaryPost as it assigned to Post model.

    EDIT:

    Above answer is completly wrong. :foreign_type is availble in polymorphic model in belongs_to association to specify name of the column that contains type of associated model.

    As I look in Rails sources, this line sets this type for association:

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

    As you can see it uses base_class.name to get type name. As far as I know you can do nothing with it.

    So my sugestion is to add one column to Photo model, on example: photo_type. And set it to 0 if it is first photo, or set it to 1 if it is second photo. In your associations add :conditions => {:photo_type => 0} and :conditions => {:photo_type => 1}, respectively. I know it is not a solution you are looking for, but I can't find anything better. By the way, maybe it would be better to just use has_many association?

    0 讨论(0)
  • 2020-11-28 03:42

    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
    
    0 讨论(0)
  • 2020-11-28 03:43

    Future reference for people checking this post

    This can be achieved using the following code...

    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
    

    Not sure why, but in Rails 3, you need to supply a foreign_key value alongside the conditions and class_name. Do not use 'as: :attachable' as this will automatically use the calling class name when setting the polymorphic type.

    The above applies to has_many too.

    0 讨论(0)
  • 2020-11-28 03:56

    Something like following worked for querying, but assigning from User to address didn't work

    User Class

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

    Address Class

    belongs_to :address_holder, polymorphic: true
    
    0 讨论(0)
提交回复
热议问题