はじめに
Rails 6 に追加されそうな新機能を試す第66段。 今回は、 query with large number
編です。
Rails 6 では、 検索条件の値が Integer などの型の範囲外の値であっても検索できるようになりました。
Ruby 2.6.3, Rails 6.0.0.rc1, Rails 5.2.3 で確認しました。Rails 6.0.0.rc1 は gem install rails --prerelease
でインストールできます。
(Rails 6.0.0 がリリースされましたが、確認当時は Rails 6.0.0.rc1 がリリースされた時でした。悪しからず。)
$ rails --version
Rails 6.0.0.rc1
今回は、 User モデルに :bigint
を指定した age
を追加して、 age
の範囲外の値を検索条件にして検索してみます。
プロジェクトを作る
rails new rails6_0_0rc1
cd rails6_0_0rc1
model を作る
User モデルを作ります。
name
の他に :bigint
を指定した age
を追加します。
bin/rails g model User name age:bigint
seed データを作る
1件だけですが、 seed データを作成します。
User.create(name: 'Taro', age: 1)
User モデルを修正する
User モデルに scope を5つ作ります。
ここで条件に age の値の範囲に含まれない大きな値と小さな値を指定します。
class User < ApplicationRecord
LARGE = 9223372036854775808
SMALL = -9223372036854775809
scope :age_in_large, -> { where(age: [1..LARGE]) }
scope :age_eq_large, -> { where(age: LARGE) }
scope :age_not_eq_large, -> { where.not(age: LARGE) }
scope :age_in_small, -> { where(age: [SMALL..1]) }
scope :age_in_small_large, -> { where(age: [SMALL..LARGE]) }
end
seed データを登録します。
$ bin/rails db:create db:migrate db:seed
rails console で確認する
rails console
で確認してみます。
irb(main):001:0> User.age_in_large
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."age" >= $1 LIMIT $2 [["age", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, name: "Taro", age: 1, created_at: "2019-07-26 21:21:06", updated_at: "2019-07-26 21:21:06">]>
irb(main):002:0> User.age_eq_large
User Load (0.5ms) SELECT "users".* FROM "users" WHERE 1=0 LIMIT $1 [["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
irb(main):003:0> User.age_not_eq_large
User Load (0.5ms) SELECT "users".* FROM "users" WHERE 1=1 LIMIT $1 [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, name: "Taro", age: 1, created_at: "2019-07-26 21:21:06", updated_at: "2019-07-26 21:21:06">]>
irb(main):004:0> User.age_in_small
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."age" <= $1 LIMIT $2 [["age", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, name: "Taro", age: 1, created_at: "2019-07-26 21:21:06", updated_at: "2019-07-26 21:21:06">]>
irb(main):005:0> User.age_in_small_large
User Load (0.8ms) SELECT "users".* FROM "users" WHERE 1=1 LIMIT $1 [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, name: "Taro", age: 1, created_at: "2019-07-26 21:21:06", updated_at: "2019-07-26 21:21:06">]>
ここで、実際に発行されているSQLに注目してください。SQL自体には、 -9223372036854775809 や 9223372036854775808 は登場しません。
これは、 age の値として、 -9223372036854775809 や 9223372036854775808 は、あり得ない範囲外の値なので、検索条件に含まなくても良いためです。
工夫されてますね。 User.age_not_eq_large
の条件の where 1=1
など、なるほどと思ってしまいます。
Rails 5では
検索条件の値が、 age
の範囲に収まらないため、 RangeError になってしまいます。
irb(main):001:0> User.age_eq_large
Traceback (most recent call last):
ActiveModel::RangeError (9223372036854775808 is out of range for ActiveModel::Type::Integer with limit 8 bytes)
irb(main):002:0> User.age_not_eq_large
Traceback (most recent call last):
ActiveModel::RangeError (9223372036854775808 is out of range for ActiveModel::Type::Integer with limit 8 bytes)
irb(main):003:0> User.age_in_large
Traceback (most recent call last):
ActiveModel::RangeError (9223372036854775808 is out of range for ActiveModel::Type::Integer with limit 8 bytes)
irb(main):004:0> User.age_in_small
Traceback (most recent call last):
ActiveModel::RangeError (-9223372036854775809 is out of range for ActiveModel::Type::Integer with limit 8 bytes)
irb(main):005:0> User.age_in_small_large
Traceback (most recent call last):
ActiveModel::RangeError (-9223372036854775809 is out of range for ActiveModel::Type::Integer with limit 8 bytes)
試したソース
試したソースは以下にあります。
https://github.com/suketa/rails6_0_0rc1/tree/try066_query_with_large_number