使用Elasticsearch在Rails中进行全文搜索

WBOY
发布: 2023-08-31 08:41:05
原创
1503 人浏览过

在本文中,我将向您展示如何使用 Ruby on Rails 和 Elasticsearch 实现全文搜索。如今,每个人都习惯于输入搜索词并获取建议以及突出显示搜索词的结果。如果您尝试搜索的内容拼写错误,自动更正也是一项不错的功能,正如我们在 Google 或 Facebook 等网站上看到的那样。

仅使用 MySQL 或 Postgres 等关系数据库来实现所有这些功能并不简单。因此,我们使用 Elasticsearch,您可以将其视为专门为搜索构建和优化的数据库。它是开源的,构建在 Apache Lucene 之上。

Elasticsearch 最好的功能之一是使用 REST API 公开其功能,因此有一些库为大多数编程语言封装了该功能。

Elasticsearch 简介

之前,我提到 Elasticsearch 就像一个用于搜索的数据库。如果您熟悉它的一些术语,这将会很有用。

  • 字段:字段就像一个键值对。该值可以是简单值(字符串、整数、日期),也可以是嵌套结构(如数组或对象)。字段类似于关系数据库中表中的列。
  • 文档:文档是字段列表。它是一个存储在 Elasticsearch 中的 JSON 文档。它就像关系数据库中表中的一行。每个文档都存储在索引中,并具有类型和唯一 ID。
  • 类型:类型就像关系数据库中的表。每种类型都有一个可以为该类型的文档指定的字段列表。
  • 索引:索引相当于关系数据库。它包含多种类型的定义并存储多个文档。

这里需要注意的一件事是,在 Elasticsearch 中,当您将文档写入索引时,会逐字分析文档字段,以使搜索变得轻松快捷。 Elasticsearch 还支持地理定位,因此您可以搜索位于给定位置一定距离内的文档。这正是 Foursquare 实现搜索的方式。

我想提一下,Elasticsearch 在构建时就考虑到了高可扩展性,因此很容易构建具有多个服务器的集群,并且即使某些服务器出现故障也具有高可用性。我不会在本文中详细介绍如何规划和部署不同类型的集群。

安装Elasticsearch

如果您使用的是 Linux,则可能可以从其中一个存储库安装 Elasticsearch。它可以在 APT 和 YUM 中使用。

如果您使用 Mac,则可以使用 Homebrew 安装:brew install elasticsearch。安装elasticsearch后,您将在终端中看到相关文件夹的列表:

使用Elasticsearch在Rails中进行全文搜索

要验证安装是否正常工作,请在终端中输入 elasticsearch 来启动它。然后在终端中运行 curl localhost:9200,您应该会看到类似以下内容的内容:

使用Elasticsearch在Rails中进行全文搜索

安装 Elastic HQ

Elastic HQ 是一个监控插件,我们可以使用它从浏览器管理 Elasticsearch,类似于 MySQL 的 phpMyAdmin。要安装它,只需在终端中运行:

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

安装完成后,在浏览器中导航至 http://localhost:9200/_plugin/hq:

使用Elasticsearch在Rails中进行全文搜索

点击连接,您将看到一个显示集群状态的屏幕: p>

使用Elasticsearch在Rails中进行全文搜索

此时,正如您所料,尚未创建任何索引或文档,但我们已经有了 Elasticsearch 的本地实例安装并运行。

创建 Rails 应用程序

我将创建一个非常简单的 Rails 应用程序,您可以在其中将文章添加到数据库中,以便我们可以使用 Elasticsearch 对它们执行全文搜索。首先创建一个新的 Rails 应用程序:

rails 新的 elasticsearch-rails

接下来我们使用脚手架生成一个新的文章资源:

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

现在我们需要添加一个新的根路由,这样我们就可以默认看到文章列表。编辑config/routes.rb

Rails.application.routes.draw do
  root to: 'articles#index'
  resources :articles
end
登录后复制

通过运行命令 rake db:migrate 创建数据库。如果您启动 rails server,打开浏览器,导航到 localhost:3000 并向数据库添加一些文章,或者只是下载文件 db/seeds.rb 以及我创建的虚拟数据,以便您不必花费大量时间填写表格。

添加搜索

现在我们有了包含数据库中文章的小 Rails 应用程序,我们准备添加搜索功能。我们将首先添加对两个官方 Elasticsearch Gems 的引用:

