ActiveRecord Arel O condición
¿Cómo se pueden combinar 2 condiciones diferentes usando lógico O en lugar de Y?
NOTA: 2 las condiciones se generan como ámbitos rails y no se pueden cambiar fácilmente a algo como where("x or y")
directamente.
Ejemplo simple:
admins = User.where(:kind => :admin)
authors = User.where(:kind => :author)
Es fácil de aplicar Y condicionar (que para este caso particular no tiene sentido):
(admins.merge authors).to_sql
#=> select ... from ... where kind = 'admin' AND kind = 'author'
Pero, ¿cómo puede producir la siguiente consulta teniendo 2 relaciones Arel diferentes ya disponibles?
#=> select ... from ... where kind = 'admin' OR kind = 'author'
Parece ( según Arel readme):
El operador OR aún no está soportado
Pero espero que no se aplique aquí y espero escribir algo como:
(admins.or authors).to_sql
10 answers
Llego un poco tarde a la fiesta, pero aquí está la mejor sugerencia que se me ocurrió:
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'))"
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-24 01:38:37
Las consultas ActiveRecord son objetos ActiveRecord::Relation
(que enloqueciendo no soportan or
), no objetos Arel (que lo hacen).
[ UPDATE: a partir de Rails 5," or " está soportado en ActiveRecord::Relation
; ver https://stackoverflow.com/a/33248299/190135 ]
Pero afortunadamente, su método where
acepta objetos de consulta ARel. Así que si User < ActiveRecord::Base
...
users = User.arel_table
query = User.where(users[:kind].eq('admin').or(users[:kind].eq('author')))
query.to_sql
ahora muestra el tranquilizador:
SELECT "users".* FROM "users" WHERE (("users"."kind" = 'admin' OR "users"."kind" = 'author'))
Para mayor claridad, puede extraer alguna consulta parcial temporal variables:
users = User.arel_table
admin = users[:kind].eq('admin')
author = users[:kind].eq('author')
query = User.where(admin.or(author))
Y naturalmente, una vez que tenga la consulta, puede usar query.all
para ejecutar la llamada a la base de datos real.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-15 19:38:51
De la página real del área :
El operador OR funciona así:
users.where(users[:name].eq('bob').or(users[:age].lt(25)))
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-02 06:02:02
A partir de Rails 5 tenemos ActiveRecord::Relation#or
, lo que le permite hacer esto:
User.where(kind: :author).or(User.where(kind: :admin))
...que se traduce en el sql que esperarías:
>> puts User.where(kind: :author).or(User.where(kind: :admin)).to_sql
SELECT "users".* FROM "users" WHERE ("users"."kind" = 'author' OR "users"."kind" = 'admin')
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-20 23:03:19
He tenido el mismo problema buscando una alternativa de activerecord a mongoid #any_of
.
@jswanner la respuesta es buena, pero solo funcionará si los parámetros where son un Hash:
> 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'
Para poder usar tanto cadenas como hashes, puedes usar esto:
q1 = User.where( "email = 'foo'" )
q2 = User.where( email: 'bar' )
User.where( q1.arel.constraints.reduce( :and ).or( q2.arel.constraints.reduce( :and ) ) )
De hecho, eso es feo, y no quieres usarlo a diario. He aquí un poco de #any_of
implementación que he hecho: https://gist.github.com/oelmekki/5396826
Deja hacer eso :
> 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')))
Editar: desde entonces, he publicado una gema para ayudar a construir O consultas.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-01 11:01:15
Simplemente haga un alcance para su condición OR:
scope :author_or_admin, where(['kind = ? OR kind = ?', 'Author', 'Admin'])
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-02 20:36:53
Usando SmartTuple se verá algo como esto:
tup = SmartTuple.new(" OR ")
tup << {:kind => "admin"}
tup << {:kind => "author"}
User.where(tup.compile)
O
User.where((SmartTuple.new(" OR ") + {:kind => "admin"} + {:kind => "author"}).compile)
Puede pensar que estoy sesgado, pero todavía considero que las operaciones de estructura de datos tradicionales son mucho más claras y convenientes que el encadenamiento de métodos en este caso particular.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-03 09:45:12
Para extender la respuesta de jswanner (que en realidad es una solución increíble y me ayudó) para buscar en Google a la gente:
Puede aplicar el ámbito así
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))
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:06
Qué pasa con este enfoque: http://guides.rubyonrails.org/active_record_querying.html#hash-conditions (y comprobar 2.3.3)
admins_or_authors = User.where(:kind => [:admin, :author])
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-16 11:21:02
Desafortunadamente no es compatible de forma nativa, por lo que necesitamos hackear aquí.
Y el hack se ve así, que es bastante ineficiente SQL (espero que los DBA no lo estén mirando: -)):
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)
Esto genera subselets.
Y un poco mejor hack (desde la perspectiva SQL) se ve así:
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>
Esto genera una condición propia o condición, pero obviamente solo toma en cuenta la parte DONDE de los ámbitos.
Elegí el primero hasta que veré cómo realizar.
En cualquier caso, debe tener bastante cuidado con él y ver el SQL generado.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-11 08:28:58