Rails多态关联在同一模型上具有多个关联

时间:2010-03-22 17:41:48

标签: ruby-on-rails polymorphic-associations

我的问题基本上与这个问题相同: Polymorphic Association with multiple associations on the same model

然而,提议/接受的解决方案不起作用,如后来的评论者所示。

我有一个在我的应用程序中使用的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之外,只返回通过Photo关联附加的相同照片,例如发布#photo ===发布#second_photo。看看SQL,它确实是WHERE type =“Photo”而不是像我想要的那样“SecondaryPhoto”......

思考?谢谢!

12 个答案:

答案 0 :(得分:69)

我已经在我的项目中完成了这项工作。

诀窍是照片需要一个将在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非常聪明,可以做到这一点。

答案 1 :(得分:20)

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

您需要提供foreign_key,因为......或者Rails会在照片表中询问post_id列。 Attachable_type列将填充Rails魔法SecondaryPhoto

答案 2 :(得分:5)

查看此帖子的人的未来参考

这可以使用以下代码实现......

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中,您需要提供foreign_key值以及条件和class_name。不要使用'as :: attachachable',因为这会在设置多态类型时自动使用调用类名。

以上内容也适用于has_many。

答案 3 :(得分:5)

以下内容适用于查询,但是从用户分配到地址没有工作

用户类

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

答案 4 :(得分:3)

我没有使用它,但我用Google搜索并查看了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模型中添加一列,例如:photo_type。如果是第一张照片,则将其设置为0;如果是第二张照片,则将其设置为1。在您的关联中,分别添加:conditions => {:photo_type => 0}:conditions => {:photo_type => 1}。我知道这不是你想要的解决方案,但我找不到更好的东西。顺便说一句,也许最好只使用has_many关联?

答案 5 :(得分:3)

之前的答案都没有帮助我解决这个问题,所以我把这个放在这里,任何其他人都会遇到这个问题。使用Rails 4.2 +。

创建迁移(假设您已经有一个地址表):

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!

希望能帮到别人。

答案 6 :(得分:2)

你必须将foreign_type的概念修改为has_one关系。 这就是我为has_many所做的。在初始化文件夹文件夹中的一个新的.rb文件中,我调用了我的add_foreign_type_support.rb 它允许您指定attachable_type的内容。 示例:has_many photo,:class_name =&gt; “图片”,:as =&gt;可附加的,:foreign_type =&gt; '产品图'

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

答案 7 :(得分:1)

对于 mongoid ,请使用此解决方案

在发现这个问题之后遇到了困难时期但是得到了很酷的解决方案

添加到您的Gemfile

  

gem'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

答案 8 :(得分:1)

这些解决方案似乎都不适用于Rails 5.出于某种原因,看起来围绕关联条件的行为已经发生了变化。分配相关对象时,插入中似乎不使用这些条件;只有在阅读协会时才会这样做。

我的解决方案是覆盖关联的setter方法:

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

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

答案 9 :(得分:0)

您可以添加如下的SecondaryPhoto模型:

class SecondaryPhoto < Photo
end

然后从has_one:secondary_photo?

跳过:class_name

答案 10 :(得分:0)

可能有点晚了,但这可能对某人有所帮助,所以我是这样解决这个问题的 (rails 5.2, ruby 2.6):

我向模型添加了一个名为 enumkind,然后向 has_one 关联添加了适当的范围:

class Photo 
   belongs_to :attachable, :polymorphic => true
   enum kind: %i[first_photo secondary_photo]
end

class Post
   has_one :photo, -> { where(kind: :first_photo) }, :as => :attachable, :dependent => :destroy
   has_one :secondary_photo, -> { where(kind: :secondary_photo) }, :as => :attachable, :dependent => :destroy
end

需要作用域是因为 ActiveRecord 可以区分对象/关联。

希望以上有帮助! ?

答案 11 :(得分:0)

  has_one :photo, -> { where attachable_type: "Photo" }, foreign_key: :attachable_id, class_name: Attachment.to_s, dependent: :destroy
  has_one :logo, -> { where attachable_type: "Logo" }, foreign_key: :attachable_id, class_name: Attachment.to_s, dependent: :destroy

附加时:

  ActiveRecord::Base.transaction do
     attachment = user.attachments.find( id )
     user.logo = attachment
     user.save

     attachment.update( attachable_type: "Logo" )
     attachment.save
  end