ActiveRecord関連で初心者がつまづきやすいちょっとしたこと、やり方を忘れて何度も調べてしまうものをまとめておきます。随時更新する予定。
SQL直書きを極力避け、Railsの文法で書く
ActiveRecordはwhere内でSQLの書き方でも書けてしまうため、そちらで書いてしまったりしますが、極力rails流の文法で書くほうがきれいで、アップグレードにも対応しやすいです。
null
User.where(name: nil)
# = User.where("name IS NULL")
参考:[Rails]Where句にNOT NULLを指定する
範囲指定
..
だとBETWEEN :a AND :b
...
だとsomething < :a AND something > :b
User.where(created_at: 7.days.ago..3.days.ago)
# = User.where("created_at BETWEEN :a AND :b", a: 7.days.ago, b: 3.days.ago)
User.where(created_at: 7.days.ago...3.days.ago)
# = User.where("created_at > :a AND created_at < :b", a: 7.days.ago, b: 3.days.ago)
参考: ActiveRecordで日付・時刻の範囲検索をシンプルに書く方法
IN句
Arrayで渡します。
User.where(name: ['John','Smith','Richard'])
# = User.where("name IN ('John','Smith','Richard')")
PostgreSQL Array型を使う
PostgreSQLだと、Array型を使うことができます。
Arrayがどれかを含む
Array中に指定した要素のいずれかあるレコードを取得するにはANYを使います。
# Schedule.day_of_week = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']
# 'Sun'を含むレコードを抽出
Schedule.where(":day = ANY(day_of_week)", :day => 'Sun')
Arrayが空のレコードを抽出
Schedule.where("day_of_week = {}"}
子モデル/孫モデルの結合と絞込をうまいことやる
子モデルや孫モデルをキャッシュしたり(N+1対策)、子モデル・孫モデルで条件検索しようとした場合、joins/includes/eager_load/preloadを使いますが、
慣れないうちはどんな場合にどれを使えばいいかが難しいです。
その辺のことは他にすばらしいまとめがあるので、基本的な使い分けなどはこのあたりを参考に。
- ActiveRecordのincludes、joins、eager_load等の違いを調べてみた
- 似ているようで全然違う!?Activerecordにおけるincludesとjoinsの振る舞いまとめ
- ActiveRecord ~ 複数テーブルにまたがる検索(先読み、JOINなど)
孫モデルをincludes
User.includes(friends: :address)
User.includes(:address, friends: [:address, :followers]) # 複数だとこうなる
子モデルをキャッシュ&絞込
子モデルで条件検索しつつ、子モデルのデータも使いたい時にはincludesとともにreferencesも指定する必要があるようです。
User.includes(:friends).references(:friends).where("friends.name = ?",'John')
参考 ActiveRecord::QueryMethods#includes
子モデル/孫モデルが存在するレコードを抽出
# 子モデルの場合
User.joins(:friends).where("friends.id IS NOT NULL")
# 孫モデルの場合
User.friends.joins(:followers).where("followers.id IS NOT NULL")
joinsで複数の同レコードを返さないようにする
子レコードで絞込をしたい&キャッシュはしない時にはjoinsを使いますが、親レコードが複数の子レコードを持つ場合、子レコードの数だけ結果が返ってきてしまいます。
# Spot has_many :stations
Spot.all.size
# => 1
Spot.first.stations.size
# => 3
spots = Spot.joins(:stations).where("stations.name = hoge")
spots.size
# => 3
というような感じで、上の例だと同じレコードが3件返ってきてしまいます。
Spot.all.size
が1なので、ここで戻ってきてほしいのは1件だけです。レコードが重複をせずuniqになるようにするにはdistinctを使います。
spots = Spot.joins(:stations).where("stations.name = hoge").distinct
spots.size
# => 1
distinctとorderを両方使いたい
参考 Rails tips: Active Recordの#from
を使ってorderとdistinctを1つのクエリにする(翻訳)
ORを使いこなす
普通の書き方は特に難しいことはありません。
Spot.where(prefecture: 'kanagawa').or(Spot.where(city: 'machida'))
子モデルでOR
john = User.find_by(name: 'John')
john.friends.where(gender: 'male').or(john.friends.where(name: 'Maria'))
# これはだめ
john.friends.where(gender: 'male').or(Friend.where(name: 'Maria'))
結合先(子モデル)の条件でORを使う
User.joins(:friends).where("friends.gender = male").or(User.joins(:friends).where("friends.name = Maria"))
# これもやっぱりだめ
User.joins(:friends).where("friends.gender = male").or(where("friends.name = Maria"))
ORの前と後ろはきちんと同じ形式になっていないとダメみたいです。