Rails多态关联has_many

14

如何在Ruby on Rails中实现多态的has_many关联,其中拥有者始终是已知的,但关联中的项目将是某个多态(但同质)类型,由拥有者中的一个列指定?例如,假设Producerhas_many产品,但制造商实例可能实际上有许多自行车、冰棒或鞋带。我可以很容易地使每个产品类(自行车、冰棒等)与生产者之间具有belongs_to关系,但是给定制造商实例,如果它们是不同类型的产品(针对每个制造商实例),那么如何获取产品集合呢?

Rails的多态关联允许制造商属于多个产品,但我需要相反的关系。例如:

class Bicycle < ActiveRecord::Base
  belongs_to :producer
end

class Popsicle < ActiveRecord::Base
  belongs_to :producer
end

class Producer < ActiveRecord::Base
  has_many :products, :polymorphic_column => :type # last part is made-up...
end

我的Producer表已经有一个"类型"列,对应一些产品类别(例如:自行车,冰棒等),但是如何让Rails让我做像这样的事情:

>> bike_producer.products
#=> [Bicycle@123, Bicycle@456, ...]
>> popsicle_producer.products
#=> [Popsicle@321, Popsicle@654, ...]

如果这是显而易见的或者常见的重复问题,我很抱歉;但是我发现这很困难。


只是一个提醒,我强烈建议不要使用 Factory 作为模型名称,因为 factory_girl 是一个非常常用的扩展,用于生成模型而不是固定装置,这可能会对阅读您的代码的人造成很大的困惑。 - Jamie Wong
没问题。另外,我还没有找到一个好的解决方案。据我所知,在Rails中,对象具有多态子类的能力尚不存在。不过,你可以参考一下http://blog.hasmanythrough.com/2006/4/3/polymorphic-through。 - Jamie Wong
是的,我刚读了那篇文章。我已经编写了一个简单的实例方法,使用列值到类名的映射,并执行find_by_producer_id,这很好用,但我没有得到任何关联便利方法。我认为这肯定是一个已解决的问题,但也许不是... - maerics
STI很好,但是产品类之间(无论是属性还是行为)都不相关,所以继承没有意义。多表继承将是一个不错的选择(基类只有producer_id,子类会有自己的列/属性),但似乎Rails中不存在这种模式... - maerics
你必须在生产者上使用STI。 - rewritten
显示剩余3条评论
6个回答

7

你需要在生产者上使用STI技术,而不是在产品上使用。这样,每种类型的生产者都有不同的行为,但在一个单独的producers表中。

几乎没有任何多态性!

class Product < ActiveRecord::Base
  # does not have a 'type' column, so there is no STI here,
  # it is like an abstract superclass.
  belongs_to :producer
end

class Bicycle < Product
end

class Popsicle < Product
end

class Producer < ActiveRecord::Base
  # it has a 'type' column so we have STI here!!
end

class BicycleProducer < Producer
  has_many :products, :class_name => "Bicycle", :inverse_of => :producer
end

class PopsicleProducer < Producer
  has_many :products, :class_name => "Popsicle", :inverse_of => :producer
end

有没有让产品继承相同的父类会影响它们的可变性呢?例如,自行车是否可以有一个"frame_material"属性,而冰棒则有一个"flavor"属性? - maerics
@maerics,这里没有单表继承(STI),所有属性都来自于各个子类的特定表格。 - rewritten

2

请按照格式要求进行操作

class Bicycle < ActiveRecord::Base 
  belongs_to :bicycle_obj,:polymorphic => true 
end 

class Popsicle < ActiveRecord::Base
  belongs_to :popsicle_obj , :polymorphic => true 
end 

class Producer < ActiveRecord::Base 
  has_many :bicycles , :as=>:bicycle_obj 
  has_many :popsicle , :as=>:popsicle_obj 
end 

请使用这段代码。如果您遇到任何问题,请留下评论。

等一下 - 你为什么发了两个答案? - Jamie Wong
我的评论回答格式不正确,所以我提供了一个单独的答案。 - user386660
感谢@Jamie Wong的修订。 实际上,我是Stack Overflow的新手,不太了解文本格式化。 - user386660
如果您需要很多空间来回复评论,请编辑您的原始答案并注明这是对评论的回应。 - Jamie Wong
这种策略并不是我想要的,因为当我有一个生产者时,我需要知道它将生产什么样的产品,以便决定是调用 p.bicycles 还是 p.popsicles,我希望有一个单一的方法来返回产品(例如 p.products)。 - maerics

1

这是我目前正在使用的解决方法。它不提供从真正的ActiveRecord::Associations获得的任何便利方法(集合操作),但它确实提供了一种获取给定生产者产品列表的方法:

