简体   繁体   English

Rails:包含多态关联

[英]Rails: includes with polymorphic association

I read this interesting article about Using Polymorphism to Make a Better Activity Feed in Rails . 我读了这篇有趣的文章,关于使用多态来在Rails中创建更好的活动源

We end up with something like 我们最终得到类似的东西

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

Now, if two of those subjects are for example: 现在,如果其中两个主题是例如:

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

With create_activities defined as 将create_activities定义为

def create_activities
  Activity.create(subject: self)
end

And with guests and tags defined as: 客人和标签定义为:

class Guest < ActiveRecord::Base
  belongs_to :event
end

class Tag < ActiveRecord::Base
  belongs_to :image
end

If we query the last 20 activities logged, we can do: 如果我们查询记录的最后20个活动,我们可以这样做:

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

We have a first N+1 query issue that we can solve with: 我们有第一个N + 1查询问题,我们可以通过以下方式解决:

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

But then, when we call guests or tags, we have another N+1 query problem. 但是,当我们呼叫访客或标签时,我们会遇到另一个N + 1查询问题。

What's the proper way to solve that in order to be able to use pagination ? 为了能够使用分页,解决这个问题的正确方法是什么?

Edit 2 : I'm now using rails 4.2 and eager loading polymorphism is now a feature :) 编辑2 :我现在使用rails 4.2和eager loading polymorphism现在是一个功能:)

Edit : This seemed to work in the console, but for some reason, my suggestion of use with the partials below still generates N+1 Query Stack warnings with the bullet gem. 编辑 :这似乎在控制台中工作,但出于某种原因,我建议使用下面的部分仍然生成子弹宝石的N + 1查询堆栈警告。 I need to investigate... 我需要调查......

Ok, I found the solution ([edit] or did I ?), but it assumes that you know all subjects types. 好的,我找到了解决方案([编辑]或我做了吗?),但它假设你知道所有科目类型。

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

And now you can do 现在你可以做到

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

But for eager loading to work, you must use for example 但是为了急切加载工作,你必须使用例如

activity.event.guests.first

and not 并不是

activity.part.guests.first

So you can probably define a method to use instead of subject 所以你可以定义一个方法来代替主题

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

So now you can have a view with 所以现在你可以有一个视图

render partial: :subject, collection: activity

A partial with 偏爱的

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

And two (dummy) partials 还有两个(虚拟)部分

# _event.html.erb
<p><%= event.guests.map(&:name).join(', ') %></p>

# _image.html.erb
<p><%= image.tags.first.map(&:name).join(', ') %></p>

This will hopefully be fixed in rails 5.0. 这有望在rails 5.0中得到修复。 There is already an issue and a pull request for it. 已经存在问题和拉取请求。

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

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

I have forked rails and applied the patch to 4.2-stable and it works for me. 我有分叉导轨并将补丁应用到4.2稳定,它适用于我。 Feel free to use my fork, even though I cannot guarantee to sync with upstream on a regular basis. 尽管我不能保证定期与上游同步,但请随意使用我的前叉。

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

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)

After I read Eager-loading Polymorphic Relationships in Rails article I ended up with the following solution: 在我阅读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

I would suggest adding the polymorphic association to your Event and Guest models. 我建议将多态关联添加到您的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

and then try doing 然后尝试做

Activity.includes(:subject => [:event, :guest]).order(created_at: :desc).limit(20)

Does this generate a valid SQL query or does it fail because events can't be JOIN ed with tags and images can't be JOIN ed with guests ? 这是生成有效的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])

This would use will_paginate . 这将使用will_paginate

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM