2012-02-02 68 views
5

我正在将使用acts_as_solr的Rails应用程序转换为太阳黑子。如何在太阳黑子中动态构建搜索块?

该应用程序使用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 

这是通过也需要做持续时间(秒,整数)范围和否定,如果查询需要变得复杂。

在当前系统上,用户可以在标题中搜索某些内容,但不包括其他字段中的其他字段的记录以及按持续时间确定范围。

简而言之,如何动态生成这些块?

+0

只是一个想法:我们可以在红宝石创建块动态,并把它传递给搜索功能? – 2012-03-12 08:00:18

回答

1

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

这需要一个单独的查询构建器库,用于询问solr索引以确保不为不存在的索引字段创建范围。

的代码是非常具体的,以我的项目,而过长全面发布,但是这是我做的:

1斯普利特搜索字词

这给了我一个数组条款或条款加场:

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

2.这是传递给查询生成器。

构建器根据可用的索引将数组转换为作用域。此方法是一个示例,它接受模型类,字段和值,并在字段被索引时返回范围。

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 

如果有人需要它,因为sortability检查:

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 
4

最近我使用instance_eval到这种事情在太阳黑子搜索块的上下文中评估procs(在别处创建)。

的优点是,这些特效可以在任何地方在你的应用程序创建尚未你可以用相同的语法,如果你是一个太阳黑子搜索块里面写他们。

这里有一个简单的例子,让你开始为你的具体情况:

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的另一个PROC内一个进程(在我的情况下,我由任意-nested“和”/“或”条件)。

+0

谢谢,这真的很有用。你如何测试生成器? – 2012-05-18 20:32:02

+0

@RichardHulse - 每个测试用例都可以构造一个条件列表并将其传递给查询构建器,然后检查结果是否如预期的那样(给定测试数据库中已知的一组数据)......是那种类型的东西你的意思是? – antinome 2012-05-22 17:00:41

+0

我在想更多的是孤立的,只是测试正确的特效是编译好的,但我说我只是回答我自己的问题! – 2012-05-22 20:17:17

2

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

Sunspot's source code提供的一个例子:

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 

这种灵活性,你应该能够动态地构建查询。此外,您还可以提取共同条件的方法,像这样:

​​