class Bicycle < ActiveRecord::Base
  belongs_to :producer
end

class Popsicle < ActiveRecord::Base
  belongs_to :producer
end

class Producer < ActiveRecord::Base
  PRODUCT_TYPE_MAPPING = {
    'bicycle' => Bicycle,
    'popsicle' => Popsicle
  }.freeze
  def products
    klass = PRODUCT_TYPE_MAPPING[self.type]
    klass ? klass.find_all_by_producer_id(self.id) : []
  end
end

另一个缺点是我必须维护类型字符串到类型类的映射,但这可以自动化。然而,这个解决方案对我的目的来说已经足够了。

0
我发现Rails中的多态关联文档不够充分。虽然有单表继承模式,但是如果你没有使用单表继承,那么就会缺少一些信息。
可以使用:polymorphic => true选项启用belongs_to关联。但是,除非您使用单表继承,否则has_many关联将无法工作,因为它需要知道可能具有外键的表集。
(根据我所发现的),我认为最好的解决方案是为基类创建一个表和模型,并在基表中设置外键。
create_table "products", :force => true do |table|
    table.integer  "derived_product_id"
    table.string   "derived_product_type"
    table.integer  "producer_id"
  end

  class Product < ActiveRecord::Base
    belongs_to :producer
  end

  class Producer < ActiveRecord::Base
    has_many :products
  end

然后,对于一个生产对象(producer),您应该使用 producer.products.derived_products 获取产品。

我还没有尝试过使用 has_many through 来将关联压缩到 producer.derived_products,因此无法评论如何使其工作。


0
这是我是如何做到的,可以生成正确的SQL查询语句:
class Message < ApplicationRecord
  module FromTypes
    USER = "User"
    PHONE_NUMBER = "PhoneNumber"
    ALL = [USER, PHONE_NUMBER]
  end

  module ToTypes
    USER = "User"
    PHONE_NUMBER = "PhoneNumber"
    ALL = [USER, PHONE_NUMBER]
  end

  belongs_to :from, polymorphic: true
  belongs_to :to, polymorphic: true

  scope :from_user, -> { where(from_type: FromTypes::USER) }
  scope :from_phone_number, -> { where(from_type: FromTypes::PHONE_NUMBER) }
  scope :to_user, -> { where(to_type: ToTypes::USER) }
  scope :to_phone_number, -> { where(from_type: ToTypes::PHONE_NUMBER) }

  validates :from_type, presence: true, inclusion: { in: FromTypes::ALL }
  validates :to_type, presence: true, inclusion: { in: ToTypes::ALL }

  def from_user?
    from_type == FromTypes::USER
  end

  def from_phone_number?
    from_type == FromTypes::PHONE_NUMBER
  end

  def to_user?
    to_type == ToTypes::USER
  end

  def to_phone_number?
    to_type == ToTypes::PHONE_NUMBER
  end
end

  has_many(
    :messages_sent,
    -> { from_phone_number },
    class_name: "Message",
    foreign_key: :from_id
  )
  has_many(
    :messages_received,
    -> { to_phone_number },
    class_name: "Message",
    foreign_key: :to_id
  )

这使得SQL查询变得像这样:
>> PhoneNumber.first.messages_received
  PhoneNumber Load (1.1ms)  SELECT "phone_numbers".* FROM "phone_numbers" ORDER BY "phone_numbers"."id" ASC LIMIT $1  [["LIMIT", 1]]
  Message Load (2.1ms)  SELECT "messages".* FROM "messages" WHERE "messages"."to_id" = $1 AND "messages"."from_type" = $2 /* loading for pp */ LIMIT $3  [["to_id", 1], ["from_type", "PhoneNumber"], ["LIMIT", 11]]

-3
class Note < ActiveRecord::Base

 belongs_to :note_obj, :polymorphic => true
 belongs_to :user


end


class Contact < ActiveRecord::Base

 belongs_to :contact_obj, :polymorphic => true
 belongs_to :phone_type 

end



class CarrierHq < ActiveRecord::Base


 has_many :contacts, :as => :contact_obj
 has_many :notes, :as => :note_obj


end

你能否稍微解释一下你的答案,甚至修改一下以使用问题中的生产者/产品术语? - maerics
自行车类 < ActiveRecord::Base 属于 :bicycle_obj,:polymorphic => true end 冰棒类 < ActiveRecord::Base 属于 :popsicle_obj , :polymorphic => true end 生产者类 < ActiveRecord::Base 拥有多个 :bicycles , :as=>:bicycle_obj 拥有多个 :popsicle , :as=>:popsicle_obj end 如果您在使用对象时遇到任何问题,请使用此代码并在下面留下评论。 - user386660

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接