Szyny: Zawiera ze Związkiem polimorficznym

Przeczytałem ciekawy artykuł o wykorzystywaniu polimorfizmu do lepszego podawania aktywności w Railach .

Kończymy z czymś takim

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

Teraz, jeśli dwa z tych tematów to na przykład:

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

Z create_activities zdefiniowanymi jako

def create_activities
  Activity.create(subject: self)
end

Oraz z Gośćmi i tagami zdefiniowanymi jako:

class Guest < ActiveRecord::Base
  belongs_to :event
end

class Tag < ActiveRecord::Base
  belongs_to :image
end

Jeśli zapytamy ostatnie 20 zarejestrowanych aktywności, możemy wykonać:

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

Mamy pierwszy problem z zapytaniem N + 1, który możemy rozwiązać za pomocą:

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

Ale następnie, gdy wywołujemy gości lub tagi, mamy kolejny problem z zapytaniem N + 1.

Jaki jest właściwy sposób, aby to rozwiązać, aby móc korzystać z paginacji ?

Author: Ilya Lavrov, 2014-02-25

6 answers

Miejmy nadzieję, że zostanie to naprawione w rails 5.0. Jest już problem i żądanie pull dla niego.

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

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

Mam rozwidlone rails i zastosowałem patch do 4.2-stable i u mnie działa. Nie krępuj się używać mojego forka, mimo że nie mogę zagwarantować regularnej synchronizacji z upstream.

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

 7
Author: tosch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-20 13:42:53

Edit 2 : korzystam teraz z rails 4.2 i niecierpliwy polimorfizm ładowania jest teraz funkcją :)

Edit : wydawało się, że działa to w konsoli, ale z jakiegoś powodu moja sugestia użycia z poniższymi częściami nadal generuje N + 1 ostrzeżeń stosu zapytań z klejnotem bullet. Muszę to zbadać...

Ok, znalazłem rozwiązanie ([edytuj] czy udało mi się ?), ale zakłada, że znasz wszystkie typy przedmiotów.

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)

Ale dla chętnych ładowanie do pracy, musisz użyć na przykład

activity.event.guests.first

I nie

activity.part.guests.first

Więc prawdopodobnie możesz zdefiniować metodę, której chcesz użyć zamiast tematu

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

Więc teraz możesz mieć widok z

render partial: :subject, collection: activity

Częściowy z

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

I dwa (atrapy) częściowe

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

# _image.html.erb
<p><%= image.tags.first.map(&:name).join(', ') %></p>
 15
Author: Nycen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-11 14:47:08

Po przeczytaniu Eager-loading polimorficznych relacji w Rails artykuł skończyłem z następującym rozwiązaniem:

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
Author: Hirurg103,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-28 13:37:05

Sugerowałbym dodanie asocjacji polimorficznej do Modeli Event i Guest. polimorficzny 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

A potem spróbuj zrobić

Activity.includes(:subject => [:event, :guest]).order(created_at: :desc).limit(20)
 1
Author: xlembouras,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-25 11:58:04
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
Author: Hesham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-27 13:07:54

Czy to generuje poprawne zapytanie SQL, czy nie, ponieważ events nie może być JOIN ed z tags i images nie może być JOIN ed z 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])

To użyje will_paginate.

 1
Author: Dan Croak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-28 19:10:25