在本文中,我将向您展示如何使用 Ruby on Rails 和 Elasticsearch 实现全文搜索。如今,每个人都习惯于输入搜索词并获取建议以及突出显示搜索词的结果。如果您尝试搜索的内容拼写错误,自动更正也是一项不错的功能,正如我们在 Google 或 Facebook 等网站上看到的那样。
仅使用 MySQL 或 Postgres 等关系数据库来实现所有这些功能并不简单。因此,我们使用 Elasticsearch,您可以将其视为专门为搜索构建和优化的数据库。它是开源的,构建在 Apache Lucene 之上。
Elasticsearch 最好的功能之一是使用 REST API 公开其功能,因此有一些库为大多数编程语言封装了该功能。
之前,我提到 Elasticsearch 就像一个用于搜索的数据库。如果您熟悉它的一些术语,这将会很有用。
这里需要注意的一件事是,在 Elasticsearch 中,当您将文档写入索引时,会逐字分析文档字段,以使搜索变得轻松快捷。 Elasticsearch 还支持地理定位,因此您可以搜索位于给定位置一定距离内的文档。这正是 Foursquare 实现搜索的方式。
我想提一下,Elasticsearch 在构建时就考虑到了高可扩展性,因此很容易构建具有多个服务器的集群,并且即使某些服务器出现故障也具有高可用性。我不会在本文中详细介绍如何规划和部署不同类型的集群。
如果您使用的是 Linux,则可能可以从其中一个存储库安装 Elasticsearch。它可以在 APT 和 YUM 中使用。
如果您使用 Mac,则可以使用 Homebrew 安装: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:
点击连接,您将看到一个显示集群状态的屏幕: p>
此时,正如您所料,尚未创建任何索引或文档,但我们已经有了 Elasticsearch 的本地实例安装并运行。
我将创建一个非常简单的 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
即可。
现在我们已准备好尝试搜索功能。如果我输入“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”:
如您所见,突出显示搜索词很容易,但并不理想,因为我们需要发送 JSON 查询正如 Elasticsearch 文档所指定的,我们没有任何类型的抽象。
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 %>
现在是时候再次运行应用程序并测试搜索功能了:
请注意,我输入了搜索词“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 提供的低级 gem,以及 Searchkick gem,这是一个隐藏了 Elasticsearch 工作原理的一些细节的抽象。
根据您的具体需求,您可能会很乐意使用 Searchkick 并快速轻松地实施全文搜索。另一方面,如果您有一些其他复杂的查询,包括过滤器或组,您可能需要了解有关 Elasticsearch 上查询语言的详细信息,并最终使用较低级别的 gem 'elasticsearch-models' 和 'elasticsearch-导轨”。
以上是使用Elasticsearch在Rails中进行全文搜索的详细内容。更多信息请关注PHP中文网其他相关文章!