2012-02-02 11 views
5

Sto convertendo un'app Rails dall'utilizzo di act_as_solr a sunspot.Come si crea dinamicamente un blocco di ricerca in sunspot?

L'applicazione utilizza la funzionalità di ricerca nel campo solr che è stato esposto in acts_as_solr. Si potrebbe dare una stringa di query come questa:

title:"The thing to search" 

e sarebbe cercare quella stringa nel campo del titolo.

Nella conversione di macchie solari che sto parsing fuori campo specifiche porzioni della stringa di query e ho bisogno di generare dinamicamente il blocco di ricerca. Qualcosa di simile:

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

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

Ciò è complicato dal anche bisogno di fare durata (secondi, integer) gamme e negazione se la query richiede.

sugli utenti attuali del sistema può cercare qualcosa nel titolo, esclusi i record con qualcos'altro in un altro campo e scoping per durata.

In breve, come si generano questi blocchi dinamicamente?

+0

Solo un pensiero: possiamo creare un blocco in ruby ​​in modo dinamico e passarlo alla funzione di ricerca? –

risposta

1

Ho risolto io stesso. La soluzione che ho usato era compilare gli ambiti richiesti come stringhe, concatenarli e quindi valutarli all'interno del blocco di ricerca.

Ciò ha richiesto un libreria di generatore di query separata che interroga gli indici Solr per garantire che un ambito non è stato creato per un campo indice inesistente.

il codice è molto specifico per il mio progetto, e troppo a lungo per pubblicare per intero, ma questo è quello che faccio:

1. Split i termini di ricerca

questo mi dà una serie di i termini o termini più campi:

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

2. Questa viene passato al generatore di query.

Il generatore converte l'array di ambiti, in base a ciò sono disponibili indici. Questo metodo è un esempio che accetta la classe del modello, il campo e il valore e restituisce l'ambito se il campo è indicizzato.

3. unire tutti gli ambiti

Gli scopi generati sono uniti join("\n") e che è eval ed.

Questo approccio permette all'utente di selezionati i modelli che vogliono ricercare, ed eventualmente per fare campo di ricerca specifico. Il sistema cercherà quindi solo i modelli con campi specificati (o campi comuni), ignorando il resto.

il metodo di verifica se il campo viene indicizzato è:

# 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 

E se qualcuno ha bisogno, un assegno di 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

Recentemente ho fatto questo genere di cose utilizzando instance_eval a valutare i proc (creati altrove) nel contesto del blocco di ricerca di Sunspot.

Il vantaggio è che questi proc possono essere creati ovunque nella tua applicazione ancora puoi scriverli con la stessa sintassi come se fossi all'interno di un blocco di ricerca di macchie solari.

Ecco un rapido esempio per iniziare per il vostro caso particolare:

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 

A proposito, se è necessario, si può anche instance_eval un proc all'interno di un altro proc (nel mio caso ho composto arbitrariamente -nested 'and'/'or' condizioni).

+0

Grazie, è davvero utile. Come collaudi il costruttore? –

+0

@RichardHulse - Ogni caso di test può costruire un elenco di condizioni e passarlo al generatore di query, quindi controllare che i risultati siano come previsto (dato un insieme noto di dati nel database di test) ... È questo il tipo di cosa intendevi? – antinome

+0

Pensavo che fosse più isolato dal fatto che solo testare i procs corretti sono stati compilati, ma spio che ho appena risposto alla mia stessa domanda! –

2

Sunspot fornisce un metodo chiamato Sunspot.new_search che consente di creare le condizioni di ricerca in modo incrementale ed eseguirlo su richiesta.

Un esempio fornito dal 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 questa flessibilità, si dovrebbe essere in grado di costruire la query in modo dinamico. Inoltre, puoi estrarre condizioni comuni a un metodo, ad esempio:

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

search = Sunspot.new_search(Blog) 
search.build(&blog_facets) 
search.execute 
Problemi correlati