Отправка массива значений в SQL-запрос в ruby?
Я борюсь за то, что кажется проблемой семантики рубина. Я пишу метод, который принимает переменное число параметров из формы и создает запрос Postgresql.
def self.search(params)
counter = 0
query = ""
params.each do |key,value|
if key =~ /^field[0-9]+$/
query << "name LIKE ? OR "
counter += 1
end
end
query = query[0..-4] #remove extra OR and spacing from last
params_list = []
(1..counter).each do |i|
field = ""
field << '"%#{params[:field'
field << i.to_s
field << ']}%", '
params_list << field
end
last_item = params_list[-1]
last_item = last_item[0..-3] #remove trailing comma and spacing
params_list[-1] = last_item
if params
joins(:ingredients).where(query, params_list)
else
all
end
end
Несмотря на то, что params_list - это массив значений, которые по числу совпадают с именем "LIKE?" части в запросе, я получаю сообщение об ошибке: неверное количество переменных связывания (1 для 2) в: имя LIKE? ИЛИ имя как? Я попытался с params_list в качестве строки, и это тоже не сработало. Я довольно новичок в рубине.
У меня было это работает для 2 параметров с помощью следующего кода, но я хочу, чтобы пользователь мог отправить до 5 (:field1,:field2,:field3 ...)
def self.search(params)
if params
joins(:ingredients).where(['name LIKE ? OR name LIKE ?',
"%#{params[:field1]}%", "%#{params[:field2]}%"]).group(:id)
else
all
end
end
Может ли кто-нибудь пролить свет на то, как я должен программировать это?
2 ответа
PostgreSQL поддерживает стандартные массивы SQL и стандартные any op (...)
синтаксис:
9.23.3.ЛЮБОЙ / НЕКОТОРЫЙ (массив)
expression operator ANY (array expression) expression operator SOME (array expression)
Правая часть - это выражение в скобках, которое должно давать значение массива. Левое выражение оценивается и сравнивается с каждым элементом массива с использованием данного оператора, который должен давать логический результат. Результат
ANY
"true", если получен какой-либо истинный результат. Результатом является "ложь", если истинный результат не найден (включая случай, когда массив имеет нулевые элементы).
Это означает, что вы можете построить SQL следующим образом:
where name ilike any (array['%Richard%', '%Feynman%'])
Это мило и лаконично, так как же нам заставить Rails это построить? Это на самом деле довольно просто:
Model.where('name ilike any (array[?])', names.map { |s| "%#{s}%" })
Не требуется ручное цитирование, ActiveRecord преобразует массив в правильно цитируемый / экранированный список, когда он заполняет ?
заполнитель в.
Теперь вам просто нужно построить names
массив. Что-то простое, как это должно сделать:
fields = params.keys.select { |k| k.to_s =~ /\Afield\d+\z/ }
names = params.values_at(*fields).select(&:present)
Вы также можете конвертировать один 'a b'
входы в 'a', 'b'
бросая split
а также flatten
в смесь:
names = params.values_at(*fields)
.select(&:present)
.map(&:split)
.flatten
Вы можете достичь этого легко:
def self.search(string)
terms = string.split(' ') # split the string on each space
conditions = terms.map{ |term| "name ILIKE #{sanitize("'%#{term}%'")}" }.join(' OR ')
return self.where(conditions)
end
Это должно быть гибким: независимо от количества терминов в вашей строке, он должен возвращать объект, соответствующий хотя бы одному из терминов.
Объяснение:
Условие использует "ILIKE
"не"LIKE
":
- "
ILIKE
"нечувствителен к регистру - "
LIKE
"чувствителен к регистру.
Цель sanitize("'%#{term}%'")
часть заключается в следующем:
sanitize()
предотвратит SQL-инъекции, такие как сдача '; DROP TABLE пользователи;' в качестве входа для поиска.
Использование:
User.search('Michael Mich Mickey')
# can return
<User: Michael>
<User: Juan-Michael>
<User: Jean michel>
<User: MickeyMouse>