rails polymorphic with includes基于类的类型

时间:2013-02-26 18:08:44

标签: ruby-on-rails ruby-on-rails-3 activerecord polymorphic-associations eager-loading

假设我们有这些模型

class Message
  belongs_to :messageable, polymorphic: true
end

class Ticket
  has_many :messages, as: :messageable
  has_many :comments
end

class User
  has_many :messages, as: :messageable
  has_many :ratings
end

class Rating
  belongs_to :user
end

class Comment
  belongs_to :ticket
end

现在我要加载所有邮件(已关联ticketsusers),并根据类的类型加载急切加载,commentsticketsratings

users

当然Message.includes(:messageable).order("created_at desc")只包含直接关联的对象,但问题是如何包含从每种模型类型派生的不同关联类型(即在此示例中,如何预先加载{{1} }和comments for tickets)?

这只是一个简单的例子,但是更复杂的情况呢,我想为ratings for users包含其他内容,另一个关联,以及如果该关联需要更多包括什么?

3 个答案:

答案 0 :(得分:2)

我能想到的唯一方法是使用通用名称复制每个模型上的关联:

class Ticket
  has_many :messages, as: :messageable
  has_many :comments
  has_many :messageable_includes, class_name: "Comment"
end

class User
  has_many :messages, as: :messageable
  has_many :ratings
  has_many :messageable_includes, class_name: "Rating"
end

Message.includes(:messageable => :messageable_includes) ...

我不确定我会将此策略用作广泛使用的解决方案,但如果您的案例变得复杂,那么它可能对您有用。

答案 1 :(得分:0)

我在自己的项目中使用了以下帮助方法:

def polymorphic_association_includes(association, includes_association_name, includes_by_type)
  includes_by_type.each_pair do |includes_association_type, includes|
    polymorphic_association_includes_for_type(association, includes_association_name, includes_association_type, includes)
  end
end

def polymorphic_association_includes_for_type(association, includes_association_name, includes_association_type, includes)
  id_attr = "#{includes_association_name}_id"
  type_attr = "#{includes_association_name}_type"

  items = association.select {|item| item[type_attr] == includes_association_type.to_s }
  item_ids = items.map {|item| item[id_attr] }
  items_with_includes = includes_association_type.where(id: item_ids).includes(includes).index_by(&:id)

  items.each do |parent|
    parent.send("#{includes_association_name}=", items_with_includes[parent[id_attr]])
  end
end

这些可以让你说:

messages = Message.all
polymorhpic_association_includes messages, :messageable, {
  Ticket => :comments,
  User => :ratings
}

不是特别流畅的界面,但它一般都有用。

答案 2 :(得分:0)

将包含放在每个模型的默认范围内:

class Ticket
  has_many :messages, as: :messageable
  has_many :comments
  default_scope -> { includes(:comments).order('id DESC') }
end

class User
  has_many :messages, as: :messageable
  has_many :ratings
  default_scope -> { includes(:ratings).order('id DESC') }
end

然后,无论何时调用Message.all,每个多态关联都将包含它自己的资源。

此外,如果您需要在没有范围的情况下调用该类,只需使用unscoped或创建不同的范围:

class Ticket
  has_many :messages, as: :messageable
  has_many :comments
  has_many :watchers
  default_scope -> { includes(:comments).order('id DESC') }
  scope :watched -> {includes(:watchers)}
end

Ticket.unscoped.all # without comments or watchers (or order)
Ticket.watched.all  # includes watchers only