多形性を使用してRailsでより良いアクティビティフィードを作成する に関するこの興味深い記事を読みました。
最終的には
class Activity < ActiveRecord::Base
belongs_to :subject, polymorphic: true
end
さて、これらのサブジェクトのうちの2つがたとえば次の場合:
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
私はRailsをフォークして4.2-stableにパッチを適用し、それは私にとってはうまくいきました。定期的にアップストリームと同期することは保証できませんが、フォークを使ってください。
編集2:現在Rails 4.2を使用しており、熱心な読み込みポリモーフィズムが機能になっています:)
編集:これはコンソールで機能するように見えましたが、何らかの理由で、以下のパーシャルでの使用を提案すると、弾丸の宝石で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
そして2つの(ダミー)パーシャル
# _event.html.erb
<p><%= event.guests.map(&:name).join(', ') %></p>
# _image.html.erb
<p><%= image.tags.first.map(&:name).join(', ') %></p>
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
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
はJOIN
とtags
を使用できず、images
はJOIN
ed with 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 を使用します。
Event
モデルとGuest
モデルに多態的な関連付けを追加することをお勧めします。 ポリモーフィックドキュメント
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)