ActiveRecord Arel или условие
как вы можете объединить 2 различных условий с помощью логического ИЛИ вместо и?
Примечание: 2 условия генерируются как области rails и не могут быть легко изменены в нечто вроде where("x or y")
напрямую.
простой пример:
admins = User.where(:kind => :admin)
authors = User.where(:kind => :author)
легко применить и условие (которое для этого конкретного случая бессмысленно):
(admins.merge authors).to_sql
#=> select ... from ... where kind = 'admin' AND kind = 'author'
но как вы можете создать следующий запрос, имеющий уже 2 разных отношения Arel доступно?
#=> select ... from ... where kind = 'admin' OR kind = 'author'
кажется (согласно Arel readme):
оператор OR еще не поддерживается
но я надеюсь, что это не относится здесь и ожидать, чтобы написать что-то вроде:
(admins.or authors).to_sql
10 ответов:
Я немного опоздал на вечеринку, но вот лучшее предложение, которое я мог бы придумать:
admins = User.where(:kind => :admin) authors = User.where(:kind => :author) admins = admins.where_values.reduce(:and) authors = authors.where_values.reduce(:and) User.where(admins.or(authors)).to_sql # => "SELECT \"users\".* FROM \"users\" WHERE ((\"users\".\"kind\" = 'admin' OR \"users\".\"kind\" = 'author'))"
ActiveRecord запросы
ActiveRecord::Relation
объекты (которые сводят с ума не поддерживаютor
), а не Arel объекты (которые делают).[ обновление: начиная с рельсов 5, "или" поддерживается в
ActiveRecord::Relation
; см.https://stackoverflow.com/a/33248299/190135]но, к счастью, их
where
метод принимает объекты запроса ARel. Так что еслиUser < ActiveRecord::Base
...users = User.arel_table query = User.where(users[:kind].eq('admin').or(users[:kind].eq('author')))
query.to_sql
теперь показывает ободряюще:SELECT "users".* FROM "users" WHERE (("users"."kind" = 'admin' OR "users"."kind" = 'author'))
для ясности, вы может извлечь некоторые временные переменные частичного запроса:
users = User.arel_table admin = users[:kind].eq('admin') author = users[:kind].eq('author') query = User.where(admin.or(author))
и естественно, если у вас есть запрос, вы можете использовать
query.all
для выполнения фактического вызова базы данных.
по состоянию на рельсы 5 у нас есть
ActiveRecord::Relation#or
, что позволяет сделать этого:User.where(kind: :author).or(User.where(kind: :admin))
...который переводится на sql, который вы ожидаете:
>> puts User.where(kind: :author).or(User.where(kind: :admin)).to_sql SELECT "users".* FROM "users" WHERE ("users"."kind" = 'author' OR "users"."kind" = 'admin')
у меня такая же проблема, искал и ActiveRecord альтернатива mongoid по
#any_of
.@jswanner ответ хорош, но будет работать только в том случае, если параметры where являются хэшем :
> User.where( email: 'foo', first_name: 'bar' ).where_values.reduce( :and ).method( :or ) => #<Method: Arel::Nodes::And(Arel::Nodes::Node)#or> > User.where( "email = 'foo' and first_name = 'bar'" ).where_values.reduce( :and ).method( :or ) NameError: undefined method `or' for class `String'
чтобы иметь возможность использовать как строки, так и хэши, вы можете использовать это:
q1 = User.where( "email = 'foo'" ) q2 = User.where( email: 'bar' ) User.where( q1.arel.constraints.reduce( :and ).or( q2.arel.constraints.reduce( :and ) ) )
действительно, это некрасиво, и вы не хотите использовать это на ежедневной основе. Вот некоторые
#any_of
реализация я сделал : https://gist.github.com/oelmekki/5396826Он позволил сделать это:
> q1 = User.where( email: 'foo1' ); true => true > q2 = User.where( "email = 'bar1'" ); true => true > User.any_of( q1, q2, { email: 'foo2' }, "email = 'bar2'" ) User Load (1.2ms) SELECT "users".* FROM "users" WHERE (((("users"."email" = 'foo1' OR (email = 'bar1')) OR "users"."email" = 'foo2') OR (email = 'bar2')))
Edit: с тех пор я опубликовал драгоценный камень, чтобы помочь строить или запросы.
просто сделайте область для вашего или условия:
scope :author_or_admin, where(['kind = ? OR kind = ?', 'Author', 'Admin'])
используя SmartTuple это будет выглядеть примерно так:
tup = SmartTuple.new(" OR ") tup << {:kind => "admin"} tup << {:kind => "author"} User.where(tup.compile)
или
User.where((SmartTuple.new(" OR ") + {:kind => "admin"} + {:kind => "author"}).compile)
вы можете подумать, что я предвзят, но я все еще считаю, что традиционные операции структуры данных являются гораздо яснее и удобно, чем метод цепочки в данном конкретном случае.
направить jswanner ответ (что на самом деле является удивительным решением и помогло мне) для людей в гугле:
вы можете применить область, как это
scope :with_owner_ids_or_global, lambda{ |owner_class, *ids| with_ids = where(owner_id: ids.flatten).where_values.reduce(:and) with_glob = where(owner_id: nil).where_values.reduce(:and) where(owner_type: owner_class.model_name).where(with_ids.or( with_glob )) } User.with_owner_ids_or_global(Developer, 1, 2) # => ...WHERE `users`.`owner_type` = 'Developer' AND ((`users`.`owner_id` IN (1, 2) OR `users`.`owner_id` IS NULL))
Как насчет такого подхода:http://guides.rubyonrails.org/active_record_querying.html#hash-conditions (и проверьте 2.3.3)
admins_or_authors = User.where(:kind => [:admin, :author])
к сожалению, он не поддерживается изначально, поэтому нам нужно взломать здесь.
и Хак выглядит так, что это довольно неэффективный SQL (надеюсь, что DBAs не смотрят на него : -)):
admins = User.where(:kind => :admin) authors = User.where(:kind => :author) both = User.where("users.id in (#{admins.select(:id)}) OR users.id in (#{authors.select(:id)})") both.to_sql # => where users.id in (select id from...) OR users.id in (select id from)
это создает подмножества.
и немного лучше взломать (с точки зрения SQL) выглядит так:
admins_sql = admins.arel.where_sql.sub(/^WHERE/i,'') authors_sql = authors.arel.where_sql.sub(/^WHERE/i,'') both = User.where("(#{admins_sql}) OR (#{authors_sql})") both.to_sql # => where <admins where conditions> OR <authors where conditions>
это создает правильное или условие, но, очевидно, оно учитывает только часть областей WHERE.
Я выбрал 1-й, пока я не посмотрю, как он работает.
в любом случае, вы должны быть очень осторожны с ним и посмотреть сгенерированный SQL.