Help us understand the problem. What is going on with this article?

Rails ActiveRecord/SQL 小技集

More than 1 year has passed since last update.

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を使いますが、
慣れないうちはどんな場合にどれを使えばいいかが難しいです。
その辺のことは他にすばらしいまとめがあるので、基本的な使い分けなどはこのあたりを参考に。

孫モデルを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の前と後ろはきちんと同じ形式になっていないとダメみたいです。

参考: ActiveRecord で join と or と and が入り混じった場合

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away