LoginSignup
235
174

More than 5 years have passed since last update.

Rails 5 の or を色々試してみた

Last updated at Posted at 2016-08-03

はじめに

Rails 5 で ActiveRecord に待望の or メソッドが追加されましたね。これで where メソッドで SQL をベタ書きすることなく OR 演算子が使えます!ただ、where と or を組み合わせた場合、実際にどのような SQL が発行されるのかが気になったので検証してみました。

検証

前準備

以下の Model と seed データを用意します。

app/models/anime.rb
class Anime < ApplicationRecord
  has_many :characters
end
app/models/character.rb
class Character < ApplicationRecord
  belongs_to :anime

  enum sex: { male: 1, female: 2 }
end
db/seeds.rb
Anime.create!(title: 'STEINS;GATE', year: 2011).tap do |anime|
  anime.characters.create!(name: '岡部 倫太郎', sex: :male,   age: 18)
  anime.characters.create!(name: '椎名 まゆり', sex: :female, age: 16)
end

Anime.create!(title: '刀語', year: 2010).tap do |anime|
  anime.characters.create!(name: '鑢 七花', sex: :male, age: 24)
  anime.characters.create!(name: 'とがめ',  sex: :female)
end

1. Model.where(A).or(Model.where(B)) の場合

コード

Character
  .where(name: '岡部 倫太郎')
  .or(Character.where(sex: :female))

SQL

SELECT "characters".*
FROM "characters"
WHERE ("characters"."name" = '岡部 倫太郎' OR "characters"."sex" = 2)

+----+-------------+--------+-----+
| id | name        | sex    | age |
+----+-------------+--------+-----+
| 1  | 岡部 倫太郎 | male   | 18  |
| 2  | 椎名 まゆり | female | 16  |
| 4  | とがめ      | female |     |
+----+-------------+--------+-----+
3 rows in set

結果

A OR B となりました。

2. Model.where(A, B).or(Model.where(C)) の場合

コード

Character
  .where(name: '岡部 倫太郎', sex: :male)
  .or(Character.where(age: nil))

SQL

SELECT "characters".*
FROM "characters"
WHERE ("characters"."name" = '岡部 倫太郎' AND "characters"."sex" = 1 OR "characters"."age" IS NULL)

+----+-------------+--------+-----+
| id | name        | sex    | age |
+----+-------------+--------+-----+
| 1  | 岡部 倫太郎 | male   | 18  |
| 4  | とがめ      | female |     |
+----+-------------+--------+-----+
2 rows in set

結果

A AND B OR C となりました。OR より AND の方が優先して評価されるため、これは (A AND B) OR C と同義です。

ちなみに

Character
  .where(name: '岡部 倫太郎').where(sex: :male)
  .or(Character.where(age: nil))

と書いても発行される SQL は同じです。

3. Model.where(A).or(Model.where(B, C)) の場合

コード

Character
  .where(name: '岡部 倫太郎')
  .or(Character.where(sex: :male, age: nil))

SQL

SELECT "characters".*
FROM "characters"
WHERE ("characters"."name" = '岡部 倫太郎' OR "characters"."sex" = 1 AND "characters"."age" IS NULL)

+----+-------------+------+-----+
| id | name        | sex  | age |
+----+-------------+------+-----+
| 1  | 岡部 倫太郎 | male | 18  |
+----+-------------+------+-----+
1 row in set

結果

A OR B AND C となりました。これは A OR (B AND C) と同義ですね。

4. Model.where(A).or(Model.where(B)).or(Model.where(C)) の場合

コード

Character
  .where(name: '岡部 倫太郎')
  .or(Character.where(sex: :male))
  .or(Character.where(age: nil))

SQL

SELECT "characters".*
FROM "characters"
WHERE (("characters"."name" = '岡部 倫太郎' OR "characters"."sex" = 1) OR "characters"."age" IS NULL)

+----+-------------+--------+-----+
| id | name        | sex    | age |
+----+-------------+--------+-----+
| 1  | 岡部 倫太郎 | male   | 18  |
| 3  | 鑢 七花     | male   | 24  |
| 4  | とがめ      | female |     |
+----+-------------+--------+-----+
3 rows in set

結果

(A OR B) OR C となりました。これは A OR B OR C と同義ですね。

5. joins を含めた場合

最後に joins (INNER JOIN) を含めたコードで検証してみます。

まず以下のコードを実行します。

Character
  .joins(:anime)
  .merge(Anime.where(title: '刀語'))
  .or(Character.where(sex: :male))

するとエラーとなってしまいます。

ArgumentError: Relation passed to #or must be structurally compatible. Incompatible values: [:joins]

「or に渡される Relation は構造的に互換性がなくてはいけない」ということらしいですね。そこで、この指摘に従って書き換えてみます。

コード

relation = Character.joins(:anime)
relation
  .merge(Anime.where(title: '刀語'))
  .or(relation.where(sex: :male))

SQL

SELECT "characters".*
FROM "characters"
INNER JOIN "animes" ON "animes"."id" = "characters"."anime_id"
WHERE ("animes"."title" = '刀語' OR "characters"."sex" = 1)

+----+-------------+--------+-----+
| id | name        | sex    | age |
+----+-------------+--------+-----+
| 1  | 岡部 倫太郎 | male   | 18  |
| 3  | 鑢 七花     | male   | 24  |
| 4  | とがめ      | female |     |
+----+-------------+--------+-----+
3 rows in set

結果

joins と merge を使ったコードでも、or を使って想定通りの SQL を発行することができました。 :blush:

参考

235
174
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
235
174