12/08/2018, 12:57

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 ...

1407685071Fotolia_67902893_Subscription_Monthly_M-300x300.jpg

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

0