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
Rails Polymorphic Association with multiple associations on the same model
提问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?

