07/09/2018, 17:51

Search and Autocomplete in Rails Apps

Tìm kiếm là một trong những tính năng phổ biến nhất mà chúng ta có thể tìm thấy trên bất kỳ trang web nào ngày nay. Có rất nhiều giải pháp để thực hiện đưa tính năng ngày một cách dễ dàng vào trong ứng dụng của bạn. Trong bài viết này sẽ giới thiệu về tìm kiếm với Postgres bằng cách sử dụng gem ...

Tìm kiếm là một trong những tính năng phổ biến nhất mà chúng ta có thể tìm thấy trên bất kỳ trang web nào ngày nay. Có rất nhiều giải pháp để thực hiện đưa tính năng ngày một cách dễ dàng vào trong ứng dụng của bạn. Trong bài viết này sẽ giới thiệu về tìm kiếm với Postgres bằng cách sử dụng gem pg_search và kết hợp với plugin select2.
Bài viết sẽ giới thiệu qua ví dụ tìm kiếm công nhân với tính năng autocomplete. Cụ thể bài viết sẽ giới thiệu:

  • xây dựng một tính năng tìm kiếm căn bản
  • xây dựng một tính năng autocomplete để hiển thị kết quả tìm kiếm theo tên người dùng

Xây dựng ứng dụng

Để bắt đầu chúng ta tạo một ứng dụng sử dụng postgresql

rails new Autocomplete --database=postgresql

Tạo một PG database và cài đặt config/database.yml đúng, dùng dotenv-rails để lưu tên và mật khẩu kết nối với Postegres database.
Gemfile

# ...
group :development do
  gem 'dotenv-rails'
end

Để cài đặt chúng, chúng ta chạy

$ bundle install

và tạo một tệp trong thư mục gốc của project như sau
.env

PG_USER: 'user'
PG_PASS: 'password'

và ignore chúng khi dùng quản lý phiên bản git
.gitignore

.env

Cuối cùng là cài đặt cấu hình của bạn có thể giống như sau

development:
  adapter: postgresql
  database: autocomplete
  host: localhost
  user: < %= ENV['PG_USER'] %>
  password: < %= ENV['PG_PASS'] %>

Tạo một bảng và lưu một vài thông tin của người dùng

$ rails g model User name:string surname:string
$ rails db:migrate

Để tạo dữ liệu mẫu, chúng ta có thể sử dụng Faker
Gemfile

# ...
group :development do
  gem 'faker'
end

Để cài đặt chúng, chạy lại $ bundle install
Chúng ta có thể tạo user với tên ngẫu nhiên như sau
db/seeds.rb

50.times do
  User.create({name: Faker::Name.first_name,
              surname: Faker::Name.last_name})
end

Để khởi tạo chúng chỉ cần chạy $ rails db:seed
Cuối cùng là giới thiệu một root route, controller với index action và một view để hiển thị tất cả các người dùng
config/routes.rb

# ...
resources :users, only: [:index]
root to: 'users#index'

userscontroller.rb

class UsersController < ApplicationController
  def index
    @users = User.all
  end
end

views/users/index.html.erb

<ul>
  < %= render @users %>
</ul>

views/users/user.html.erb

<li>
  < %= user.name %> < %= user.surname %>
</li>

Tìm kiếm Users

Chúng ta sẽ làm một form trên đầu trang chủ để tìm kiếm người dùng theo tên hoặc họ.
Bắt đầu với form có thể như sau
views/users/index.html.erb

< %= form_tag users_path, method: :get do %>
  < %= text_field_tag 'term', params[:term], placeholder: "Enter user's name or surname" %>
  < %= submit_tag 'Search!' %>
< % end %>

Giờ là xử lý ở backend, chúng ta sẽ sử dụng pg_search
Gemfile

# ...
gem 'pg_search'