gem 'elasticsearch-model'
gem 'elasticsearch-rails'
登录后复制

在许多网站上,所有页面的顶部菜单中都有一个用于搜索的文本框是很常见的。因此,我将在 app/views/search/_form.html.erb 上创建一个表单部分。 如您所见,我将发送使用 GET 生成表单,因此可以轻松复制并粘贴特定搜索的 URL。

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

在主网站布局中添加对表单的引用。编辑app/views/layouts/application.html.erb。

<body>
  <%= render 'search/form' %>
  <%= yield %>
</body>
登录后复制

现在我们还需要一个控制器来执行实际搜索并显示结果,因此我们运行命令 rails g 新控制器 Search 来生成它。

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

如您所见,我在 Article 模型上调用方法 search。我们还没有定义它,所以如果我们尝试在此时执行搜索,我们会收到错误。另外,我们还没有在 config/routes.rb 文件中添加 SearchController 的路由,所以让我们这样做:

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

  resources :articles
  get "search", to: "search#search"
end
登录后复制

如果我们查看 gem 'elasticsearch-rails' 的文档,我们需要在要在 Elasticsearch 中索引的模型上包含两个模块,在我们的例子中文章.rb.

require 'elasticsearch/model'

class Article < ActiveRecord::Base
  include Elasticsearch::Model
  include Elasticsearch::Model::Callbacks
end
登录后复制

第一个模型注入了我们在之前的控制器中使用的 Search 方法。第二个模块与 ActiveRecord 回调集成,为我们保存到数据库的文章的每个实例建立索引,如果我们修改或从数据库中删除文章,它还会更新索引。所以这对我们来说都是透明的。

如果您之前将数据导入数据库,这些文章仍然不在 Elasticsearch 索引中;只有新的才会自动索引。因此,我们必须手动索引它们,如果我们启动 rails console 就很容易。然后我们只需要运行 irb(main) > Article.import 即可。

使用Elasticsearch在Rails中进行全文搜索

现在我们已准备好尝试搜索功能。如果我输入“ruby”并单击搜索,结果如下:

使用Elasticsearch在Rails中进行全文搜索

搜索突出显示

在许多网站上,您可以在搜索结果页面上看到您搜索的字词如何突出显示。使用 Elasticsearch 可以很容易地做到这一点。

编辑app/models/article.rb并修改默认搜索方式:

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
登录后复制

默认情况下,search 方法由 gem 'elasticsearch-models' 定义,并提供代理对象 __elasticsearch__ 来访问 Elasticsearch API 的包装类。因此,我们可以使用文档提供的标准 JSON 选项修改默认查询。

现在搜索方法将用指定的 HTML 标签包装与查询匹配的结果。为此,我们还需要更新搜索结果页面,以便能够安全地渲染 HTML 标签。为此,请编辑 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 %>
登录后复制

将 CSS 样式添加到 app/assets/stylesheets/search.scss,用于突出显示的标记:

.search_results em {
  background-color: yellow;
  font-style: normal;
  font-weight: bold;
}
登录后复制

再次尝试搜索“ruby”:

使用Elasticsearch在Rails中进行全文搜索

如您所见,突出显示搜索词很容易,但并不理想,因为我们需要发送 JSON 查询正如 Elasticsearch 文档所指定的,我们没有任何类型的抽象。

Searchkick 宝石

Searchkick gem 由 Instacart 提供,它是官方 Elasticsearch gem 之上的抽象。我将重构突出显示功能,因此我们首先将 gem 'searchkick' 添加到 gemfile 中。我们需要更改的第一个类是 Article.rb 模型:

class Article < ActiveRecord::Base
  searchkick
end
登录后复制

正如您所看到的,它要简单得多。我们需要再次重新索引文章,并执行命令 rake searchkick:reindex CLASS=Article。为了突出显示搜索词,我们需要从 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
登录后复制

我们需要修改的最后一个文件是 views/search/search.html.erb ,因为 searchkick 现在以不同的格式返回结果:

<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 %>
登录后复制

现在是时候再次运行应用程序并测试搜索功能了:

使用Elasticsearch在Rails中进行全文搜索

请注意,我输入了搜索词“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 实现:

使用Elasticsearch在Rails中进行全文搜索

结论

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

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

以上是使用Elasticsearch在Rails中进行全文搜索的详细内容。更多信息请关注PHP中文网其他相关文章!

相关标签:
来源:php.cn
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板