Отправка массива значений в 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>
Другие вопросы по тегам