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

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

More than 3 years have passed since last update.

はじめに

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:

参考

QUANON
あんた、マジなんだな?
http://quanon.github.io/
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
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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
ユーザーは見つかりませんでした