In questo articolo mostrerò come implementare la ricerca full-text utilizzando Ruby on Rails e Elasticsearch. Tutti vengono utilizzati al giorno d'oggi per inserire un termine di ricerca e ottenere suggerimenti e risultati con il termine di ricerca evidenziato. Se sbagliate l'ortografia di quello che state cercando, l'auto-correzione è anche una bella funzionalità, come possiamo vedere su siti Web come Google o Facebook.
Implementare tutte queste funzionalità utilizzando solo un database relazionale come MySQL o Postgres non è semplice. Per questo motivo, utilizziamo Elasticsearch, che puoi considerare come un database appositamente creato e ottimizzato per la ricerca. È open source ed è costruito su Apache Lucene.
Una delle più belle funzionalità di Elasticsearch è che espone le sue funzionalità utilizzando l'API REST, quindi ci sono librerie che includono questa funzionalità per la maggior parte dei linguaggi di programmazione.
In precedenza, ho menzionato che Elasticsearch è come un database per la ricerca. Sarebbe utile se hai familiarità con alcuni dei termini che lo circondano.
Una cosa da notare qui è che in Elasticsearch, quando si scrive un documento in un indice, i campi del documento vengono analizzati, parola per parola, per rendere la ricerca facile e veloce. Elasticsearch supporta anche la geolocalizzazione, quindi puoi cercare documenti che si trovano entro una certa distanza da una determinata posizione. Questo è esattamente il modo in cui Foursquare implementa la ricerca.
Vorrei ricordare che Elasticsearch è stato progettato tenendo presente l'alta scalabilità, quindi è molto semplice creare un cluster con più server e disporre di un'elevata disponibilità anche se alcuni server non funzionano. Non tratterò le specifiche di come pianificare e distribuire diversi tipi di cluster in questo articolo.
Se stai usando Linux, è possibile installare Elasticsearch da uno dei repository. È disponibile in APT e YUM.
Se usi Mac, puoi installarlo usando Homebrew: preparare installare elasticsearch
. Dopo aver installato elasticsearch, vedrai l'elenco delle cartelle rilevanti nel tuo terminale:
Per verificare che l'installazione funzioni, digita elasticsearch
nel tuo terminale per avviarlo. Quindi corri arricciamento localhost: 9200
nel tuo terminale e dovresti vedere qualcosa del tipo:
Elastic HQ è un plug-in di monitoraggio che possiamo utilizzare per gestire Elasticsearch dal browser, in modo simile a phpMyAdmin per MySQL. Per installarlo, esegui semplicemente il tuo terminale:
/usr/local/Cellar/elasticsearch/2.2.0_1/libexec/bin/plugin -install royrusso / elasticsearch-HQ
Una volta installato, accedere a http: // localhost: 9200 / _plugin / hq nel browser:
Clicca su Collegare e vedrai una schermata che mostra lo stato del cluster:
Al momento, come puoi immaginare, non sono ancora stati creati indici o documenti, ma abbiamo installato ed eseguito la nostra istanza locale di Elasticsearch.
Creerò un'applicazione Rails molto semplice, in cui è possibile aggiungere articoli al database in modo da poter eseguire una ricerca full-text su di essi utilizzando Elasticsearch. Inizia creando una nuova applicazione Rails:
rotaie nuove rotaie elasticsearch
Successivamente generiamo una nuova risorsa articolo con scaffolding:
rails genera scaffold Titolo dell'articolo: testo stringa: testo
Ora dobbiamo aggiungere una nuova route root, in modo che possiamo vedere per default l'elenco degli articoli. modificare config / routes.rb:
Rails.application.routes.draw fa la root a: 'articoli # index' risorse: articoli fine
Creare il database eseguendo il comando rake db: migrate
. Se inizi rails server
, apri il tuo browser, vai a localhost: 3000 e aggiungi alcuni articoli al database, oppure scarica semplicemente il file db / seeds.rb con i dati fittizi che ho creato in modo da non dover perdere molto tempo a compilare i moduli.
Ora che abbiamo la nostra piccola app Rails con articoli nel database, siamo pronti ad aggiungere la nostra funzionalità di ricerca. Inizieremo aggiungendo il riferimento ad entrambe le gemme Elasticsearch ufficiali:
gemma 'elasticsearch-model' gemma 'elasticsearch-rails'
Su molti siti web, è molto comune avere una casella di testo per la ricerca nel menu in alto su tutte le pagine. Per questo motivo, creerò un modulo parziale su app / views / ricerca / _form.html.erb.Come puoi vedere, sto inviando il modulo utilizzando GET, quindi è facile copiare e incollare l'URL per una ricerca specifica.
<%= form_for :term, url: search_path, method: :get do |form| %><%= text_field_tag :term, params[:term] %> <%= submit_tag "Search", name: nil %>
<% end %>
Aggiungi un riferimento al modulo al layout del sito web principale. modificare app / views / layout / application.html.erb.
<%= render 'search/form' %> <%= yield %>
Ora abbiamo anche bisogno di un controller per eseguire la ricerca effettiva e visualizzare i risultati, quindi lo generiamo eseguendo il comando rails g new controller Search
.
class SearchController < ApplicationController def search if params[:term].nil? @articles = [] else @articles = Article.search params[:term] end end end
Come puoi vedere, sto chiamando il metodo ricerca
sul modello dell'articolo. Non l'abbiamo ancora definito, quindi se proviamo a eseguire una ricerca a questo punto, riceviamo un errore. Inoltre, non abbiamo aggiunto un percorso per SearchController su config / routes.rb file, quindi facciamolo così:
Rails.application.routes.draw effettua il root su: risorse 'articles # index': gli articoli ottengono "search", a: "search # search" end
Se guardiamo la documentazione per la gemma 'elasticsearch-rails', abbiamo bisogno di includere due moduli sui modelli che vogliamo essere indicizzati in Elasticsearch, nel nostro caso Article.rb.
richiedere l'articolo di classe "elasticsearch / model" < ActiveRecord::Base include Elasticsearch::Model include Elasticsearch::Model::Callbacks end
Il primo modello inietta il metodo di ricerca che stavamo usando nel nostro precedente controller, tra gli altri. Il secondo modulo si integra con i callback di ActiveRecord per indicizzare ogni istanza di un articolo che salviamo nel database e aggiorna anche l'indice se modifichiamo o cancelliamo l'articolo dal database. Quindi è tutto trasparente per noi.
Se hai precedentemente importato i dati nel database, questi articoli non sono ancora nell'indice Elasticsearch; solo quelli nuovi sono indicizzati automaticamente. Per questo motivo, dobbiamo indicizzarli manualmente, ed è facile iniziare console di rotaie
. Quindi dobbiamo solo correre irb (main)> Article.import
.
Ora siamo pronti per provare la funzionalità di ricerca. Se digito "ruby" e clicco su search, ecco i risultati:
Su molti siti Web, è possibile visualizzare nella pagina dei risultati di ricerca come viene evidenziato il termine cercato. Questo è molto facile da fare usando Elasticsearch.
modificare app / modelli / article.rb e modifica il metodo di ricerca predefinito:
def self.search (query) __elasticsearch __. search (query: multi_match: query: query, fields: ['title', 'text'], evidenziare: pre_tags: [''], post_tags: [''], campi: titolo: , testo: ) fine
Di default, il ricerca
il metodo è definito dalla gem 'elasticsearch-models' e l'oggetto proxy __elasticsearch__ viene fornito per accedere alla classe wrapper per l'API Elasticsearch. Quindi possiamo modificare la query predefinita usando le opzioni JSON standard fornite dalla documentazione.
Ora il metodo di ricerca comprenderà i risultati che corrispondono alla query con i tag HTML specificati. Per questo motivo, abbiamo anche bisogno di aggiornare la pagina dei risultati di ricerca in modo che possiamo rendere sicuri i tag HTML. Per fare ciò, modifica app / views / ricerca / search.html.erb.
risultati di ricerca
<% if @articles %>
<%= snippet.html_safe %>...
<% end %> <% end %>La tua ricerca non corrisponde a nessun documento.
<% end %>Aggiungi uno stile CSS a app / beni / fogli di stile / search.scss, per il tag evidenziato:
.search_results em background-color: yellow; stile font: normale; font-weight: bold;
Prova a cercare di nuovo "rubino":
Come puoi vedere, è facile evidenziare il termine di ricerca, ma non è l'ideale, poiché abbiamo bisogno di inviare una query JSON come specificato dalla documentazione di Elasticsearch, e non abbiamo alcun tipo di astrazione.
Searchkick gem è fornito da Instacart ed è un'astrazione in cima alle gemme ufficiali di Elasticsearch. Ho intenzione di ridefinire la funzionalità di evidenziazione, quindi iniziamo aggiungendo gemma 'searchkick'
al file gemma. La prima classe che dobbiamo cambiare è il modello Article.rb:
articolo di classe < ActiveRecord::Base searchkick end
Come puoi vedere, è molto più semplice. Abbiamo bisogno di reindicizzare nuovamente gli articoli ed eseguire il comando rastrello searchkick: reindex CLASS = Articolo
. Per evidenziare il termine di ricerca, è necessario passare un parametro aggiuntivo al metodo di ricerca dal nostro search_controller.rb.
class SearchController < ApplicationController def search if params[:term].nil? @articles = [] else term = params[:term] @articles = Article.search term, fields: [:text], highlight: true end end end
L'ultimo file che dobbiamo modificare è views / search / search.html.erb come i risultati vengono restituiti in un formato diverso da searchkick ora:
Cerca risultati per: <%= params[:term] %>
<% if @articles %>
<%= details[:highlight][:text].html_safe %>...
La tua ricerca non corrisponde a nessun documento.
<% end %>Ora è il momento di eseguire nuovamente l'applicazione e testare la funzionalità di ricerca:
Si noti che ho inserito come termine di ricerca "dato". L'ho fatto apposta per mostrarti che per impostazione predefinita searchkickè impostato per analizzare il testo indicizzato ed essere più permissivo con errori di ortografia.
Autosuggest o typeahead predice ciò che un utente digiterà, rendendo l'esperienza di ricerca più veloce e più semplice. Tieni presente che, a meno che tu non abbia migliaia di record, potrebbe essere meglio filtrare dal lato client.
Iniziamo aggiungendo il plugin typeahead, che è disponibile attraverso il gem 'bootstrap-typeahead-rails'
, e aggiungilo al tuo Gemfile. Successivamente, dobbiamo aggiungere qualche JavaScript a app / Attività / javascripts / application.js in modo che quando inizi a digitare nella casella di ricerca, vengono visualizzati alcuni suggerimenti.
// = richiede jquery // = richiede jquery_ujs // = richiede turbolinks // = richiede bootstrap-typeahead-rails // = require_tree. var ready = function () var engine = new Bloodhound (datumTokenizer: function (d) console.log (d); return Bloodhound.tokenizers.whitespace (d.title); queryTokenizer: Bloodhound.tokenizers.whitespace, remote: url: '... / search / typeahead /% QUERY'); var promise = engine.initialize (); promise .done (function () console.log ('success');) .fail (function () console.log ('error')); $ ("# term"). typeahead (null, name: "article", displayKey: "title", source: engine.ttAdapter ()); $ (Document) .ready (pronto); $ (document) .on ('page: load', ready);
Alcuni commenti sullo snippet precedente. Nelle ultime due righe, perché non ho disabilitato i turbolinks, questo è il modo per collegare il codice che voglio eseguire al caricamento della pagina. Nella prima parte della sceneggiatura, puoi vedere che sto usando Bloodhound. È il motore di suggerimento typeahead.js e sto anche impostando l'endpoint JSON per rendere le richieste AJAX per ottenere i suggerimenti. Dopo, chiamo inizializzare()
sul motore, e ho impostato typeahead sul campo di testo di ricerca usando il suo id "term".
Ora, abbiamo bisogno di fare l'implementazione del backend per i suggerimenti, iniziamo aggiungendo il percorso, modifica app / config / routes.rb.
Rails.application.routes.draw fa root a: 'articoli # index' risorse: gli articoli ottengono "cerca", a: "cerca # cerca" ottieni 'cerca / typeahead /: term' => 'cerca # typeahead' fine
Successivamente, aggiungerò l'implementazione app / controllers / search_controller.rb.
def typeahead render json: Article.search (params [: term], fields: ["title"], limit: 10, load: false, errori di ortografia: below: 5,). map do | article | title: article.title, value: article.id end end
Questo metodo restituisce i risultati della ricerca per il termine inserito utilizzando JSON. Sto solo cercando per titolo, ma potrei specificare anche il corpo dell'articolo. Sto anche limitando il numero di risultati di ricerca a 10 al massimo.
Ora siamo pronti per provare l'implementazione typeahead:
Come puoi vedere, l'uso di Elasticsearch con Rails rende la ricerca dei nostri dati davvero facile e molto veloce. Qui vi ho mostrato come usare le gemme di basso livello fornite da Elasticsearch, così come la gemma Searchkick, che è un'astrazione che nasconde alcuni dettagli di come Elasticsearch funziona.
A seconda delle tue esigenze specifiche, potresti essere felice di utilizzare Searchkick e ottenere la tua ricerca full-text implementata rapidamente e facilmente. D'altra parte, se hai altre query complesse che includono filtri o gruppi, potresti dover imparare di più sui dettagli del linguaggio di query su Elasticsearch e finire con l'uso dei modelli di elasticsearch gemelli di livello inferiore e di elasticsearch- Rails'.