Ruby
Rails
ActiveRecord
rails3

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

More than 5 years have passed since last update.

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


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。