Maison >interface Web >tutoriel HTML >Recherche en texte intégral dans Rails à l'aide d'Elasticsearch

Recherche en texte intégral dans Rails à l'aide d'Elasticsearch

WBOY
WBOYoriginal
2023-08-31 08:41:051507parcourir

Dans cet article, je vais vous montrer comment implémenter la recherche en texte intégral à l'aide de Ruby on Rails et Elasticsearch. De nos jours, tout le monde est habitué à saisir un terme de recherche et à obtenir des suggestions et des résultats mis en évidence pour le terme de recherche. La correction automatique est également une fonctionnalité intéressante si ce que vous essayez de rechercher est mal orthographié, comme nous l'avons vu sur des sites comme Google ou Facebook.

Réaliser toutes ces fonctionnalités en utilisant uniquement une base de données relationnelle comme MySQL ou Postgres n'est pas simple. Nous utilisons donc Elasticsearch, que vous pouvez considérer comme une base de données créée et optimisée spécifiquement pour la recherche. Il est open source et construit sur Apache Lucene.

L'une des meilleures fonctionnalités d'Elasticsearch est d'exposer ses fonctionnalités à l'aide d'une API REST. Il existe donc des bibliothèques qui encapsulent cette fonctionnalité pour la plupart des langages de programmation.

Présentation d'Elasticsearch

