2012-02-02 11 views
5

Estoy convirtiendo una aplicación Rails de usar acts_as_solr a manchas solares.¿Cómo creo dinámicamente un bloque de búsqueda en manchas solares?

La aplicación utiliza la capacidad de búsqueda de campo en solr que se expuso en acts_as_solr. Podría darle una cadena de consulta como esta:

title:"The thing to search" 

y buscaría esa cadena en el campo del título.

Al convertir a la mancha solar, estoy analizando porciones específicas de campo de la cadena de consulta y necesito generar dinámicamente el bloque de búsqueda. Algo como esto:

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

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

Esto se complica por también tener que hacer (segundos), número entero rangos de duración y la negación si la consulta requiere.

En el sistema actual, los usuarios pueden buscar algo en el título, excluyendo los registros con otra cosa en otro campo y el alcance por duración.

En pocas palabras, ¿cómo se generan estos bloques dinámicamente?

+0

Solo un pensamiento: ¿podemos crear un bloque en ruby ​​dinámicamente y pasarlo a la función de búsqueda? –

Respuesta

1

Lo he resuelto yo mismo. La solución que utilicé fue compilar los ámbitos requeridos como cadenas, concatenarlos y luego evaluarlos dentro del bloque de búsqueda.

Esto requirió una biblioteca de generador de consultas separada que interroga los índices de solr para garantizar que no se crea un alcance para un campo de índice no existente.

El código es muy específico para mi proyecto, y demasiado largo para publicar en su totalidad, pero esto es lo que hago:

1. Dividir los términos de búsqueda

esto me da una gran variedad de los términos o términos más campos:

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

2. Esto se pasa al generador de consultas.

El constructor convierte la matriz en ámbitos, según los índices disponibles. Este método es un ejemplo que toma la clase de modelo, el campo y el valor, y devuelve el alcance si el campo está indexado.

3. Comunicar todos los ámbitos

Los alcances generados se unen join("\n") y que es eval ed.

Este enfoque permite al usuario seleccionar los modelos que desea buscar y, opcionalmente, realizar búsquedas específicas de campo. El sistema solo buscará los modelos con los campos especificados (o campos comunes), ignorando el resto.

El método para comprobar si el campo está indexado es:

# 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 

Y si alguien lo necesita, un cheque por 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

poco hice este tipo de cosas usando instance_eval a evaluar procs (creados en otro lugar) en el contexto del bloque de búsqueda de manchas solares.

La ventaja es que estos procs se pueden crear en cualquier lugar de su aplicación; sin embargo, puede escribirlos con la misma sintaxis que si estuviera dentro de un bloque de búsqueda de manchas solares.

Aquí está un ejemplo rápido para que pueda empezar para su caso particular:

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 

Por cierto, si es necesario, incluso se puede instance_eval un proc dentro de otro proc (en mi caso compuesta de forma arbitraria -nested 'y'/'o' condiciones).

+0

Gracias, eso es realmente útil. ¿Cómo se prueba el constructor? –

+0

@RichardHulse: cada caso de prueba podría construir una lista de condiciones y pasarla al generador de consultas, luego verificar que los resultados sean los esperados (dado un conjunto conocido de datos en la base de datos de prueba) ... ¿Es ese el tipo de cosas? ¿te referías? – antinome

+0

Estaba pensando más aislado que eso, solo estoy probando los procesos correctos, ¡pero supongo que acabo de responder mi propia pregunta! –

2

Sunspot proporciona un método llamado Sunspot.new_search que le permite crear las condiciones de búsqueda de forma incremental y ejecutarlo bajo demanda.

Un ejemplo proporcionado por el 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 

Con esta flexibilidad, usted debería ser capaz de construir su consulta de forma dinámica. Además, puede extraer las condiciones comunes de un método, así:

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

search = Sunspot.new_search(Blog) 
search.build(&blog_facets) 
search.execute 
Cuestiones relacionadas