Hard work by INTERNET

ベンチャーで働くひとりぼっちWEB開発者が頑張るブログ

Scope that finds nothing or ends with .first does another lookup

結果が見つからなくてfirstメソッドで終わるとscopeが別の結果になる。

本文

Rails4.2とRuby2.2だよ。

class Car < ActiveRecord::Base
  scope :where_color_or_tint, ->(q) { where("color = ? OR tint = ?", q, q).limit(1).first  } 
  scope :find_color_or_tint, ->(q) { find_by("color = ? OR tint = ?", q, q) }
  scope :where_color, ->(q) { where(color: q).first }
  scope :find_color, ->(q) { find_by(color: q) }

最初に何も見つからんかったら、次にクエリが実行し、最初の値を返している(何も見つからなかったら全てを返しているの意かな?)。これは意図している?

Car.where_color_or_tint('test')

  Car Load (39.4ms)  SELECT  `cars`.* FROM `cars` WHERE (color = 'test' OR tint = 'test')  ORDER BY `users`.`id` ASC LIMIT 1
  Car Load (37.7ms)  SELECT `cars`.* FROM `cars`
=> [
  [0] #<Car:0x00000006bec6d8> {
Car.find_color_or_tint('test')

  Car Load (0.8ms)  SELECT  `cars`.* FROM `cars` WHERE (color = 'test' OR tint = 'test') LIMIT 1
  Car Load (39.1ms)  SELECT `cars`.* FROM `cars`
=> [
  [0] #<Car:0x000000068c53b0> {
Car.where_color('test')

 Car Load (0.8ms)  SELECT  `cars`.* FROM `cars` WHERE `cars`.`color` = 'test'  ORDER BY `cars`.`id` ASC LIMIT 1
  Car Load (37.4ms)  SELECT `cars`.* FROM `cars`
=> [
  [0] #<Car:0x00000006806168> {
Car.find_color('test')

  Car Load (0.8ms)  SELECT  `cars`.* FROM `cars` WHERE `cars`.`color` = 'test' LIMIT 1
  Car Load (38.9ms)  SELECT `cars`.* FROM `cars`
=> [
  [0] #<Car:0x00000006720550> {

クラスメソッドとして定義したらnilや[]を返す意図通りになる。

レス1

ActiveRecord::Scoping::Named で定義している、scopeメソッドnil || allを返すから、このような結果になっているのように見える。 あなたのケースはfirstfind_byでnilが返る。したがってすべてのレコードが得る。

first/find_byを使用せず、レコードがない場合の結果は言うでもないがnilではない空のARオブジェクトだ。

Car.where("color = 'gold' OR tint = 'gold'")
Car Load (0.3ms)  SELECT "cars".* FROM "cars" WHERE (color = 'gold' OR tint = 'gold')
=> #<ActiveRecord::Relation []>

Car.where("color = 'gold' OR tint = 'gold'").nil?
=> false

私が思うに、scopes は1レコードを返すfind_byや類似メソッドと使うために作られていない。

あなたの場合はこのようなのを試すほうがたぶんより適している。

class Car < ActiveRecord::Base
  scope :where_color_or_tint, ->(q) { where("color = ? OR tint = ?", q, q) }

  def self.first_car_with_color_or_tint(q)
    where_color_or_tint(q).first
  end
end

レス2

なるほど。ありがとう。

以上。