在本文中,我将向您展示如何使用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 elasticsearch
: brew install elasticsearch
。 安装Elasticsearch之后,您将在终端中看到相关文件夹的列表:
要验证安装是否正常,请在终端中键入elasticsearch
以启动安装。 然后在终端中运行curl localhost:9200
,您应该看到类似以下内容:
安装弹性总部
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应用程序
我将创建一个非常简单的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| %>
<%= text_field_tag :term, params[:term] %>
<%= submit_tag "Search", name: nil %>
<% end %>
在主网站布局中添加对表单的引用。 编辑app / views / layouts / application.html.erb。
<%= render 'search/form' %>
<%= yield %>
现在,我们还需要一个控制器来执行实际的搜索并显示结果,因此我们将在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: [''],
post_tags: [''],
fields: {
title: {},
text: {}
}
}
}
)
end
默认情况下, search
方法由gem'elasticsearch-models'定义,并且提供了代理对象__elasticsearch__来访问Elasticsearch API的包装器类。 因此,我们可以使用文档提供的标准JSON选项修改默认查询。
现在,搜索方法将包装与查询匹配的结果与指定HTML标记。 因此,我们还需要更新搜索结果页面,以便我们可以安全地呈现HTML标签。 为此,请编辑app / views / search / search.html.erb 。
Search Results
<% if @articles %>
<% @articles.each do |article| %>
-
<%= link_to article.try(:highlight).try(:title) ?
article.highlight.title[0].html_safe : article.title,
controller: "articles", action: "show", id: article._id %>
<% if article.try(:highlight).try(:text) %>
<% article.highlight.text.each do |snippet| %>
<%= snippet.html_safe %>...
<% end %>
<% end %>
<% end %>
<% else %>
Your search did not match any documents.
<% end %>
将CSS样式添加到app / assets / stylesheets / search.scss中 ,以突出显示标签:
.search_results em {
background-color: yellow;
font-style: normal;
font-weight: bold;
}
尝试再次搜索“Ruby”:
如您所见,突出显示搜索词很容易,但并不理想,因为我们需要发送Elasticsearch文档中指定的JSON查询,并且没有任何抽象。
搜寻宝石
Searchkick gem由Instacart提供,它是官方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以不同的格式返回了结果:
Search Results for: <%= params[:term] %>
<% if @articles %>
<% @articles.with_details.each do |article, details| %>
-
<%= link_to article.title, controller: "articles", action: "show", id: article.id %>
<%= details[:highlight][:text].html_safe %>...
<% end %>
<% else %>
Your search did not match any documents.
<% 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