woshidan's loose leaf

ぼんやり勉強しています

ActiveRecordで関連しているモデルに対してメソッドを定義する

Userと1対多関連の関係にあるCommentというモデルがあるとする。

あるUserに関連したコメントに対して user.commentes.some_method のようにスコープやメソッドを定義したいという場合があって、その場合以下のように書ける。

class User < ApplicationRecord
  has_many :comments do # has_many関連を定義した部分にブロックを渡すとuser.comments.shortest_commentのように書ける
    def shortest_comment
      self.order(Arel.sql('length(contents) asc')).first
    end
  end
end
class Comment < ApplicationRecord
  belongs_to :user

  def self.longest_comment # クラスメソッドでallを使うと呼び出された時点でのhas_many関連などの上にメソッドチェインをつなげることができる
    all.order(Arel.sql('length(contents) desc')).first
  end
end

確認用のテストコード

RSpec.describe Comment, type: :model do
  let(:user) { create(:user) }
  let!(:comment1) { create(:comment, contents: '' * 3, user: user)}
  let!(:comment2) { create(:comment, contents: '' * 5, user: user)}
  let!(:another_user_comment) { create(:comment, contents: '' * 7, user: create(:user)) }
  let!(:other_user_comment) { create(:comment, contents: '' * 1, user: create(:user)) }

  describe '.longest_comment' do
    it 'userのコメントの中で一番長いコメントが返ってくる' do
      expect(user.comments.longest_comment.contents).to eq comment2.contents
    end
  end

  describe '.shortest_comment' do
    it 'userのコメントの中で一番短いコメントが返ってくる' do
      expect(user.comments.shortest_comment.contents).to eq comment1.contents
    end
  end
end

参考

qiita.com