En este artículo, le mostraré cómo implementar la búsqueda de texto completo utilizando Ruby on Rails and Elasticsearch. Hoy en día, todo el mundo se usa para ingresar un término de búsqueda y obtener sugerencias y resultados con el término de búsqueda resaltado. Si escribe mal lo que está tratando de buscar, tener una autocorrección también es una buena característica, como podemos ver en sitios web como Google o Facebook.
Implementar todas estas funciones utilizando solo una base de datos relacional como MySQL o Postgres no es sencillo. Por este motivo, estamos utilizando Elasticsearch, que puede considerarse como una base de datos específicamente creada y optimizada para la búsqueda. Es de código abierto y está construido sobre Apache Lucene..
Una de las mejores características de Elasticsearch es que expone su funcionalidad mediante la API REST, por lo que hay bibliotecas que integran esa funcionalidad para la mayoría de los lenguajes de programación..
Anteriormente, mencioné que Elasticsearch es como una base de datos para la búsqueda. Sería útil si está familiarizado con la terminología que la rodea..
Una cosa a tener en cuenta aquí es que en Elasticsearch, cuando escribe un documento en un índice, los campos del documento se analizan, palabra por palabra, para que la búsqueda sea fácil y rápida. Elasticsearch también admite la geolocalización, por lo que puede buscar documentos que se encuentran dentro de una cierta distancia de una ubicación determinada. Así es exactamente como Foursquare implementa la búsqueda..
Me gustaría mencionar que Elasticsearch se creó teniendo en cuenta una gran escalabilidad, por lo que es muy fácil crear un clúster con varios servidores y tener una alta disponibilidad, incluso si algunos servidores se caen. No voy a cubrir los detalles de cómo planificar y desplegar diferentes tipos de clústeres en este artículo.
Si está utilizando Linux, posiblemente pueda instalar Elasticsearch desde uno de los repositorios. Está disponible en APT y YUM..
Si usas Mac, puedes instalarlo usando Homebrew: brew install elasticsearch
. Después de instalar elasticsearch, verá la lista de carpetas relevantes en su terminal:
Para verificar que la instalación está funcionando, escriba elasticsearch
en su terminal para iniciarlo. Entonces corre curl localhost: 9200
en tu terminal, y deberías ver algo como:
Elastic HQ es un complemento de monitoreo que podemos usar para administrar Elasticsearch desde el navegador, similar a phpMyAdmin para MySQL. Para instalarlo, simplemente ejecuta en tu terminal:
/usr/local/Cellar/elasticsearch/2.2.0_1/libexec/bin/plugin -install royrusso / elasticsearch-HQ
Una vez instalado, navegue a http: // localhost: 9200 / _plugin / hq en su navegador:
Haga clic en Conectar y verá una pantalla que muestra el estado del clúster:
En este momento, como es de esperar, aún no se han creado índices o documentos, pero tenemos nuestra instancia local de Elasticsearch instalada y en ejecución.
Voy a crear una aplicación Rails muy simple, donde puedes agregar artículos a la base de datos para que podamos realizar una búsqueda de texto completo en ellos usando Elasticsearch. Comience creando una nueva aplicación de Rails:
nuevos carriles elasticsearch-rails
A continuación generamos un nuevo recurso de artículo con andamios:
rieles generan andamio Título del artículo: texto de la cadena: texto
Ahora necesitamos agregar una nueva ruta raíz, para que podamos ver por defecto la lista de artículos. Editar config / route.rb:
Rails.application.routes.draw do root to: 'articles # index' resources: articles end
Crea la base de datos ejecutando el comando rastrillo db: migrar
. Si empiezas servidor de rieles
, abra su navegador, navegue a localhost: 3000 y agregue algunos artículos a la base de datos, o simplemente descargue el archivo db / seeds.rb con los datos ficticios que he creado para que no tenga que perder mucho tiempo rellenando formularios.
Ahora que tenemos nuestra pequeña aplicación Rails con artículos en la base de datos, estamos listos para agregar nuestra funcionalidad de búsqueda. Vamos a comenzar agregando la referencia a ambas gemas oficiales de Elasticsearch:
gema 'elasticsearch-model' gema 'elasticsearch-rails'
En muchos sitios web, es muy común tener un cuadro de texto para buscar en el menú superior en todas las páginas. Por esa razón, voy a crear un formulario parcial en app / views / search / _form.html.erb.Como puede ver, estoy enviando el formulario con GET, por lo que es fácil copiar y pegar la URL para una búsqueda específica.
<%= form_for :term, url: search_path, method: :get do |form| %><%= text_field_tag :term, params[:term] %> <%= submit_tag "Search", name: nil %>
<% end %>
Agregue una referencia al formulario al diseño del sitio web principal. Editar app / views / layouts / application.html.erb.
<%= render 'search/form' %> <%= yield %>
Ahora también necesitamos un controlador para realizar la búsqueda real y mostrar los resultados, por lo que lo generamos ejecutando el comando rieles g nuevo controlador Buscar
.
clase SearchController < ApplicationController def search if params[:term].nil? @articles = [] else @articles = Article.search params[:term] end end end
Como puedes ver, estoy llamando al método. buscar
en el modelo del artículo. Aún no lo hemos definido, así que si intentamos realizar una búsqueda en este punto, obtenemos un error. Además, no hemos agregado una ruta para el SearchController en el config / route.rb archivo, así que vamos a hacerlo:
Rails.application.routes.draw do root to: 'articles # index' resources: los artículos obtienen "search", to: "search # search" end
Si nos fijamos en la documentación de la gema. 'elasticsearch-rails', Necesitamos incluir dos módulos en los modelos que queremos que sean indexados en Elasticsearch, en nuestro caso Article.rb.
Requiere clase 'elasticsearch / model' Artículo < ActiveRecord::Base include Elasticsearch::Model include Elasticsearch::Model::Callbacks end
El primer modelo inyecta el método de búsqueda que estábamos usando en nuestro controlador anterior entre otros. El segundo módulo se integra con las devoluciones de llamada de ActiveRecord para indexar cada instancia de un artículo que guardamos en la base de datos, y también actualiza el índice si modificamos o eliminamos el artículo de la base de datos. Así que todo es transparente para nosotros..
Si importó los datos a la base de datos anteriormente, esos artículos aún no están en el índice de Elasticsearch; Solo los nuevos son indexados automáticamente. Por esta razón, tenemos que indexarlos manualmente, y es fácil si empezamos consola de rieles
. Entonces solo tenemos que correr. irb (main)> Article.import
.
Ahora estamos listos para probar la funcionalidad de búsqueda. Si escribo 'ruby' y hago clic en buscar, aquí están los resultados:
En muchos sitios web, puede ver en la página de resultados de búsqueda cómo se resalta el término que buscó. Esto es muy fácil de hacer usando Elasticsearch.
Editar app / models / article.rb y modifique el método de búsqueda por defecto:
def self.search (consulta) __elasticsearch __. search (query: multi_match: query: query, fields: ['title', 'text'], resaltado: pre_tags: [''], post_tags: [''], campos: título: , texto: ) fin
Por defecto, el buscar
El método está definido por la gema 'elasticsearch-models', y el objeto proxy __elasticsearch__ se proporciona para acceder a la clase de envoltorio para la API Elasticsearch. Por lo tanto, podemos modificar la consulta predeterminada utilizando las opciones JSON estándar tal como lo proporciona la documentación..
Ahora el método de búsqueda ajustará los resultados que coincidan con la consulta con las etiquetas HTML especificadas. Por este motivo, también debemos actualizar la página de resultados de búsqueda para poder representar las etiquetas HTML de forma segura. Para ello, edita. app / views / search / search.html.erb.
Resultados de la búsqueda
<% if @articles %>
<%= snippet.html_safe %>...
<% end %> <% end %>Su búsqueda no coincide con ningún documento..
<% end %>Agrega un estilo CSS a app / asset / stylesheets / search.scss, para la etiqueta resaltada:
.search_results em background-color: yellow; estilo de letra: normal; font-weight: negrita;
Intenta buscar 'ruby' otra vez:
Como puede ver, es fácil resaltar el término de búsqueda, pero no es lo ideal, ya que necesitamos enviar una consulta JSON como lo especifica la documentación de Elasticsearch, y no tenemos ningún tipo de abstracción.
La gema Searchkick es proporcionada por Instacart, y es una abstracción sobre las gemas oficiales de Elasticsearch. Voy a refactorizar la funcionalidad de resaltado, así que comenzamos agregando joya 'searchkick'
al gemfile. La primera clase que necesitamos cambiar es el modelo Article.rb:
clase de artículo < ActiveRecord::Base searchkick end
Como puedes ver, es mucho más simple. Necesitamos reindexar los artículos nuevamente, y ejecutar el comando rake searchkick: reindex CLASS = Artículo
. Para resaltar el término de búsqueda, debemos pasar un parámetro adicional al método de búsqueda de nuestro search_controller.rb.
clase SearchController < ApplicationController def search if params[:term].nil? @articles = [] else term = params[:term] @articles = Article.search term, fields: [:text], highlight: true end end end
El último archivo que necesitamos modificar es views / search / search.html.erb como los resultados se devuelven en un formato diferente con searchkick ahora:
Resultados de la búsqueda para: <%= params[:term] %>
<% if @articles %>
<%= details[:highlight][:text].html_safe %>...
Su búsqueda no coincide con ningún documento..
<% end %>Ahora es el momento de volver a ejecutar la aplicación y probar la funcionalidad de búsqueda:
Note que ingresé como un término de búsqueda 'dato'. Hice esto a propósito para mostrarte que, por defecto, Searchkickestá configurado para analizar el texto indexado y ser más permisivo con las faltas de ortografía.
La sugerencia automática o automática predice lo que un usuario escribirá, lo que hace que la experiencia de búsqueda sea más rápida y fácil. Tenga en cuenta que a menos que tenga miles de registros, podría ser mejor filtrar en el lado del cliente.
Comencemos agregando el complemento typeahead, que está disponible a través de gema 'bootstrap-typeahead-rails'
, y agregarlo a tu Gemfile. A continuación, necesitamos agregar un poco de JavaScript a app / asset / javascripts / application.js para que cuando empieces a escribir en el cuadro de búsqueda, aparezcan algunas sugerencias..
// = require jquery // = require jquery_ujs // = require turbolinks // = require bootstrap-typeahead-rails // = require_tree. var ready = function () var engine = new Bloodhound (datumTokenizer: function (d) console.log (d); devolver Bloodhound.tokenizers.whitespace (d.title);, queryTokenizer: Bloodhound.tokenizers.whitespace, remoto: url: '... / search / typeahead /% QUERY'); promesa de var = engine.initialize (); promesa .done (function () console.log ('éxito');) .fail (function () console.log ('error')); $ ("# term"). typeahead (null, name: "article", displayKey: "title", source: engine.ttAdapter ()); $ (documento) .ready (listo); $ (documento) .on ('página: cargar', listo);
Algunos comentarios sobre el fragmento anterior. En las dos últimas líneas, debido a que no he deshabilitado los turbolinks, esa es la forma de conectar el código que quiero ejecutar en la carga de la página. En la primera parte de la secuencia de comandos, puedes ver que estoy usando Bloodhound. Es el motor de sugerencias typeahead.js, y también estoy configurando el punto final JSON para que las solicitudes AJAX obtengan las sugerencias. Despues llamo inicializar()
en el motor, y configuré typeahead en el campo de búsqueda de texto usando su id "término".
Ahora, necesitamos hacer la implementación de back-end para las sugerencias, comencemos por agregar la ruta, editar app / config / route.rb.
Rails.application.routes.draw do root to: 'articles # index' resources: articles get "search", to: "search # search" get 'search / typeahead /: term' => 'search # typeahead' end
A continuación, voy a agregar la implementación en app / controllers / search_controller.rb.
def typeahead render json: Article.search (params [: term], fields: ["title"], límite: 10, load: false, faltas de ortografía: abajo: 5,). map do | article | title: article.title, value: article.id end end end
Este método está devolviendo los resultados de búsqueda para el término ingresado usando JSON. Solo busco por título, pero también puedo especificar el cuerpo del artículo. También estoy limitando el número de resultados de búsqueda a 10 máximo.
Ahora estamos listos para probar la implementación de typeahead:
Como puede ver, el uso de Elasticsearch with Rails hace que la búsqueda de nuestros datos sea realmente fácil y muy rápida. Aquí te mostré cómo usar las gemas de bajo nivel proporcionadas por Elasticsearch, así como la gema Searchkick, que es una abstracción que oculta algunos de los detalles de cómo funciona Elasticsearch..
Dependiendo de sus necesidades específicas, es posible que esté contento de usar Searchkick y de que su búsqueda de texto completo se implemente rápida y fácilmente. Por otro lado, si tiene otras consultas complejas, incluidos filtros o grupos, es posible que necesite aprender más sobre los detalles del lenguaje de consulta en Elasticsearch y terminar usando las gemas de nivel inferior 'elasticsearch-models' y 'elasticsearch- rieles.