Rails Association has_one Последняя запись
У меня есть следующая модель:
class Section < ActiveRecord::Base
belongs_to :page
has_many :revisions, :class_name => 'SectionRevision', :foreign_key => 'section_id'
has_many :references
has_many :revisions, :class_name => 'SectionRevision',
:foreign_key => 'section_id'
delegate :position, to: :current_revision
def current_revision
self.revisions.order('created_at DESC').first
end
end
куда current_revision
это самый последний созданный revision
, Можно ли повернуть current_revision
в ассоциацию, чтобы я мог выполнить запрос, как Section.where("current_revision.parent_section_id = '1'")
? Или я должен добавить current_revision
столбец в моей базе данных вместо того, чтобы пытаться создать его виртуально или через ассоциации?
5 ответов
Как я понимаю, вы хотите получить разделы, где последняя ревизия каждого раздела имеет parent_section_id = 1;
У меня похожая ситуация, во-первых, это SQL (пожалуйста, подумайте, категории для вас как разделы, посты как ревизии и user_id как parent_section_id -sorry, если я не перемещаю код по вашей необходимости, но мне нужно идти):
SELECT categories.*, MAX(posts.id) as M
FROM `categories`
INNER JOIN `posts`
ON `posts`.`category_id` = `categories`.`id`
WHERE `posts`.`user_id` = 1
GROUP BY posts.user_id
having M = (select id from posts where category_id=categories.id order by id desc limit 1)
И это запрос в Rails:
Category.select("categories.*, MAX(posts.id) as M").joins(:posts).where(:posts => {:user_id => 1}).group("posts.user_id").having("M = (select id from posts where category_id=categories.id order by id desc limit 1)")
Это работает, это уродливо, я думаю, что лучший способ - это "обрезать" запрос, но если у вас слишком много разделов, это может стать проблемой при их циклическом цикле; Вы также можете поместить этот запрос в статический метод, а также, по вашей первой идее, иметь revision_id внутри таблицы разделов, что поможет оптимизировать запрос, но упадет нормализация (иногда это необходимо), и вам придется Обновление этого поля при создании новой ревизии для этого раздела (поэтому, если вы собираетесь делать много ревизий в огромной базе данных, возможно, будет плохой идеей, если у вас медленный сервер...)
ОБНОВЛЕНИЕ Я вернулся, хе-хе, я делал несколько тестов, и проверить это:
def last_revision
revisions.last
end
def self.last_sections_for(parent_section_id)
ids = Section.includes(:revisions).collect{ |c| c.last_revision.id rescue nil }.delete_if {|x| x == nil}
Section.select("sections.*, MAX(revisions.id) as M")
.joins(:revisions)
.where(:revisions => {:parent_section_id => parent_section_id})
.group("revisions.parent_section_id")
.having("M IN (?)", ids)
end
Я сделал этот запрос и работал с моими таблицами (надеюсь, я хорошо назвал параметры, это тот же самый запрос Rails, что и раньше, но я изменяю запрос в поиске оптимизации); следить за группой; Включение делает его оптимальным для больших наборов данных, и извините, я не смог найти способ установить связь с has_one, но я хотел бы пойти на это, но также пересмотреть область, которую вы упоминали в начале.
Чтобы получить последнее для has_many, вам нужно сделать что-то похожее на @jvnill, за исключением добавления области с упорядочением в ассоциации:
has_one :current_revision, -> { order created_at: :desc },
class_name: 'SectionRevision', foreign_key: :section_id
Это гарантирует, что вы получите самую последнюю версию из базы данных.
Вы можете изменить его на ассоциацию, но обычно, заказывая has_one
или же belongs_to
ассоциации всегда неправильно интерпретируются при использовании в запросах. На ваш вопрос, когда вы превращаете это в ассоциацию, это было бы
has_one :current_revision, class_name: 'SectionRevision', foreign_key: :section_id, order: 'created_at DESC'
Проблема в том, что когда вы пытаетесь объединить это с другими запросами, это обычно дает вам неправильную запись.
>> record.current_revision
# gives you the last revision
>> record.joins(:current_revision).where(section_revisions: { id: 1 })
# searches for the revision where the id is 1 ordered by created_at DESC
Поэтому я предлагаю вам добавить current_revision_id
вместо.
Как упоминает @jvnill, решения, использующие
order
перестать работать при выполнении больших запросов, потому что
order
область действия - это полный запрос, а не только ассоциация.
Решение здесь требует точного SQL:
has_one :current_revision, -> { where("NOT EXISTS (select 1 from section_revisions sr where sr.id > section_revisions.id and sr.section_id = section_revisions.section_id LIMIT 1)") }, class_name: 'SectionRevision', foreign_key: :section_id
Если ваша база данных поддерживает
DISTINCT ON
class Section < ApplicationRecord
has_one :current_revision, -> { merge(.latest_by_section) }, class_name: "SectionRevision", inverse_of: :section
end
class SectionRevision < ApplicationRecord
belongs_to: :section
scope :latest_by_section, -> do
query = arel_table
.project(Arel.star)
.distinct_on(arel_table[:section_id])
.order(arel_table[:section_id].asc, arel_table[:created_at].desc)
revisions = Arel::Nodes::TableAlias.new(
Arel.sql(format("(%s)", query.to_sql)), arel_table.name
)
from(revisions)
end
end
Работает с предзагрузкой
Section.includes(:current_revision)