web-dev-qa-db-ja.com

Rails:多態的な関連付けを含む

多形性を使用して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クエリ問題が発生します。

ページネーションを使用できるようにするためにそれを解決する適切な方法は何ですか?

33
Nycen

これは、うまくいけば、Rails 5.0で修正される予定です。すでに問題とそれに対するプルリクエストがあります。

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

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

私はRailsをフォークして4.2-stableにパッチを適用し、それは私にとってはうまくいきました。定期的にアップストリームと同期することは保証できませんが、フォークを使ってください。

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

9
tosch

編集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>
16
Nycen

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
2
Hirurg103
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)
2
Hesham

これは有効なSQLクエリを生成しますか、それともeventsJOINtagsを使用できず、imagesJOINed 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 を使用します。

1
Dan Croak

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