Đừng quên để cài đặt, chúng ta chạy $ bundle install
Pg_search hỗ trợ 2 chế độ: multisearchable và pg_search_scope. Ở đây ta sẽ sử dụng kiểu thứ 2 đó là tạo một scope để tìm kiếm.
Bắt đầu để sử dụng, chúng ta phải include PgSearch module
models/user.rb

# ...
include PgSearch
pg_search_scope :search_by_full_name, against: [:name, :surname]

Trong ví dụ này :name và :surname sẽ được đưa vào để tìm kiếm
userscontroller.rb

# ...
if params[:term]
  @users = User.search_by_full_name(params[:term])
else
  @users = User.all
end

Vậy là chúng ta có thể sử dụng tính năng tìm kiếm một cách đơn giản rồi.

Tính năng search autocomplete

Một tính năng phổ biến có thể dễ thấy đó là autocomplete search. Chúng ta có thể sử dụng select2 để dễ dàng đạt được yêu cầu
Gemfile

# ...
gem 'select2-rails'
gem 'underscore-rails'

Select2 là một plugin biến những trường nhập đơn giản thành một trường nhập linh hoạt với người dùng, còn Underscore.js sẽ cùng cấp một vài phương thức để có thể dễ dàng hơn. Để sử dụng thư viện, chúng ta phải khai báo như sau
javascripts/application.js

//= require underscore
//= require select2
//= require messages

stylesheets/application.scss

@import 'select2';

Giờ ta tạo một route, controller , view mới:
config/routes.rb

# ...
resources :messages, only: [:new]

messagescontroller.rb

class MessagesController < ApplicationController
  def new
  end
end

views/messages/new.html.erb

<%= form_tag ' do %>
  < %= label_tag 'to' %>
  < %= select_tag 'to', nil, style: 'awidth: 100%' %>
< % end %>

Trường select không có bất kỳ giá trị nào, nó sẽ là phần hiển thị động kết quả dựa trên thông tin người dùng nhập vào. Để thực thi việc autosearch, chúng ta sẽ phải lấy giá trị cho trường select này mỗi khi người dùng gõ ký tự bất kỳ để tìm kiếm. Công việc này sẽ được điều khiển bởi đoạn javascript đơn giản sau:

jQuery(document).on 'turbolinks:load', ->
  $('#to').select2
    ajax: {
      url: '/users'
      data: (params) ->
        {
          term: params.term
        }
      dataType: 'json'
      delay: 500
      processResults: (data, params) ->
        {
          results: _.map(data, (el) ->
            {
              id: el.id
              name: "#{el.surname}, #{el.name}"
            }
          )
        }
      cache: true
    }
    escapeMarkup: (markup) -> markup
    minimumInputLength: 2
    templateResult: (item) -> item.name
    templateSelection: (item) -> item.name

Tiếp theo chúng ta phải sử lý kết quả search động mà server cần phải tìm và trả về
userscontroller.rb

# ...
def index
  respond_to do |format|
    if params[:term]
      @users = User.search_by_full_name(params[:term]).with_pg_search_highlight
    else
      @users = User.all
    end
    format.json
    format.html
  end
end

views/users/index.json.jbuilder

json.array! @users do |user|
  json.id user.id
  json.name user.name
  json.surname user.surname
end

views/users/index.json.jbuilder

json.array! @users do |user|
  json.id user.id
  json.full_name user.pg_search_highlight.html_safe
  json.name user.name
  json.surname user.surname
end

Để xử lý kết quả trả về từ server, chúng ta thực thi processResults ở trên như sau

processResults: (data, params) ->
  {
    results: _.map(data, (el) ->
      {
        name_highlight: el.full_name
        id: el.id
        name: "#{el.surname}, #{el.name}"
      }
    )
  }

Vậy là hoàn thành một chứ năng đơn giản nhưng rất phổ biến và thông dụng ngày nay. Bạn có thể tìm mã code chương trình hoàn chỉn ở đây

Refs

Search and Autocomplete in Rails Apps

0