Tích hợp Elastic Search trong ứng dụng Rails
I, Giới thiệu Một công cụ tìm kiếm toàn văn bản sẽ kiểm tra tất cả các từ trong mỗi tài liệu được lưu trữ sao cho kết quả phù hợp với các tiêu chí tìm kiếm. Ví dụ, nếu bạn muốn tìm tất cả các bài báo nói về Rails thì bạn phải search từ khóa rails. Nếu bạn không có một kỹ thuật đánh chỉ số đặc ...
I, Giới thiệu
Một công cụ tìm kiếm toàn văn bản sẽ kiểm tra tất cả các từ trong mỗi tài liệu được lưu trữ sao cho kết quả phù hợp với các tiêu chí tìm kiếm. Ví dụ, nếu bạn muốn tìm tất cả các bài báo nói về Rails thì bạn phải search từ khóa rails. Nếu bạn không có một kỹ thuật đánh chỉ số đặc biệt nào thì nó sẽ phải quét tất cả các bản ghi để tìm kiếm, điều đó thực sự không hiệu quả. Một trong những cách để giải quyết vấn đề này là "inverted index" tức là đánh chỉ số ngược, nghĩa là map các từ trong nội dung của tất cả bản ghi đến vị trí của nó trong database.
Ví dụ, nếu primary key index như này:
article#1 -> "breakthrough drug for schizophrenia" article#2 -> "new schizophrenia drug" article#3 -> "new approach for treatment of schizophrenia" article#4 -> "new hopes for schizophrenia patients" ...
Thì inverted index cho những bản ghi sẽ như này:
breakthrough -> article#1 drug -> article#1, article#2 schizophrenia -> article#1, article#2, article#3, article#4 approach -> article#3 new -> article#2, article#3, article#4 hopes -> article#4 ...
Bây giờ nếu tìm kiếm bằng từ khóa "drug" thì kết quả lập tức trả về article#1 và article#2
II, Xây dựng một ứng dụng
1, Tạo một ứng dụng Rails
Lần lượt thực hiện các lệnh sau trong terminal:
$ rails new blog $ cd blog $ bundle install $ rails s
2, Tạo controller Articles
$ rails g controller articles
Sau đó mở config/routes.rb và add thêm:
Blog::Application.routes.draw do resources :articles end
Bây giờ mở app/controllers/articles_controller.rb và thêm các method index, show, create:
def index @articles = Article.all end def show @article = Article.find params[:id] end def new end def create @article = Article.new article_params if @article.save redirect_to @article else render 'new' end end private def article_params params.require(:article).permit :title, :text end
3, Active model
Chúng ta cần sinh ra model cho articles:
$ rails g model Article title:string text:text $ rake db:migrate
4, Views
Tạo file app/views/articles/new.html.erb với nội dung như sau:
<h1>New Article</h1> <%= form_for :article, url: articles_path do |f| %> <% if not @article.nil? and @article.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(@article.errors.count, "error") %> prohibited this article from being saved:</h2> <ul> <% @article.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> <p> <%= f.label :title %><br> <%= f.text_field :title %> </p> <p> <%= f.label :text %><br> <%= f.text_area :text %> </p> <p> <%= f.submit %> </p> <% end %> <%= link_to '<- Back', articles_path %> Show One Article Create another file at app/views/articles/show.html.erb: <p> <strong>Title:</strong> <%= @article.title %> </p> <p> <strong>Text:</strong> <%= @article.text %> </p> <%= link_to '<- Back', articles_path %>
III, Tích hợp Elastic Search
Hiện tại, chúng ta có thể tìm một bài báo thông qua id của nó. Tích hợp ElasticSearch sẽ cho phép bạn tìm thấy bài báo bằng bất kì từ nào ở tiêu đề cũng như trong text của nó.
1, Install
_ Ubuntu:
Đến trang elasticsearch.org/download và download về file DEB. Sau khi hoàn thành, gõ:
$ sudo dpkg -i elasticsearch-[version].deb
_ Mac:
Nếu đang trên Mac thì chỉ cần chạy lệnh:
$ brew install elasticsearch
_ Validate Installation:
Truy cập vào đường link: http://localhost:9200 và bạn sẽ thấy như sau:
{ "status" : 200, "name" : "Anvil", "version" : { "number" : "1.2.1", "build_hash" : "6c95b759f9e7ef0f8e17f77d850da43ce8a4b364", "build_timestamp" : "2014-06-03T15:02:52Z", "build_snapshot" : false, "lucene_version" : "4.8" }, "tagline" : "You Know, for Search" }
_ Add basic search
Vào trong file Gemfile thêm:
gem 'elasticsearch-model' gem 'elasticsearch-rails'
rùi
bundle install
Tạo một controller tên search:
$ rails g controller search
Và add method search vào app/controller/search_controller.rb:
def search if params[:q].nil? @articles = [] else @articles = Article.search params[:q] end end
_ Tích hợp Search vào trong Article:
Để tích hợp ElasticSearch vào model Article, phải require elasticsearch/model vào model article, trong file app/models/article.rb:
require 'elasticsearch/model' class Article < ActiveRecord::Base include Elasticsearch::Model include Elasticsearch::Model::Callbacks end Article.import # for auto sync model with elastic search
_ Search View:
Tạo một file mới app/views/search/search.html.erb: với nội dung như sau
<h1>Articles Search</h1> <%= form_for search_path, method: :get do |f| %> <p> <%= f.label "Search for" %> <%= text_field_tag :q, params[:q] %> <%= submit_tag "Go", name: nil %> </p> <% end %> <ul> <% @articles.each do |article| %> <li> <h3> <%= link_to article.title, controller: "articles", action: "show", id: article._id%> </h3> </li> <% end %> </ul>
Cuối cùng vào route.rb và thêm vào:
get 'search', to: 'search#search'
Bây giờ bạn có thể vào trang http://localhost:3000/search và tìm kiếm bất kì từ nào trong những bài báo mà bạn đã tạo.
IV, Enhance the Search
Bạn cần lưu ý một số giới hạn của search engine. Cho ví dụ, việc tìm kiếm một phần của một từ, như "rub" hoặc "roby" để thay thế cho "ruby" sẽ cho kết quả zezo. Thay vào đó ElasticSearch cung cấp rất nhiều tính năng nhằm tối ưu việc tìm kiếm của bạn. Cho ví dụ:
_ Custom Query:
Chúng ta có thể sử dụng nhiều kiểu query khác nhau. Ban đầu, chúng ta sử dụng ElasticSearch query mặc định. Để thay đổi, chúng ta có thể custom lại query này, ví dụ như để tăng độ ưu tiên cao hơn cho trường title hơn các trường khác. Add a custom search method to our article model in app/models/article.rb:
def self.search(query) __elasticsearch__.search( { query: { multi_match: { query: query, fields: ['title^10', 'text'] } } } ) end
Với mũ 10 để đánh 10 điểm cho mỗi kết quả được tìm thấy trên title.
_ Custom Mapping
Mapping là tiến trình để định nghĩa một tài liệu được map đến search engine như nào, bao gồm những kí tự nào có thể search, những trường nào có thể search và chúng được mã hóa như nào.
Chúng ta sẽ cải tiến search để khi bạn gõ "search" thì kết quả trả về bao gồm cả "searches" và "searching" bằng việc sử dụng bộ phân tích tiếng anh trong ElasticSearch:
Add this mapping to the Article class: at app/models/article.rb:
settings index: { number_of_shards: 1 } do mappings dynamic: 'false' do indexes :title, analyzer: 'english' indexes :text, analyzer: 'english' end end
Để tự động drop và rebuild lại index khi file article.rb được load:
# Delete the previous articles index in Elasticsearch Article.__elasticsearch__.client.indices.delete index: Article.index_name rescue nil # Create the new index with the new mapping Article.__elasticsearch__.client.indices.create index: Article.index_name, body: { settings: Article.settings.to_hash, mappings: Article.mappings.to_hash } # Index all article records from the DB to Elasticsearch Article.import
_ Search Highlighting
Để hightlight những kết quả tìm kiếm được trong text, ta thực hiện các bước sau:
- add the “highlight” parameter to the ElasticSearch query:
def self.search(query) __elasticsearch__.search( { query: { multi_match: { query: query, fields: ['title^10', 'text'] } }, highlight: { pre_tags: ['<em>'], post_tags: ['</em>'], fields: { title: {}, text: {} } } } ) end
- Show hightlight trên view. Trong app/views/search/search.html.erb:
<ul> <% @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>
- Add style trong app/assets/stylesheets/search.css.scss:
em { background: yellow; }
- Nếu muốn show title cho mỗi kết quả tìm kiếm được, add thêm index_options: 'offsets' cho title mapping:
settings index: { number_of_shards: 1 } do mappings dynamic: 'false' do indexes :title, analyzer: 'english', index_options: 'offsets' indexes :text, analyzer: 'english' end end
V, Lời kết
Trên đây là ví dụ nhanh việc tích hợp ElasticSearch trong một ứng dụng Rails. Chúng ta đã add basic search, custom query, mapping, highlight.
Nguồn:
http://www.sitepoint.com/full-text-search-rails-elasticsearch/
Source code:
https://github.com/khuongnt1688/ElasticSearch