はじめに
RailsのActiveRecordでよく使ってしまうテクニックをまとめてみた。
大体、試行錯誤の果てに生まれたり見つけたりしたものなので、パフォーマンスや正しさなどはわからない
全体的にもっとスマートな書き方があったら教えてほしいです。
LEFT JOINに条件を加える
INNER JOINであれば、whereを書けばとりあえず条件は書けるが、LEFT JOINだとそうもいかない
リレーションに条件を加えたり、Arelを使う方法もあるが、どっちも嫌なので
User.joins("LEFT JOIN messages ON(messages.user_id = user.id and messages.opend = 1)")
# eager_loadingしたいときは
User.eager_load(:messages).joins('AND messages.opend = 1')
みたいな感じで書いている。
Rail5からleft_joins
というそのまんまなメソッドが追加されたが、これだとうまくいかなかった。なぞ。
参考: railsでのeager_loadの結合条件の追加はできますか?
NULLや空文字の入ったカラムで並び替えする
NULLが入ったカラムを昇順に並び替えすると、NULLの入ったレコードが先頭に来るが、そうなって欲しくないとき
User.order('users.image IS NULL ASC')
# もしくは
User.order("users.image = '' ")
と書くとNULLや空文字のレコードが下の方に並び替えられる
参考: MySQLのソートで空白の行を下げようとしたらハマった話
countした値で並び替えたい
記事(article) has many コメント(comments)みたいなリレーションで、記事についたコメントの件数で並び替えたいとかよくある
scope :popular, -> {
article_ids = Article.
joins(:comments).
group('comments.article_id').
order('count_commnets_article_id desc').
count('comments.article_id').
keys
where(id: article_ids).order(['FIELD(articles.id, ?)', article_ids])
}
countを使うと、prefixにcount_がつくようなのでそれでorder
{ article_id => count_comments_article_id }
のハッシュが返されるので、keys
でarticle_idだけ取り出して、FIELD関数で並び替え
これももっとうまい方法がある気がするが慣れるとササッとかけるし、応用が効くのでつい頼ってしまうやつ
スコープをモジュール化する
module readable
def self.included(base)
base.class_eval do
scope :already_read, -> {
where(read: true)
}
end
end
end
上のコードはmoduleがincludeされたときに、class_evalメソッドでブロック内をクラスメソッドのように扱ってるらしい
scopeって要はクラスメソッドの糖衣構文だったので確かそう
特異クラスとかそのへんはちょっと勉強中
ランダムなレコードを1件取得する
MySQLであればorder("RAND()")
とかで済ませてしまうのだけれど、DB移行なんて滅多にないものの、わざわざ依存を作ってしまうのは良くないと思い始めた。
Blog.find(Blog.pluck(:id).sample)
というわけで読みやすさなども兼ねて上記で落ち着いている。大体はテストで使う。
参考: Rails ActiveRecordでランダムにレコードを1件取得する
(おまけ)uniqがdeprecatedになってた
ちょっと本題からは逸れるが、気づかなくてハマってしまったので
uniqはrubyのArrayクラスとかにもあるメソッドなんだけど、railsではdistinctみたいな使い方もできる
その2つの使い分けをどこでしているかは調べていないが、空のActiveRecord::Relationに対してuniqするとArrayが返って、その後のwhere句とかで落ちてしまうみたいなことがあった。
なのでdistinctを使いましょうということでした。