6

I am converting a Rails app from using acts_as_solr to sunspot.

The app uses the field search capability in solr that was exposed in acts_as_solr. You could give it a query string like this:

title:"The thing to search"

and it would search for that string in the title field.

In converting to sunspot I am parsing out field specific portions of the query string and I need to dynamically generate the search block. Something like this:

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

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

This is complicated by also needing to do duration (seconds, integer) ranges and negation if the query requires it.

On the current system users can search for something in the title, excluding records with something else in another field and scoping by duration.

In a nutshell, how do I generate these blocks dynamically?

4

3 回答 3

4

我最近做了这种事情,instance_eval用于在太阳黑子搜索块的上下文中评估 procs(在其他地方创建)。

优点是这些 proc 可以在您的应用程序中的任何位置创建,但您可以使用相同的语法编写它们,就好像您在太阳黑子搜索块中一样。

这是一个快速示例,可帮助您开始处理特定案例:

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

顺便说一句,如果你需要,你甚至可以在另一个 proc 中 instance_eval 一个 proc(在我的例子中,我组成了任意嵌套的 'and'/'or' 条件)。

于 2012-05-18T18:45:45.487 回答
2

Sunspot 提供了一个名为 Sunspot.new_search 的方法,它可以让您逐步构建搜索条件并按需执行。

太阳黑子源代码提供的一个例子:

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
于 2015-10-06T20:35:57.790 回答
1

我自己解决了这个问题。我使用的解决方案是将所需的范围编译为字符串,将它们连接起来,然后在搜索块中对它们进行评估。

这需要一个单独的查询构建器库来询问 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编辑。

这种方法允许用户选择他们想要搜索的模型,并且可以选择进行特定领域的搜索。然后系统将仅搜索具有任何指定字段(或公共字段)的模型,而忽略其余部分。

检查字段是否被索引的方法是:

# 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
于 2012-05-15T22:47:25.790 回答