scopeの条件はjoinsで絞ろう
Railsのscopeは強力な機能で、ActiveRecordにできることをかなりこなせますが、それが故にトラブルとなることもあります。
TL; DR
- 絞込用のscope内でのテーブル結合は
.joins
で行う
設例
.joins
で行う今回の例を説明するサンプルとして、こんなアプリケーションを考えてみます(何がモデルかは気にしないでください)。
- ユーザーが投稿(
Post
)をしていく。 - 投稿には、複数のコメント(
Comment
)を付けられる。 - コメントには、各ユーザーが感謝(
Thank
)を表明できる。
素直にRailsのモデルに起こすと、以下のようになります。
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :user
has_many :thanks
end
class Thank < ActiveRecord::Base
belongs_to :comment
belongs_to :user
end
scope
とは
詳しい記事があるので詳細は省略しますが、要は「モデルに続けられるメソッドチェーンを束ねたもの」です。これ自体もActiveRecord::Relationを返すようにしておけば、メソッドチェーンの一部として使えるようになります。
ここでは、「ある人が感謝をつけたコメント」というスコープを立ててみます。
scope :thanked_by, ->(id) { eager_load(:thanks).where(thanks: { user_id: id }) }
このようにしておけば、Comment.thanked_by(2)
のように使えます。
scope
のmerge
…失敗
Railsのscope
にはさらに強力な機能がありまして、merge
というものがあります。これは、「別なテーブルとつないだときにも、検索に同じscope
を使える」というものです(関連記事)。ということで、さっき作ったthanked_by
を使って、「感謝したコメントのある投稿を検索する」というスコープを作ってみることにします。
scope :with_thanked_comments, ->(id) { eager_load(:comments).merge(Comment.thanked_by(id)) }
一見動きそうに見えますが、このままPost.with_thanked_comments
を使おうとすると、ActiveRecord::ConfigurationError
になってしまいました。
失敗の原因と対策
ここの記事に詳しいのですが、Comment.thanked_by
の中で使っていた.eager_load
には、「SQLでテーブルをJOINする」ことと「eager_load
先をActiveRecordオブジェクトに起こす」という2つの役割があります。そして、mergeした後にも後者の機能が残ってしまって、Post
上でthanksのリレーションを使おうとして、エラーとなっていたのですた。
ということで、.eager_load
を.joins
に置き換えてJOINだけさせるようにすれば、問題なく動くようになりました。なお、.joins
でJOINした先に対して.eager_load
や.includes
を使った場合も、テーブルをもう一度読んでしまうことはなく、最初にJOINした分を有効活用してくれます。
根本的に言えば、絞込用のscopeでActiveRecordを生成する必要は本来ないわけで、そのあたりの役割分担、というのも考えておいたほうがいいかもしれません。
Author And Source
この問題について(scopeの条件はjoinsで絞ろう), 我々は、より多くの情報をここで見つけました https://qiita.com/jkr_2255/items/6a49f39ce1cdbe30f0f4著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .