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

在本文中,我将向您展示如何使用Ruby on Rails和Elasticsearch实施全文搜索。 如今,每个人都被用来输入搜索词并获得建议以及搜索词突出显示的结果。 如果您拼错了要搜索的内容,那么自动更正也是一项不错的功能,就像我们在Google或Facebook这样的网站上看到的那样。

仅使用关系数据库(如MySQL或Postgres)来实现所有这些功能并不容易。 因此,我们正在使用Elasticsearch,您可以将其视为专门为搜索而构建和优化的数据库。 它是开源的,基于Apache Lucene构建。

Elasticsearch最好的功能之一是使用REST API公开其功能,因此有一些库将大多数功能包装在该功能中。

介绍Elasticsearch

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

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

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

我想提一下,Elasticsearch是在考虑高可伸缩性的基础上构建的,因此,即使有一些服务器出现故障,构建具有多台服务器并具有高可用性的集群也非常容易。 在本文中,我不会介绍如何规划和部署不同类型的群集的细节。

安装Elasticsearch

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

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

Elasticsearch文件夹

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

Elasticsearch运行

安装弹性总部

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

Elastic HQ插件

单击“ 连接” ,您将看到一个显示集群状态的屏幕:

弹性总部集群概述

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

创建一个Rails应用程序

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

rails new elasticsearch-rails

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

rails generate scaffold Article title: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 new controller Search的命令rails g new controller 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中建立索引的模型上包括两个模块,在本例中为Article.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

文章导入

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

搜索结果

搜索突出显示

在许多网站上,您可以在搜索结果页面上看到所搜索的术语是如何突出显示的。 使用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文档中指定的JSON查询,并且没有任何抽象。

搜寻宝石

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

class Article < ActiveRecord::Base
  searchkick
end

如您所见,它要简单得多。 我们需要再次为文章重新rake searchkick:reindex CLASS=Article ,并执行命令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 %>

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

搜索结果

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

自动建议

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

让我们开始添加typeahead插件(可通过gem'bootstrap 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”的搜索文本字段设置了typeahead。

现在,我们需要对建议进行后端实现,让我们从添加路由开始,编辑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个。

现在我们准备尝试预输入实现:

提前输入

结论

如您所见,将Elasticsearch与Rails结合使用会使搜索我们的数据变得非常容易和快捷。 在这里,我向您展示了如何使用Elasticsearch提供的低级gem以及Searchkick gem,后者是一种抽象,隐藏了Elasticsearch工作方式的一些细节。

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

翻译自: https://code.tutsplus.com/articles/full-text-search-in-rails-using-elasticsearch--cms-22920

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值