ruby on rails - rails - Rieles: ¿cómo encadenar consultas de ámbito con O en lugar de Y?
ruby on rails tutorial (17)
Estoy usando Rails3, ActiveRecord
Me pregunto cómo puedo encadenar los ámbitos con declaraciones OR en lugar de AND.
p.ej
Person.where(:name => "John").where(:lastname => "Smith")
Eso normalmente devuelve name = ''John'' AND lastname = ''Smith'', pero me gustaría:
name = ''John'' O apellido = ''Smith''
Carriles 4
scope :combined_scope, -> { where("name = ? or name = ?", ''a'', ''b'') }
De acuerdo con github.com/rails/rails/pull/16052 , Rails 5 ahora admite la siguiente sintaxis para encadenar consultas:
Post.where(id: 1).or(Post.where(id: 2))
También hay una copia de respaldo de la funcionalidad en Rails 4.2 a través de esta joya.
En caso de que alguien esté buscando una respuesta actualizada a esta, parece que hay una solicitud de extracción existente para obtener esto en Rails: https://github.com/rails/rails/pull/9052 .
Gracias al parche de mono de @ j-mcnally para ActiveRecord ( https://gist.github.com/j-mcnally/250eaaceef234dd8971b ) puede hacer lo siguiente:
Person.where(name: ''John'').or.where(last_name: ''Smith'').all
Aún más valioso es la capacidad de encadenar alcances con OR
:
scope :first_or_last_name, ->(name) { where(name: name.split('' '').first).or.where(last_name: name.split('' '').last) }
scope :parent_last_name, ->(name) { includes(:parents).where(last_name: name) }
Luego puede encontrar todas las Personas con nombre o apellido o cuyos padres con apellido
Person.first_or_last_name(''John Smith'').or.parent_last_name(''Smith'')
No es el mejor ejemplo para el uso de esto, solo trata de encajarlo con la pregunta.
Entonces, la respuesta a la pregunta original, ¿puedes unir ámbitos con ''o'' en lugar de ''y'' parece ser ''no, no puedes''? Pero puede codificar manualmente un alcance o consulta completamente diferente que hace el trabajo, o utilizar un marco diferente de ActiveRecord, por ejemplo, MetaWhere o Squeel. No es útil en mi caso
Estoy ''or''ing un alcance generado por pg_search, que hace un poco más que seleccionar, incluye el orden de ASC, que hace un lío de una unión limpia. Quiero ''o'' con un alcance artesanal que hace cosas que no puedo hacer en pg_search. Así que tuve que hacerlo así.
Product.find_by_sql("(#{Product.code_starts_with(''Tom'').to_sql}) union (#{Product.name_starts_with(''Tom'').to_sql})")
Es decir, convertir los ámbitos en sql, poner corchetes alrededor de cada uno, unirlos entre sí y luego find_by_sql con el sql generado. Es un poco basura, pero funciona.
No, no me digas que puedo usar "contra: [: nombre,: código]" en pg_search, me gustaría hacerlo así, pero el campo ''nombre'' es un hstore, que pg_search no puede manejar todavía. Por lo tanto, el alcance por nombre tiene que estar hecho a mano y luego unido con el alcance pg_search.
Este sería un buen candidato para MetaWhere si está utilizando Rails 3.0+, pero no funciona en Rails 3.1. Es posible que desee probar squeel en squeel lugar. Está hecho por el mismo autor. Así es como harías una cadena OR:
Person.where{(name == "John") | (lastname == "Smith")}
Puedes mezclar y combinar AND / OR, entre muchas otras squeel .
Para mí (Rails 4.2.5) solo funciona así:
{ where("name = ? or name = ?", a, b) }
Rails 4 + Scope + Arel
class Creature < ActiveRecord::Base
scope :is_good_pet, -> {
where(
arel_table[:is_cat].eq(true)
.or(arel_table[:is_dog].eq(true))
.or(arel_table[:eats_children].eq(false))
)
}
end
Intenté encadenar ámbitos con nombre .o sin suerte, pero esto funcionó para encontrar cualquier cosa con esos booleanos establecidos. Genera SQL como
SELECT ''CREATURES''.* FROM ''CREATURES'' WHERE (((''CREATURES''.''is_cat'' = 1 OR ''CREATURES''.''is_dog'' = 1) OR ''CREATURES''.''eats_children'' = 0))
Si no puede escribir manualmente la cláusula where para incluir la instrucción "o" (es decir, si desea combinar dos ámbitos), puede usar union:
Model.find_by_sql("#{Model.scope1.to_sql} UNION #{Model.scope2.to_sql}")
(fuente: ActiveRecord Query Union )
Esto devolverá todos los registros que coincidan con cualquiera de las consultas. Sin embargo, esto devuelve una matriz, no un arel. Si realmente desea devolver un arel, puede consultar esta esencia: https://gist.github.com/j-mcnally/250eaaceef234dd8971b .
Esto hará el trabajo, siempre y cuando no te moleste el mono parches de rieles.
Simplemente publicando la sintaxis Array para la misma columna O consultas para ayudar a asomarse.
Person.where(name: ["John", "Steve"])
También puede usar MetaWhere gem para no mezclar su código con SQL:
Person.where((:name => "John") | (:lastname => "Smith"))
Tu harías
Person.where(''name=? OR lastname=?'', ''John'', ''Smith'')
En este momento, no hay ningún otro soporte OR por la nueva sintaxis AR3 (es decir, sin utilizar alguna gema de terceros).
Una versión actualizada de Rails / ActiveRecord puede admitir esta sintaxis de forma nativa. Se vería similar a:
Foo.where(foo: ''bar'').or.where(bar: ''bar'')
Como se señala en esta solicitud de extracción https://github.com/rails/rails/pull/9052
Por ahora, simplemente seguir con lo siguiente funciona muy bien:
Foo.where(''foo= ? OR bar= ?'', ''bar'', ''bar'')
Usa ARel
t = Person.arel_table
results = Person.where(
t[:name].eq("John").
or(t[:lastname].eq("Smith"))
)
la gema squeel
proporciona una forma increíblemente fácil de lograr esto (antes de esto, utilicé algo así como el método de @ coloradoblue):
names = ["Kroger", "Walmart", "Target", "Aldi"]
matching_stores = Grocery.where{name.like_any(names)}
También vea estas preguntas relacionadas: here , here y here
Para rieles 4, basado en coderwall.com/p/dgv7ag/… y esta respuesta original
Person
.unscoped # See the caution note below. Maybe you want default scope here, in which case just remove this line.
.where( # Begin a where clause
where(:name => "John").where(:lastname => "Smith") # join the scopes to be OR''d
.where_values # get an array of arel where clause conditions based on the chain thus far
.inject(:or) # inject the OR operator into the arels
# ^^ Inject may not work in Rails3. But this should work instead:
.joins(" OR ")
# ^^ Remember to only use .inject or .joins, not both
) # Resurface the arels inside the overarching query
Tenga en cuenta la precaución del artículo al final:
Rails 4.1+
Rails 4.1 trata default_scope solo como un alcance regular. El alcance predeterminado (si tiene alguno) se incluye en el resultado where_values e inject (: o) agregará o una declaración entre el alcance predeterminado y sus recursos. Eso es malo.
Para resolver eso, solo necesita desaprender la consulta.
Actualización para Rails4
no requiere gemas de terceros
a = Person.where(name: "John") # or any scope
b = Person.where(lastname: "Smith") # or any scope
Person.where([a, b].map{|s| s.arel.constraints.reduce(:and) }.reduce(:or))/
.tap {|sc| sc.bind_values = [a, b].map(&:bind_values) }
Respuesta anterior
no requiere gemas de terceros
Person.where(
Person.where(:name => "John").where(:lastname => "Smith")
.where_values.reduce(:or)
)
names = ["tim", "tom", "bob", "alex"]
sql_string = names.map { |t| "name = ''#{t}''" }.join(" OR ")
@people = People.where(sql_string)