Rails: включает в себя полиморфную ассоциацию
Я прочитал эту интересную статью о Использование полиморфизма для создания более эффективного фида активности в рельсах.
В итоге получилось что-то вроде
class Activity < ActiveRecord::Base
belongs_to :subject, polymorphic: true
end
Теперь, если два из этих предметов, например:
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.
Какой правильный способ решить эту проблему, чтобы иметь возможность использовать разбивку на страницы?
Ответы
Ответ 1
Это, надеюсь, будет исправлено в рельсах 5.0. Для этого уже существует проблема и запрос на перенос.
https://github.com/rails/rails/pull/17479
https://github.com/rails/rails/issues/8005
У меня есть разветвленные рельсы и применяется патч к 4.2-стабильным, и он работает для меня. Не стесняйтесь использовать свою вилку, хотя я не могу гарантировать синхронизацию с восходящим потоком на регулярной основе.
https://github.com/ttosch/rails/tree/4-2-stable
Ответ 2
Изменить 2: теперь я использую рельсы 4.2, а нетерпимый полиморфизм загрузки теперь является функцией:)
Изменить. Это похоже на работу в консоли, но почему-то мое предложение об использовании с частицами ниже все еще генерирует предупреждения N + 1 Query Stack с помощью маркера bullet. Мне нужно исследовать...
Хорошо, я нашел решение ([edit] или сделал?), но он предполагает, что вы знаете все типы тем.
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
И две (фиктивные) частицы
# _event.html.erb
<p><%= event.guests.map(&:name).join(', ') %></p>
# _image.html.erb
<p><%= image.tags.first.map(&:name).join(', ') %></p>
Ответ 3
После того, как я прочитал Политичные отношения с нетерпением загрузки в 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
Ответ 4
Я бы предложил добавить полиморфную ассоциацию к вашим моделям 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)
Ответ 5
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)
Ответ 6
Сгенерирует ли это правильный SQL-запрос или не работает, потому что events
не может быть JOIN
ed с tags
и images
не может быть JOIN
ed с 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.