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)
Другие вопросы по тегам