Rails: Как связать запросы области действия с ИЛИ вместо И?
Я использую Rails3, ActiveRecord
Просто интересно, как я могу связать области видимости с помощью операторов OR вместо AND.
например
Person.where(:name => "John").where(:lastname => "Smith")
Это обычно возвращает имя = 'Джон' И фамилия = 'Смит', но я бы хотел:
имя = 'Джон' ИЛИ фамилия = 'Смит'
20 ответов
Вы бы сделали
Person.where('name=? OR lastname=?', 'John', 'Smith')
Прямо сейчас, нет никакой другой ИЛИ поддержки с новым синтаксисом AR3 (то есть без использования каких-либо сторонних гемов).
Согласно этому запросу, Rails 5 теперь поддерживает следующий синтаксис для цепочки запросов:
Post.where(id: 1).or(Post.where(id: 2))
Есть также обратный порт функциональности в Rails 4.2 через этот драгоценный камень.
Просто выложите синтаксис Array для того же столбца или запросов, чтобы выглянуло.
Person.where(name: ["John", "Steve"])
Используйте ARel
t = Person.arel_table
results = Person.where(
t[:name].eq("John").
or(t[:lastname].eq("Smith"))
)
Обновление для Rails4
не требует никаких сторонних драгоценных камней
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) }
Старый ответ
не требует никаких сторонних драгоценных камней
Person.where(
Person.where(:name => "John").where(:lastname => "Smith")
.where_values.reduce(:or)
)
В случае, если кто-то ищет обновленный ответ на этот вопрос, похоже, что существует существующий пул-запрос для передачи его в Rails: https://github.com/rails/rails/pull/9052.
Благодаря патчу @j-mcnally для ActiveRecord ( https://gist.github.com/j-mcnally/250eaaceef234dd8971b) вы можете сделать следующее:
Person.where(name: 'John').or.where(last_name: 'Smith').all
Еще более ценной является способность связывать прицелы с 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) }
Затем вы можете найти всех людей с именем или фамилией или чей родитель с фамилией
Person.first_or_last_name('John Smith').or.parent_last_name('Smith')
Не лучший пример для использования этого, но просто пытаюсь соответствовать этому вопросу.
Вы также можете использовать гем MetaWhere, чтобы не смешивать ваш код с SQL-компонентами:
Person.where((:name => "John") | (:lastname => "Smith"))
Если вы хотите предоставить область (вместо явной работы над всем набором данных), вот что вы должны сделать с Rails 5:
scope :john_or_smith, -> { where(name: "John").or(where(lastname: "Smith")) }
Или же:
def self.john_or_smith
where(name: "John").or(where(lastname: "Smith"))
end
Для меня (Rails 4.2.5) это работает только так:
{ where("name = ? or name = ?", a, b) }
Сейчас я работаю в Rails 6, и кажется, что теперь это возможно. Используя запросы от OP:
# in the Person model:
scope :john, -> { where(name: "John") }
scope :smith, -> { where(lastname: "Smith") }
scope :john_or_smith, -> { john.or(self.smith) }
Это было бы хорошим кандидатом для MetaWhere, если вы используете Rails 3.0+, но это не работает на Rails 3.1. Вы можете попробовать вместо этого Squeel. Это сделано тем же автором. Вот как бы вы выполнили цепочку на основе ИЛИ:
Person.where{(name == "John") | (lastname == "Smith")}
Вы можете смешивать и сочетать AND/OR, среди многих других удивительных вещей.
Обновленная версия Rails/ActiveRecord может изначально поддерживать этот синтаксис. Это будет выглядеть примерно так:
Foo.where(foo: 'bar').or.where(bar: 'bar')
Как отмечено в этом запросе на получение https://github.com/rails/rails/pull/9052.
На данный момент просто придерживаться следующих работ прекрасно работает:
Foo.where('foo= ? OR bar= ?', 'bar', 'bar')
Рельсы 4 + Сфера + Арель
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
Я попытался связать именованные области с.or, но не повезло, но это помогло найти что-нибудь с этими булевыми значениями. Создает SQL как
SELECT 'CREATURES'.* FROM 'CREATURES' WHERE ((('CREATURES'.'is_cat' = 1 OR 'CREATURES'.'is_dog' = 1) OR 'CREATURES'.'eats_children' = 0))
Это очень удобный способ, и он отлично работает в Rails 5:
Transaction
.where(transaction_type: ["Create", "Correspond"])
.or(
Transaction.where(
transaction_type: "Status",
field: "Status",
newvalue: ["resolved", "deleted"]
)
)
.or(
Transaction.where(transaction_type: "Set", field: "Queue")
)
Рельсы 4
scope :combined_scope, -> { where("name = ? or name = ?", 'a', 'b') }
Если вы не можете выписать предложение where вручную, чтобы включить оператор "или" (т. Е. Хотите объединить две области), вы можете использовать union:
Model.find_by_sql("#{Model.scope1.to_sql} UNION #{Model.scope2.to_sql}")
(источник: ActiveRecord Query Union)
Это вернет все записи, соответствующие любому запросу. Тем не менее, это возвращает массив, а не arel. Если вы действительно хотите вернуть верблюда, вы можете ознакомиться с этой сущностью: https://gist.github.com/j-mcnally/250eaaceef234dd8971b.
Это сработает, если вы не возражаете, если обезьяна исправит рельсы.
Также посмотрите эти связанные вопросы: здесь, здесь и здесь
Для рельсов 4, на основе этой статьи и этого оригинального ответа
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
Обратите внимание на предостережение статьи в конце:
Rails 4.1+
Rails 4.1 рассматривает default_scope как обычную область видимости. Область действия по умолчанию (если она у вас есть) включена в результат where_values, а команда inject(: или) добавит или выражение между областью действия по умолчанию и вашими данными. Это плохо.
Чтобы решить это, вам просто нужно отменить запрос.
Таким образом, ответ на первоначальный вопрос, можете ли вы присоединиться к областям действия с помощью "или" вместо "и", кажется "нет, вы не можете". Но вы можете написать код совершенно другой области или запроса, который выполняет эту работу, или использовать другую среду, отличную от ActiveRecord, например, MetaWhere или Squeel. Не полезно в моем случае
Я 'или' область видимости, сгенерированная pg_search, которая делает немного больше, чем select, она включает в себя порядок ASC, что создает беспорядок чистого объединения. Я хочу "или" это с ручной областью, которая делает вещи, которые я не могу сделать в pg_search. Так что я должен был сделать это так.
Product.find_by_sql("(#{Product.code_starts_with('Tom').to_sql}) union (#{Product.name_starts_with('Tom').to_sql})")
Т.е. превратить области в sql, заключить в скобки каждый из них, объединить их вместе и затем find_by_sql, используя сгенерированный sql. Это немного мусора, но это работает.
Нет, не говорите мне, что я могу использовать "против: [:name,:code]" в pg_search, я бы хотел сделать это так, но поле 'name' - это hstore, которое pg_search не может обработать еще. Таким образом, область действия по имени должна быть создана вручную, а затем объединена с областью действия pg_search.
squeel
gem предоставляет невероятно простой способ сделать это (до этого я использовал что-то вроде метода @coloradoblue):
names = ["Kroger", "Walmart", "Target", "Aldi"]
matching_stores = Grocery.where{name.like_any(names)}
names = ["tim", "tom", "bob", "alex"]
sql_string = names.map { |t| "name = '#{t}'" }.join(" OR ")
@people = People.where(sql_string)