26

我读了这篇关于Using Polymorphism to Make a Better Activity Feed in 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查询问题。

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

+0

什么'guests'和'tags'?请提供相关的代码 – xlembouras

+0

@xlembouras完成,但我敢肯定你已经猜到了他们是什么 – Nycen

+1

是我看不到的是事件,图像,客人和标记是如何连接到'活动' – xlembouras

回答

1

我建议添加多态性关联到您的EventGuest模型。 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) 
+0

我再次编辑了问题。事件和图像只有一个活动,创建时创建。你写的东西不会奏效,你打电话给事件。甚至(subject::guests)都不起作用(相反,它会打破图像)。 – Nycen

1
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) 
1

这是否产生有效的SQL查询或它失败,因为events不能JOIN ED与tagsimages不能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]) 

这将使用will_paginate

+0

这也是我的猜测,但它不起作用。如果第一个结果是一个事件,你会得到一个错误“ActiveRecord :: ConfigurationError:在事件” – Nycen

15

编辑2:现在,我使用轨道4.2和预先加载多态性现在是一个功能:)

编辑:这似乎在控制台工作,但由于某种原因,我的建议使用下面的partials仍然会产生N + 1 Query Stack警告和子弹宝石。我需要调查...

好吧,我找到了解决方案([编辑]或我?),但它假定您知道所有主题类型。

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 

这样你就可以可能定义一个方法来代替sub JECT

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> 
+0

上找不到名为'tags'的关联我喜欢它!看起来不错。 –

+0

太棒了!这次真是万分感谢。 – jeffdill2

+0

仅强调一点 - 您需要使用自定义的belongs_to范围(此处为activity.event ...)访问嵌套的资源。如果你感到困惑,为什么Rails会预加载你的关联,但仍然喜欢N + 1,这肯定是为什么。 – niborg

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