Rails:包含多态关联

我读了这篇有趣的文章,关于使用多态来在Rails中创建更好的活动源 。

我们最终得到了类似的东西

class Activity < ActiveRecord::Base belongs_to :subject, polymorphic: true end 

现在,如果其中两个主题是例如:

 class Event < ActiveRecord::Base has_many :guests after_create :create_activities has_one :activity, as: :subject, dependent: :destroy end class Image < ActiveRecord::Base has_many :tags after_create :create_activities has_one :activity, as: :subject, dependent: :destroy end 

将create_activities定义为

 def create_activities Activity.create(subject: self) end 

客人和标签定义为:

 class Guest < ActiveRecord::Base belongs_to :event end class Tag < ActiveRecord::Base belongs_to :image end 

如果我们查询记录的最后20个活动,我们可以这样做:

 Activity.order(created_at: :desc).limit(20) 

我们有第一个N + 1查询问题,我们可以通过以下方式解决:

 Activity.includes(:subject).order(created_at: :desc).limit(20) 

但是,当我们呼叫客人或标签时,我们会遇到另一个N + 1查询问题。

为了能够使用分页,解决这个问题的正确方法是什么?

这有望在rails 5.0中得到修复。 已经存在问题和拉取请求。

https://github.com/rails/rails/pull/17479

https://github.com/rails/rails/issues/8005

我有分叉导轨并将补丁应用到4.2稳定,它适用于我。 尽管我不能保证定期与上游同步,但请随意使用我的前叉。

https://github.com/ttosch/rails/tree/4-2-stable

编辑2 :我现在使用rails 4.2和eager loading polymorphism现在是一个function:)

编辑 :这似乎在控制台中工作,但出于某种原因,我建议使用下面的部分仍然生成子弹gem的N + 1查询堆栈警告。 我需要调查……

好的,我找到了解决方案([编辑]或我做了吗?),但它假设你知道所有科目类型。

 class Activity < ActiveRecord::Base belongs_to :subject, polymorphic: true belongs_to :event, -> { includes(:activities).where(activities: { subject_type: 'Event' }) }, foreign_key: :subject_id belongs_to :image, -> { includes(:activities).where(activities: { subject_type: 'Image' }) }, foreign_key: :subject_id end 

现在你可以做到

 Activity.includes(:part, event: :guests, image: :tags).order(created_at: :desc).limit(10) 

但是,为了急切地加载工作,你必须使用例如

 activity.event.guests.first 

并不是

 activity.part.guests.first 

所以你可以定义一个方法来代替主题

 def eager_loaded_subject public_send(subject.class.to_s.underscore) end 

所以现在你可以有一个视图

 render partial: :subject, collection: activity 

偏爱的

 # _activity.html.erb render :partial => 'activities/' + activity.subject_type.underscore, object: activity.eager_loaded_subject 

还有两个(虚拟)部分

 # _event.html.erb 

<%= event.guests.map(&:name).join(', ') %>

# _image.html.erb

<%= image.tags.first.map(&:name).join(', ') %>

在我阅读Rails文章中的Eager-loaded Polymorphic Relationships后,我最终得到了以下解决方案:

 class ActivitiesController < ApplicationController def index activities = current_user.activities.page(:page) @activities = Activities::PreloadForIndex.new(activities).run end end class Activities::PreloadForIndex def initialize(activities) @activities = activities end def run preload_for event(activities), subject: :guests preload_for image(activities), subject: :tags activities end private def preload_for(activities, associations) ActiveRecord::Associations::Preloader.new.preload(activities, associations) end def event(activities) activities.select &:event? end def image(activities) activities.select &:image? end end 

我建议将多态关联添加到您的EventGuest模型中。 多态文档

 class Event < ActiveRecord::Base has_many :guests has_many :subjects after_create :create_activities end class Image < ActiveRecord::Base has_many :tags has_many :subjects after_create :create_activities end 

然后尝试做

 Activity.includes(:subject => [:event, :guest]).order(created_at: :desc).limit(20) 
 image_activities = Activity.where(:subject_type => 'Image').includes(:subject => :tags).order(created_at: :desc).limit(20) event_activities = Activity.where(:subject_type => 'Event').includes(:subject => :guests).order(created_at: :desc).limit(20) activities = (image_activities + event_activities).sort_by(&:created_at).reverse.first(20) 

这是生成有效的SQL查询还是失败,因为events无法与tags一起JOIN ,并且images无法与guests一起JOIN

 class Activity < ActiveRecord::Base self.per_page = 10 def self.feed includes(subject: [:guests, :tags]).order(created_at: :desc) end end # in the controller Activity.feed.paginate(page: params[:page]) 

这将使用will_paginate 。