railsの
今日元食べログの大エンジニアとお話していて、scopeっていけてないねっていう話になった。
scopeとは以下のようなもので
class User < AR::Base has_many :blogs has_many :fav_stars, through: :blogs scope :recent, -> { order('created_at DESC') } scope :has_blog, -> { joins(:blogs).group('users.id') } end class Blog < AR::Base has_many :fav_stars scope :favorites, -> { joins(:fav_stars).group('blogs.id').having("count(fav_stars.id) > 10") }
とかしておくと最近のユーザで人気のブログ書いてる人が
User.recent.joins(blogs: :fav_stars).merge(Blog.favorites)
とかでとれるようになる(はず)。
でありがちなのは上のメソッドチェーンを
scope :recent_favorite_users -> { recent.joins(blogs: :fav_stars).merge(Blog.favorites) }
とかでとれるようにすることだけど、これが良くないらしくてそもそもSQLのパーツであるscopeにrecent_favorite_usersのようなビジネスロジックを組み込むのは良くない、ということらしい。
あまり深く考えたことなかったけどこのscopeって確かにいろいろ欠陥があって、ActiveRecord::Relation#mergeとかいろいろあるくせになんだかんだいって複数のmodelのscopeを組み立てていくみたいなことすると途端に破綻する。
例えば
name_is_hoge -> { where(name: 'hoge') }っていうscopeならいいんだけど、having_name -> { where('name IS NOT NULL') }っていうscopeだとダメ、みたいな。
これはどういうことかというとUser.joins(blogs: :fav_stars).having_nameとかするとjoinしてるblogとかfav_starにnameっていうカラムがあるとambiguous column nameっていうエラーが出ちゃう。
where(name: 'hoge')は厳密にはwhere("name = 'hoge'")とは等価ではなくwhere("users.name = 'hoge'")と等価ということですね。
で、まぁここらへんの運用をきっちりしましょうというのもあるんだけど、そもそもいろんなmodelに渡るビジネスロジックってけっこう重要じゃないですか? というわけでmergeだの複雑なscopeチェーンがある場合は全部クラスメソッドにすべきじゃないですか? というご意見でした。
メモまでに。