OU consultas com matrizes como argumentos no Rails 4

tl; dr

query = Order.unscoped.where(uuid: uuids, id: ids)
Order.where(query.where_values.inject(:or))
#=> Order Load (0.7ms) SELECT "orders".* FROM "orders" WHERE ("orders"."uuid" IN ('5459eed8350e1b472bfee48375034103', '21313213jkads', '43ujrefdk2384us') OR "orders"."id" IN (2, 3, 4))

Visto que o ormétodo não está disponível no Rails 4, é complicado realizar consultas OR com arrays como argumentos. Claro que podemos escrever SQL puro, mas existe uma maneira mais simples de fazer isso?

Meu colega de trabalho me enviou um link para uma discussão no ruby-forum.com . É onde encontramos o seguinte código:

recipe_query = Recipe.where("pastry_id = 1").where("filling_id = 1")
Recipe.where(recipe_query.where_values.join(" OR "))

No entanto, não funcionou. where_valuesretorna uma matriz de Arel::Nodes::Inobjetos e não podemos simplesmente juntá-los.

uma postagem no Stack Overflow onde o autor se aprofunda nos métodos Arel.

Category.top_level.where_values.map(&:to_sql)
=> ["`categories`.`parent_id` IS NULL"]

Oh sim, como eu poderia esquecer to_sql? Strings em uma matriz como esta podem ser facilmente unidas, portanto, em nosso caso, o código se parece com o seguinte:

query = Order.where(uuid: uuids, id: ids)
Order.where(query.where_values.map(&:to_sql).join(" OR "))
#=> Order Load (0.7ms) SELECT "orders".* FROM "orders" WHERE ("orders"."uuid" IN ('5459eed8350e1b472bfee48375034103', '21313213jkads', '43ujrefdk2384us') OR "orders"."id" IN (2, 3, 4))

É isso aí! Onde os valores agora são unidos or, não and.

Mas espere! Podemos fazer melhor. Cameron Martin no StackOverflow postou uma versão um pouco mais elegante:

query = Order.where(uuid: uuids, id: ids)
Order.where(query.where_values.inject(:or))

Rails 4.1+

O Rails 4.1 trata default_scopeapenas como um escopo regular . O escopo padrão (se houver) é incluído no where_valuesresultado e adicionará a instrução entre o escopo padrão e seus s. Isso é ruim.inject(:or)orwhere

Para resolver isso, você só precisa remover o escopo da consulta.

query = Order.unscoped.where(uuid: uuids, id: ids)
Order.where(query.where_values.inject(:or))

Possíveis ressalvas

Ao realizar uma consulta, os elementos da matriz são convertidos em tipos correspondentes, portanto, tome cuidado.

ids = ["5459eed8350e1b472bfee48375034103", "2", "3"]
query
= Order.where(uuid: ids, id: ids)
Order.where(query.where_values.map(&:to_sql).join(" OR "))
#=> Order Load (0.6ms) SELECT "orders".* FROM "orders" WHERE ("orders"."uuid" IN ('5459eed8350e1b472bfee48375034103', '2', '3') OR "orders"."id" IN (5459, 2, 3))