Plus tôt, j'ai mentionné qu'Elasticsearch est comme une base de données pour la recherche. Cela sera utile si vous connaissez une partie de sa terminologie.

  • Field : Un champ est comme une paire clé-valeur. La valeur peut être une valeur simple (chaîne, entier, date) ou une structure imbriquée (telle qu'un tableau ou un objet). Les champs sont similaires aux colonnes des tables des bases de données relationnelles.
  • Document : Un document est une liste de champs. Il s'agit d'un document JSON stocké dans Elasticsearch. C'est comme une ligne dans une table dans une base de données relationnelle. Chaque document est stocké dans l'index et possède un type et un identifiant unique.
  • Types : les types sont comme des tables dans une base de données relationnelle. Chaque type possède une liste de champs qui peuvent être spécifiés pour les documents de ce type.
  • Index : L'index est équivalent à une base de données relationnelle. Il contient plusieurs types de définitions et stocke plusieurs documents.

Une chose à noter ici est que dans Elasticsearch, lorsque vous écrivez un document dans l'index, les champs du document sont analysés textuellement pour rendre la recherche facile et rapide. Elasticsearch prend également en charge la géolocalisation, vous pouvez donc rechercher des documents situés à une certaine distance d'un emplacement donné. C'est exactement ainsi que Foursquare implémente la recherche.

Je voudrais mentionner qu'Elasticsearch est conçu dans un souci de haute évolutivité, il est donc facile de créer des clusters avec plusieurs serveurs et d'avoir une haute disponibilité même si certains serveurs tombent en panne. Je n'entrerai pas dans les détails de la planification et du déploiement des différents types de clusters dans cet article.

Installer Elasticsearch

Si vous utilisez Linux, vous pourrez peut-être installer Elasticsearch à partir de l'un des référentiels. Il peut être utilisé dans APT et YUM.

Si vous utilisez un Mac, vous pouvez l'installer en utilisant Homebrew : brew install elasticsearch. Après avoir installé elasticsearch, vous verrez une liste de dossiers associés dans le terminal :

Recherche en texte intégral dans Rails à laide dElasticsearch

Pour vérifier que l'installation fonctionne, entrez elasticsearch 来启动它。然后在终端中运行 curl localhost:9200 dans le terminal et vous devriez voir quelque chose de similaire à ce qui suit :

Recherche en texte intégral dans Rails à laide dElasticsearch

Installer Elastic HQ

Elastic HQ est un plugin de surveillance que nous pouvons utiliser pour gérer Elasticsearch depuis le navigateur, similaire à phpMyAdmin pour MySQL. Pour l'installer, lancez simplement dans le terminal :

/usr/local/Cellar/elasticsearch/2.2.0_1/libexec/bin/plugin -install royrusso/elasticsearch-HQ

Une fois l'installation terminée, accédez à http://localhost:9200/_plugin/hq dans votre navigateur :

Recherche en texte intégral dans Rails à laide dElasticsearch

Cliquez sur Connecter et vous verrez un écran affichant l'état du cluster : p>

Recherche en texte intégral dans Rails à laide dElasticsearch

À ce stade, comme vous pouvez vous y attendre, aucun index ou document n'a encore été créé, mais nous avons une instance locale d'Elasticsearch installée et en cours d'exécution.

Créer une application Rails

Je vais créer une application Rails très simple dans laquelle vous ajoutez des articles à une base de données afin que nous puissions effectuer des recherches en texte intégral sur eux à l'aide d'Elasticsearch. Commencez par créer une nouvelle application Rails :

rails 新的 elasticsearch-rails

Ensuite, nous utilisons un échafaudage pour générer une nouvelle ressource d'article :

rails生成脚手架文章标题:string text:text

Nous devons maintenant ajouter une nouvelle route racine afin que nous puissions voir la liste des articles par défaut. Modifier config/routes.rb :

Rails.application.routes.draw do
  root to: 'articles#index'
  resources :articles
end

Ouvrez un navigateur en exécutant la commande rake db:migrate 创建数据库。如果您启动 rails server, accédez à localhost:3000 et ajoutez quelques articles à la base de données, ou téléchargez simplement le fichier db/seeds.rb avec les données factices que j'ai créées pour que vous n'ayez pas à dépenser beaucoup d'argent. du temps à remplir des formulaires.

Ajouter une recherche

Maintenant que nous avons notre petite application Rails contenant les articles de la base de données, nous sommes prêts à ajouter une fonctionnalité de recherche. Nous commencerons par ajouter des références à deux gemmes Elasticsearch officielles :

gem 'elasticsearch-model'
gem 'elasticsearch-rails'

Sur de nombreux sites Web, il est courant d'avoir une zone de texte pour la recherche dans le menu supérieur de toutes les pages. Je vais donc créer une section de formulaire sur app/views/search/_form.html.erb. Comme vous pouvez le voir, j'envoie le formulaire généré en utilisant GET afin qu'il soit facile de copier et coller l'URL pour une recherche spécifique.

<%= form_for :term, url: search_path, method: :get do |form| %>
  <p>
    <%= text_field_tag :term, params[:term] %>
    <%= submit_tag "Search", name: nil %>
  </p>
<% end %>

Ajoutez une référence au formulaire dans la mise en page principale du site Web. Modifiez app/views/layouts/application.html.erb.

<body>
  <%= render 'search/form' %>
  <%= yield %>
</body>

Maintenant, nous avons également besoin d'un contrôleur pour effectuer la recherche proprement dite et afficher les résultats, nous exécutons donc la commande rails g 新控制器 Search pour la générer.

class SearchController < ApplicationController
  def search
    if params[:term].nil?
	  @articles = []
	else
	  @articles = Article.search params[:term]
	end
  end
end

Comme vous pouvez le voir, j'appelle la méthode search sur le modèle Article. Nous ne l'avons pas encore défini, donc si nous essayons d'effectuer une recherche à ce stade, nous obtiendrons une erreur. De plus, nous n’avons pas ajouté les routes du SearchController dans le fichier config/routes.rb , alors faisons ceci :

Rails.application.routes.draw do
  root to: 'articles#index'

  resources :articles
  get "search", to: "search#search"
end

Si l'on regarde la documentation de la gem 'elasticsearch-rails', nous devons inclure deux modules sur le modèle à indexer dans Elasticsearch, dans notre cas article.rb.

require 'elasticsearch/model'

class Article < ActiveRecord::Base
  include Elasticsearch::Model
  include Elasticsearch::Model::Callbacks
end

Le premier modèle injecte la méthode Search que nous avons utilisée dans le contrôleur précédent. Le deuxième module s'intègre aux rappels ActiveRecord pour indexer chaque instance d'un article que nous enregistrons dans la base de données, et il met également à jour l'index si nous modifions ou supprimons un article de la base de données. Tout est donc transparent pour nous.

Si vous avez déjà importé des données dans la base de données, ces articles ne sont toujours pas dans l'index Elasticsearch ; seuls les nouveaux seront automatiquement indexés. Par conséquent, nous devons les indexer manuellement, ce qui est bien si nous lançons rails console 就很容易。然后我们只需要运行 irb(main) > Article.import.

Recherche en texte intégral dans Rails à laide dElasticsearch

Nous sommes maintenant prêts à essayer la fonction de recherche. Si je tape "ruby" et clique sur Rechercher, voici les résultats :

Recherche en texte intégral dans Rails à laide dElasticsearch

Point culminant de la recherche

Sur de nombreux sites Web, vous pouvez voir comment les termes que vous recherchez sont mis en évidence sur la page des résultats de recherche. C'est facile à faire avec Elasticsearch.

Modifiez app/models/article.rb et modifiez la méthode de recherche par défaut :

def self.search(query)
  __elasticsearch__.search(
    {
      query: {
        multi_match: {
          query: query,
          fields: ['title', 'text']
        }
      },
      highlight: {
        pre_tags: ['<em>'],
        post_tags: ['</em>'],
        fields: {
          title: {},
          text: {}
        }
      }
    }
  )
end

Par défaut, la méthode search est définie par la gem 'elasticsearch-models' et fournit l'objet proxy __elasticsearch__ pour accéder à la classe wrapper de l'API Elasticsearch. Par conséquent, nous pouvons modifier la requête par défaut en utilisant les options JSON standard fournies par la documentation.

La méthode de recherche encapsulera désormais les résultats correspondant à la requête avec la balise HTML spécifiée. Pour ce faire, nous devons également mettre à jour la page de résultats de recherche pour afficher en toute sécurité les balises HTML. Pour ce faire, modifiez app/views/search/search.html.erb.

<h1>Search Results</h1>

<% if @articles %>
  <ul class="search_results">
    <% @articles.each do |article| %>
      <li>
	    <h3>
	      <%= link_to article.try(:highlight).try(:title) ?
		      article.highlight.title[0].html_safe : article.title,
              controller: "articles", action: "show", id: article._id %>
	    </h3>
	    <% if article.try(:highlight).try(:text) %>
          <% article.highlight.text.each do |snippet| %>
          <p><%= snippet.html_safe %>...</p>
        <% end %>
      <% end %>
    </li>
  <% end %>
</ul>
<% else %>
  <p>Your search did not match any documents.</p>
<% end %>

Ajoutez des styles CSS à app/assets/stylesheets/search.scss pour le balisage en surbrillance :

.search_results em {
  background-color: yellow;
  font-style: normal;
  font-weight: bold;
}

Essayez de rechercher à nouveau "ruby" :

Recherche en texte intégral dans Rails à laide dElasticsearch

Comme vous pouvez le voir, mettre en évidence les termes de recherche est facile, mais pas idéal car nous devons envoyer une requête JSON et comme le précise la documentation Elasticsearch, nous n'avons aucune sorte d'abstraction.

Joyau Searchkick

La gemme Searchkick est fournie par Instacart et est une abstraction au-dessus de la gemme officielle Elasticsearch. Je vais refactoriser la fonctionnalité de mise en évidence afin que nous ajoutions d'abord gem 'searchkick' au gemfile. La première classe que nous devons changer est le modèle Article.rb :

class Article < ActiveRecord::Base
  searchkick
end

Comme vous pouvez le constater, c'est beaucoup plus simple. Nous devons réindexer à nouveau les articles et exécuter la commande rake searchkick:reindex CLASS=Article. Afin de mettre en évidence les termes de recherche, nous devons transmettre un paramètre supplémentaire de search_controller.rb à la méthode de recherche.

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

Le dernier fichier que nous devons modifier est views/search/search.html.erb car searchkick renvoie désormais les résultats dans un format différent :

<h2>Search Results for: <i><%= params[:term] %></i></h2>

<% if @articles %>
<ul class="search_results">
  <% @articles.with_details.each do |article, details| %>
    <li>
	  <h3>
	    <%= link_to article.title, controller: "articles", action: "show", id: article.id %>
	  </h3>
      <p><%= details[:highlight][:text].html_safe %>...</p>
	</li>
  <% end %>
</ul>
<% else %>
  <p>Your search did not match any documents.</p>
<% end %>

Il est maintenant temps d'exécuter à nouveau l'application et de tester la fonctionnalité de recherche :

Recherche en texte intégral dans Rails à laide dElasticsearch

请注意,我输入了搜索词“dato”。我这样做的目的是为了向您展示,默认情况下,searchkick 设置为分析索引的文本,并且更允许拼写错误。

自动建议

自动建议或预先输入可预测用户将输入的内容,从而使搜索体验更快、更轻松。请记住,除非您有数千条记录,否则最好在客户端进行过滤。

让我们首先添加 typeahead 插件,该插件可通过 gem 'bootstrap-typeahead-rails' 获得,并将其添加到您的 Gemfile 中。接下来,我们需要向 app/assets/javascripts/application.js 添加一些 JavaScript,以便当您开始在搜索框中输入内容时,会出现一些建议。

//= 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);
          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(ready);
$(document).on('page:load', ready);

关于前一个片段的一些评论。在最后两行中,因为我没有禁用涡轮链接,所以这是连接我想要在页面加载时运行的代码的方法。在脚本的第一部分,您可以看到我正在使用 Bloodhound。它是 typeahead.js 建议引擎,我还设置了 JSON 端点来发出 AJAX 请求来获取建议。之后,我在引擎上调用 initialize(),并使用其 id“term”在搜索文本字段上设置预输入。

现在,我们需要对建议进行后端实现,让我们从添加路由开始,编辑 app/config/routes.rb

Rails.application.routes.draw do
  root to: 'articles#index'

  resources :articles
  get "search", to: "search#search"
  get 'search/typeahead/:term' => 'search#typeahead'
end

接下来,我将在 app/controllers/search_controller.rb 上添加实现。

def typeahead
  render json: Article.search(params[:term], {
    fields: ["title"],
    limit: 10,
    load: false,
    misspellings: {below: 5},
  }).map do |article| { title: article.title, value: article.id } end
end

此方法返回使用 JSON 输入的术语的搜索结果。我只按标题搜索,但我也可以指定文章的正文。我还将搜索结果的数量限制为最多 10 个。

现在我们准备尝试 typeahead 实现:

Recherche en texte intégral dans Rails à laide dElasticsearch

结论

如您所见,将 Elasticsearch 与 Rails 结合使用使搜索数据变得非常简单且快速。在这里,我向您展示了如何使用 Elasticsearch 提供的低级 gem,以及 Searchkick gem,这是一个隐藏了 Elasticsearch 工作原理的一些细节的抽象。

根据您的具体需求,您可能会很乐意使用 Searchkick 并快速轻松地实施全文搜索。另一方面,如果您有一些其他复杂的查询,包括过滤器或组,您可能需要了解有关 Elasticsearch 上查询语言的详细信息,并最终使用较低级别的 gem 'elasticsearch-models' 和 'elasticsearch-导轨”。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn

Articles Liés

Voir plus