single rails polymorphic multiple ruby-on-rails performance activerecord polymorphic-associations

ruby-on-rails - multiple - rails polymorphic



Rieles: incluye con asociación polimórfica (6)

Leí este interesante artículo sobre el uso del polimorfismo para mejorar la actividad en Rails .

Terminamos con algo así como

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

Ahora, si dos de esos temas son, por ejemplo:

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

Con create_activities definido como

def create_activities Activity.create(subject: self) end

Y con invitados y etiquetas definidas como:

class Guest < ActiveRecord::Base belongs_to :event end class Tag < ActiveRecord::Base belongs_to :image end

Si consultamos las últimas 20 actividades registradas, podemos hacer:

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

Tenemos un primer problema de consulta N + 1 que podemos resolver con:

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

Pero luego, cuando llamamos invitados o etiquetas, tenemos otro problema de consulta N + 1.

¿Cuál es la forma correcta de resolver eso para poder usar la paginación?


¿Esto genera una consulta SQL válida o falla porque los events no se pueden JOIN con las tags y las images no se pueden JOIN con los 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])

Esto usaría will_paginate .


Después de leer el artículo de Relación polimórfica de carga impaciente en Rails , terminé con la siguiente solución:

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



Sugeriría agregar la asociación polimórfica a sus modelos de Event e Guest . documento polimórfico

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

y luego intenta hacer

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


Edición 2 : ahora estoy utilizando rails 4.2 y el polimorfismo de carga ansioso ahora es una característica :)

Editar : Esto pareció funcionar en la consola, pero por alguna razón, mi sugerencia de uso con los parciales a continuación todavía genera advertencias de N + 1 Stack Stack con la gema de la bala. Necesito investigar ...

Ok, encontré la solución ([edit] ¿o no?), Pero supone que conoces todos los tipos de temas.

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

Y ahora puedes hacer

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

Pero para una carga de trabajo ansiosa, debe usar, por ejemplo,

activity.event.guests.first

y no

activity.part.guests.first

Entonces, probablemente puedas definir un método para usar en lugar del tema

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

Entonces ahora puedes tener una vista con

render partial: :subject, collection: activity

Un parcial con

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

Y dos parciales (ficticios)

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


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)