Как мне динамически построить поисковый блок в солнечном пятне? - PullRequest
5 голосов
/ 03 февраля 2012

Я преобразую приложение Rails из использования acts_as_solr в солнечное пятно.

Приложение использует возможность поиска по полю в solr, которая была представлена ​​в acts_as_solr. Вы могли бы дать ему строку запроса, подобную этой:

title:"The thing to search"

и будет искать эту строку в поле заголовка.

При преобразовании в солнечное пятно я анализирую специфичные для поля части строки запроса, и мне нужно динамически сгенерировать блок поиска. Примерно так:

Sunspot.search(table_clazz) do
  keywords(first_string, :fields => :title)
  keywords(second_string, :fields => :description)

  ...
  paginate(:page => page, :per_page => per_page)      
end

Это усложняется также необходимостью делать интервалы длительности (секунды, целые числа) и отрицание, если этого требует запрос.

В текущей системе пользователи могут искать что-то в заголовке, исключая записи с чем-то другим в другом поле и просматривая по продолжительности.

В двух словах, как генерировать эти блоки динамически?

Ответы [ 3 ]

4 голосов
/ 18 мая 2012

Я недавно делал подобные вещи, используя instance_eval для оценки процедур (созданных в другом месте) в контексте блока поиска Sunspot.

Преимущество заключается в том, что эти процессы могут быть созданы в любом месте вашего приложения, но вы можете писать их с тем же синтаксисом, как если бы вы были внутри блока поиска солнечных пятен .

Вот краткий пример того, как начать работу с вашим конкретным делом:

def build_sunspot_query(conditions)
  condition_procs = conditions.map{|c| build_condition c}

  Sunspot.search(table_clazz) do
    condition_procs.each{|c| instance_eval &c}

    paginate(:page => page, :per_page => per_page)
  end
end

def build_condition(condition)
  Proc.new do
    # write this code as if it was inside the sunspot search block

    keywords condition['words'], :fields => condition[:field].to_sym
  end
end

conditions = [{words: "tasty pizza", field: "title"},
              {words: "cheap",       field: "description"}]

build_sunspot_query conditions

Кстати, если вам нужно, вы можете даже instance_eval прок внутри другого процесса (в моем случае я сочинил произвольно вложенные условия 'и' / 'или').

2 голосов
/ 06 октября 2015

Sunspot предоставляет метод Sunspot.new_search, который позволяет постепенно создавать условия поиска и выполнять их по требованию.

Пример, предоставленный исходным кодом Sunspot :

search = Sunspot.new_search do
  with(:blog_id, 1)
end
search.build do
  keywords('some keywords')
end
search.build do
  order_by(:published_at, :desc)
end
search.execute

# This is equivalent to:
Sunspot.search do
  with(:blog_id, 1)
  keywords('some keywords')
  order_by(:published_at, :desc)
end

Благодаря такой гибкости вы сможете динамически создавать запрос. Кроме того, вы можете извлечь общие условия для метода, например так:

def blog_facets
  lambda { |s|
    s.facet(:published_year)
    s.facet(:author)
  }
end

search = Sunspot.new_search(Blog)
search.build(&blog_facets)
search.execute
1 голос
/ 16 мая 2012

Я решил это сам. Решение, которое я использовал, состояло в том, чтобы скомпилировать необходимые области в виде строк, объединить их, а затем оценить их внутри блока поиска.

Для этого требовалась отдельная библиотека построителя запросов, которая опрашивает индексы solr, чтобы убедиться, что область не создана для несуществующего поля индекса.

Код очень специфичен для моего проекта и слишком длинный, чтобы публиковать его полностью, но я так и делаю:

1. Разделить условия поиска

это дает мне массив терминов или терминов плюс поля:

['field:term', 'non field terms']

2. Это передается построителю запросов.

Построитель преобразует массив в области на основе доступных индексов. Этот метод является примером, который берет класс модели, поле и значение и возвращает область, если поле проиндексировано.

def convert_text_query_to_search_scope(model_clazz, field, value)
  if field_is_indexed?(model_clazz, field)
    escaped_value = value.gsub(/'/, "\\\\'")
    "keywords('#{escaped_value}', :fields => [:#{field}])"
  else
    ""
  end
end

3. Объедините все возможности

Созданные области объединяются join("\n"), то есть eval ed.

Этот подход позволяет пользователю выбирать модели, которые он хочет найти, и, при желании, выполнять поиск по конкретному полю. Затем система будет выполнять поиск моделей только с указанными полями (или общими полями), игнорируя остальные.

Метод проверки индексации поля:

# based on http://blog.locomotivellc.com/post/6321969631/sunspot-introspection
def field_is_indexed?(model_clazz, field)
  # first part returns an array of all indexed fields - text and other types - plus ':class'
  Sunspot::Setup.for(model_clazz).all_field_factories.map(&:name).include?(field.to_sym)
end

А если кому-то это нужно, проверка на сорбируемость:

def field_is_sortable?(classes_to_check, field)
  if field.present?
    classes_to_check.each do |table_clazz|
      return false if ! Sunspot::Setup.for(table_clazz).field_factories.map(&:name).include?(field.to_sym)
    end
    return true
  end
  false
end
...