Im пытается реплицировать стиль списка поиска crunchbase, используя ruby on rails. У меня есть набор фильтров, который выглядит примерно так:
[
   {
      "id":"0",
      "className":"Company",
      "field":"name",
      "operator":"starts with",
      "val":"a"
   },
   {
      "id":"1",
      "className":"Company",
      "field":"hq_city",
      "operator":"equals",
      "val":"Karachi"
   },
   {
      "id":"2",
      "className":"Category",
      "field":"name",
      "operator":"does not include",
      "val":"ECommerce"
   }
]
Я отправляю эту строку json в свой рубиновый контроллер, где я реализовал эту логику:
filters = params[:q]
table_names = {}
filters.each do |filter|
    filter = filters[filter]
    className = filter["className"]
    fieldName = filter["field"]
    operator = filter["operator"]
    val = filter["val"]
    if table_names[className].blank? 
        table_names[className] = []
    end
    table_names[className].push({
        fieldName: fieldName,
        operator: operator,
        val: val
    })
end
table_names.each do |k, v|
    i = 0
    where_string = ''
    val_hash = {}
    v.each do |field|
        if i > 0
            where_string += ' AND '
        end
        where_string += "#{field[:fieldName]} = :#{field[:fieldName]}"
        val_hash[field[:fieldName].to_sym] = field[:val]
        i += 1
    end
    className = k.constantize
    puts className.where(where_string, val_hash)
end
Что я делаю, я петлю над массивом json и создаю хэш с ключами как имена таблиц, а значения - это массив с именем столбца, оператором и значением для применения этого оператора. Поэтому после создания тэга table_names у меня было бы что-то подобное:
{
   'Company':[
      {
         fieldName:'name',
         operator:'starts with',
         val:'a'
      },
      {
         fieldName:'hq_city',
         operator:'equals',
         val:'karachi'
      }
   ],
   'Category':[
      {
         fieldName:'name',
         operator:'does not include',
         val:'ECommerce'
      }
   ]
}
Теперь я перебираю хэш таблицы table_names и создаю запрос where с использованием синтаксиса Model.where("column_name = :column_name", {column_name: 'abcd'}).
Итак, я бы создал два запроса:
SELECT "companies".* FROM "companies" WHERE (name = 'a' AND hq_city = 'b')
SELECT "categories".* FROM "categories" WHERE (name = 'c')
Теперь у меня есть две проблемы:
1. Операторы:
У меня есть много операторов, которые можно применить к столбцу типа "начинается с", "заканчивается", "равно", "не равно", "включает", "не включает", "больше", 'меньше, чем'. Я предполагаю, что лучшим способом было бы сделать случай переключения на операторе и использовать соответствующий символ при построении строки where. Например, если оператор "начинает с", я бы сделал что-то вроде where_string += "#{field[:fieldName]} like %:#{field[:fieldName]}", а также для других.
Итак, этот подход правильный, и этот тип подстановочного синтаксиса разрешен в этом типе .where?
2. Более 1 таблицы
Как вы видели, мой подход создает 2 запроса для более чем 2 таблиц. Мне не нужны 2 запроса, мне нужно, чтобы имя категории было в том же запросе, где категория принадлежит компании.
Теперь, что я хочу сделать, мне нужно создать такой запрос:
Company.joins(:categories).where("name = :name and hq_city = :hq_city and categories.name = :categories[name]", {name: 'a', hq_city: 'Karachi', categories: {name: 'ECommerce'}})
Но это не так. Поиск может стать очень сложным. Например:
Компания имеет много FundingRound. FundingRound может иметь много инвестиций и инвестиций, которые могут иметь много IndividualInvestor. Поэтому я могу выбрать создать фильтр, например:
{
  "id":"0",
  "className":"IndividualInvestor",
  "field":"first_name",
  "operator":"starts with",
  "val":"za"
} 
Мой подход создаст такой запрос:
SELECT "individual_investors".* FROM "individual_investors" WHERE (first_name like %za%)
Этот запрос неверен. Я хочу обратиться к отдельным инвесторам с просьбами об инвестициях в раунд финансирования компании. Который много соединяет таблицы.
Подход, который я использовал, применим к одной модели и не может решить проблему, о которой я говорил выше.
Как я могу решить эту проблему?