LoginSignup
180
174

More than 5 years have passed since last update.

既存のscopeからor条件のSQLを組み立てる

Posted at

こんなモデルがあるとして

user.rb
class User < ActiveRecord::Base
  scope :male,   ->{ where(sex: :male) }   # 男性
  scope :female, ->{ where(sex: :female) } # 女性
  scope :adult,  ->{ where(arel_table[:age].gteq 20) } # 成人
  scope :minor,  ->{ where(arel_table[:age].lt 20) }   # 未成年
  scope :men,    ->{ adult.male }   # 成人,男性
  scope :women,  ->{ adult.female } # 成人,女性
  scope :boys,   ->{ minor.male }   # 未成年,男性
  scope :girls,  ->{ minor.female } # 未成年,女性
end

単純なwhereのscopeをorでつなげる場合。

ex) 男性 or 未成年

User.where(User.male.minor.where_values.reduce(:or)).to_sql
# => "SELECT `users`.* FROM `users`  WHERE ((`users`.`sex` = 'male' OR `users`.`age` < 20))"

# もちろん male.minor と boys は同じなのでこれでもいい
User.where(User.boys.where_values.reduce(:or)).to_sql
# => "SELECT `users`.* FROM `users`  WHERE ((`users`.`sex` = 'male' OR `users`.`age` < 20))"

ex) 男性 or 未成年 or :role => 'admin'

User.where(User.male.minor.where(role: 'admin').where_values.reduce(:or)).to_sql
# => "SELECT `users`.* FROM `users`  WHERE ((`users`.`sex` = 'male' OR `users`.`age` < 20 OR `users`.`role` = 'admin'))"

and条件も含むちょっと複雑なscopeをorでつなげる

ex) 成人女性(成人 and 女性) or 未成年男子(未成年 and 男子)

women = User.women.where_values.reduce(:and)
boys  = User.boys.where_values.reduce(:and)
User.where(women.or boys).to_sql
# => "SELECT `users`.* FROM `users`  WHERE ((`users`.`age` >= 20 AND `users`.`sex` = 'female' OR `users`.`age` < 20 AND `users`.`sex` = 'male'))"

ざっくり説明

ActiveRecord::Relation#where_valuesArel::Nodes::Nodeの配列が取得できる。
このNodeにはarelの.and()やら.or()やらが使えるので、そのあたりを組み合わせてからARの.where()だったり.merge()だったりに渡してあげるとOK。

180
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
180
174