我读了这篇关于Using Polymorphism to Make a Better Activity Feed in 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查询问题。
为了能够使用分页,解决这个问题的正确方法是什么?
答案 0 :(得分:16)
编辑2 :我现在正在使用rails 4.2和eager loading polymorphism现在是一个功能:)
编辑:这似乎在控制台中有效,但出于某种原因,我对下面部分使用的建议仍会生成带有bullet 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
<p><%= event.guests.map(&:name).join(', ') %></p>
# _image.html.erb
<p><%= image.tags.first.map(&:name).join(', ') %></p>
答案 1 :(得分:9)
希望在rails 5.0中修复此问题。已存在问题和拉取请求。
https://github.com/rails/rails/pull/17479
https://github.com/rails/rails/issues/8005
我有分叉导轨并将补丁应用到4.2稳定,它适用于我。尽管我不能保证定期与上游同步,但请随意使用我的分叉。
答案 2 :(得分:2)
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)
答案 3 :(得分:2)
在我阅读Eager-loading Polymorphic Relationships in Rails 文章后,我最终得到了以下解决方案:
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
答案 4 :(得分:1)
我建议将多态关联添加到Event
和Guest
模型中。
polymorphic doc
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)
答案 5 :(得分:1)
这会生成有效的SQL查询还是失败,因为events
JOIN
tags
images
JOIN
不能guests
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])
{{1}}与{{1}}编辑?
{{1}}
这将使用will_paginate。