Hisakeyのブログ

気になったことについて、調べながら投稿するブログです。

ActiveRecord::RelationとActiveRecord::Associations::CollectionProxyってなに?

概要

実際の業務でrspecを書いてるときに発生し、理解に少し時間がかかったので記事にしました。

テストは同じ結果をみているはずなのにと思っていましたが、ActiveRecordのオブジェクトの違いで落ちていました。

内容

実務とは異なりますが、下記のようなアソシエーションに新しくメソッドを定義してテストを書いていました。

class User < ApplicationRecord
  has_many :posts

# 新しく定義してメソッド
  def published_posts
    posts.where(published: true)
  end
end

class Post < ApplicationRecord
  belongs_to :user
end

テストは、以下のようにかきました。

RSpec.describe User, type: :model do
  describe '#published_posts' do
    let(:user) { User.create(name: 'John Doe') }
    let!(:post1) { Post.create(title: 'Post 1', user: user, published: true) }
    let!(:post2) { Post.create(title: 'Post 2', user: user, published: true) }
    let!(:unpublished_post) { Post.create(title: 'Unpublished Post', user: user, published: false) }

    it 'returns only published posts' do
      expect(user.published_posts).to eq(Post.where(user: user, published: true))
    end
  end
end

パッと見るとテストは通るように見えますが… 落ちます!

なぜか?

それは、期待値と結果のオブジェクト(クラス)が異なるからです。

# ActiveRecord::Associations::CollectionProxy
user.published_posts

# ActiveRecord::Relation
Post.where(user: user, published: true)

ActiveRecord::Associations::CollectionProxy

これは、has_manyなどのアソシエーションによって返されるオブジェクトです。なので、親に関連付けられた子のオブジェクトを返します。

user.posts

user.posts.where(title: 'hogehoge')

ActiveRecord::Relation

これは、ActiveRecordでデータベースクエリを表すオブジェクトです。SQLを構築します。

Post.where(user: user, title: 'hogehoge')

上記のオブジェクトの違いが、テストを落とすのです。

回避策としては、下記が挙げられそうです。

  1. オブジェクトを揃える(今回は、eqの部分をActiveRecord::Associations::CollectionProxyにする)
  2. to_aで配列に揃える

私は、1つ目のほうが厳密にテストすることになるので良いのかなと思いました。

まとめ

最初は、なぜテストが落ちているのかわかりませんでしたが、失敗した箇所をよく確認することで見つけることができました。

Activerecordについて少し理解を深めることができたのでよかったです!

最後までお付き合いいただき、ありがとうございました!