Ruby
Rails
ActiveRecord

ActiveRecord で "最新の子" を has_one で表現する

たとえば、複数のバージョンを持つブログの記事を下記のようなモデルで表現しているとする。

class Entry < ApplicationRecord
  has_many :versions

  def latest_version
    versions.order(number: :desc).first
  end
end

class Version < ApplicationRecord
  belongs_to :entry
  # 新しいバージョンほど number の値が大きいものとする
  validates :number, uniqueness: true, numericality: {greater_than: 0, only_integer: true}
end

このとき最新のバージョンは Entry.find(1).latest_version で得られるが、単なるメソッドなので includes などを使うことができない。

has_one では絞り込みの条件を lambda で渡す事ができるので下記のように書けばよい。

class Entry < ApplicationRecord
  has_many :versions
  has_one :latest_version, -> {
    # 同じ entry_id で number がこれより大きいレコードが存在しない、という条件
    where(
      <<~SQL
        NOT EXISTS (
          SELECT 1 FROM versions AS v
            WHERE
              versions.number < v.number AND
              versions.entry_id = v.entry_id
        )
      SQL
    )
  }
end

これで Entry.includes(:latest_version).find(1).latest_version のように書ける。