Ruby-on-rails Rails 多态关联在同一模型上具有多个关联

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2494452/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 22:25:57  来源:igfitidea点击:

Rails Polymorphic Association with multiple associations on the same model

ruby-on-railspolymorphic-associations

提问by Matt Rogish

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

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

However, the proposed/accepted solution does not work, as illustrated by a commenter later.

但是,如稍后的评论者所示,提议/接受的解决方案不起作用。

I have a Photo class that is used all over my app. A post can have a single photo. However, I want to re-use the polymorphic relationship to add a secondary photo.

我有一个在我的应用程序中使用的 Photo 类。一个帖子可以有一张照片。但是,我想重新使用多态关系来添加次要照片。

Before:

前:

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

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

Desired:

期望:

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

However, this fails as it cannot find the class "SecondaryPhoto". Based on what I could tell from that other thread, I'd want to do:

但是,这失败了,因为它找不到“SecondaryPhoto”类。根据我从其他线程中可以得知的信息,我想做:

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

Except calling Post#secondary_photo simply returns the same photo that is attached via the Photo association, e.g. Post#photo === Post#secondary_photo. Looking at the SQL, it does WHERE type = "Photo" instead of, say, "SecondaryPhoto" as I'd like...

除了调用 Post#secondary_photo 简单地返回通过照片关联附加的相同照片,例如 Post#photo === Post#secondary_photo。查看 SQL,它确实 WHERE type = "Photo" 而不是我想要的 "SecondaryPhoto" ...

Thoughts? Thanks!

想法?谢谢!

回答by Max Chernyak

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 :conditionshere.

诀窍是照片需要一个列,将在 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

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.

这种方法的美妙之处在于,当您使用 来创建照片时@post.build_photo,photo_type 将自动预先填充相应的类型,例如“primary_photo”。ActiveRecord 足够聪明,可以做到这一点。

回答by Artem Aminov

Rails 4.2+

导轨 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

You need to provide foreign_key according ....able'ness or Rails will ask for post_id column in photo table. Attachable_type column will fills with Rails magic as SecondaryPhoto

您需要根据 ....able'ness 提供外键,否则 Rails 会要求在 photo 表中提供 post_id 列。Attachable_type 列将填充 Rails 魔法作为SecondaryPhoto

回答by PR Whitehead

None of the previous answers helped me solve this problem, so I'll put this here incase anyone else runs into this. Using Rails 4.2 +.

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

Create the migration (assuming you have an Addresses table already):

创建迁移(假设您已经有一个 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

Setup your polymorphic association:

设置你的多态关联:

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

Setup the class where the association will be called from:

设置将从中调用关联的类:

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

The trick is that you have to call the build method on the Orderinstance or the scopecolumn won't be populated.

诀窍是您必须在Order实例上调用 build 方法,否则scope将不会填充该列。

So this does NOT work:

所以这不起作用:

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

However, this DOES WORK.

但是,这确实有效。

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

Hope that helps someone else.

希望能帮助别人。

回答by sangyongjung

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

回答by JellyFishBoy

Future reference for people checking this post

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

This can be achieved using the following code...

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

Rails 3:

轨道 3:

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

Rails 4:

导轨 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.

不知道为什么,但在 Rails 3 中,您需要在条件和 class_name 旁边提供一个外键值。不要使用 'as::attachable' 因为这将在设置多态类型时自动使用调用类名。

The above applies to has_many too.

以上也适用于 has_many。

回答by klew

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 :)

我没有使用它,但我用谷歌搜索并查看了 Rails 资源,我认为你正在寻找的是:foreign_type. 试试看,看看它是否有效:)

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

I think that type in your question should be Postinstead of Photoand, respectively, it would be better to use SecondaryPostas it assigned to Postmodel.

我认为您的问题中的类型应该是Post而不是,Photo并且最好分别使用SecondaryPost它分配给Post模型。

EDIT:

编辑:

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

以上答案完全错误。:foreign_type可在多态模型belongs_to关联中指定包含关联模型类型的列的名称。

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

当我查看 Rails 源代码时,这一行设置了这种关联类型:

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

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

如您所见,它用于base_class.name获取类型名称。据我所知,您对此无能为力。

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_manyassociation?

所以我的建议是在 Photo 模型中添加一列,例如:photo_type. 如果是第一张照片,则将其设置为 0,如果是第二张照片,则将其设置为 1。在您的关联中,分别添加:conditions => {:photo_type => 0}:conditions => {:photo_type => 1}。我知道这不是您正在寻找的解决方案,但我找不到更好的解决方案。顺便说一句,也许只使用has_many关联会更好?

回答by simonslaw

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'

您将不得不将foreign_type 的概念修补到has_one 关系中。这就是我为 has_many 所做的。在您的初始化文件夹中的一个新 .rb 文件中,我将其命名为 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 #{}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

回答by comonitos

For mongoiduse this solution

对于mongoid使用此解决方案

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

发现这个问题后度过了艰难的时期,但得到了很酷的解决方案

Add to your Gemfile

添加到您的 Gemfile

gem 'mongoid-multiple-polymorphic'

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

回答by Chris Edwards

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.

这些解决方案似乎都不适用于 Rails 5。出于某种原因,关联条件的行为似乎发生了变化。分配相关对象的时候,插入中好像没有用到条件;只有在阅读协会时。

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

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

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

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

回答by Rob Biedenharn

Can you add a SecondaryPhoto model like:

您可以添加一个 SecondaryPhoto 模型,例如:

class SecondaryPhoto < Photo
end

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

然后从 has_one :secondary_photo 中跳过 :class_name?