Tính năng tìm kiếm và Autocomplete trong Rails

Tìm kiếm là một trong những tính năng phổ biến nhất được tìm thấy trên hầu như bất kỳ trang web nào. Có rất nhiều giải pháp dễ dàng cho phép kết hợp việc tìm kiếm vào ứng dụng của bạn, nhưng trong bài này tôi sẽ thảo luận về tìm kiếm trong các ứng dụng Rails được cung cấp bởi gem pg_search. Trên hết, tôi sẽ chỉ cho bạn cách thêm tính năng autocomplete với sự trợ giúp của plugin select2.

Tôi sẽ có 3 ví dụ về việc sử dụng tính năng tìm kiếm và autocomplete trong các ứng dụng Rails. Cụ thể, bài viết này đề cập đến:

  • xây dựng một tính năng tìm kiếm cơ bản
  • thảo luận các tùy chọn bổ sung được hỗ trợ bởi pg_search
  • xây dựng tính năng autocomplete để tự động hiển thị tên người dùng

Chuẩn bị

Đầu tiên tạo một ứng dụng Rails mới. Tôi sẽ sử dụng Rails 5.0.1, nhưng hầu hết các khái niệm được giải thích trong bài viết này đều áp dụng tốt với các phiên bản cũ hơn. Miễn là chúng ta sẽ sử dụng tìm kiếm của Postgres, ứng dụng cần được khởi tạo với cơ sở dữ liệu PostgreSQL:

rails new Autocomplete --database=postgresql

Tạo một cơ sở dữ liệu PG mới và thiết lập config/database.yml. Để loại trừ username và password trong Postgres khỏi version hệ thống kiểm soát, tôi sử dụng gem dotenv-rails:

Gemfile

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

Để cài đặt nó tôi chạy câu lệnh:

$ bundle install

và tạo một file trong folder chính của ứng dụng:

.env

PG_USER: 'user'
PG_PASS: 'password'

Loại trừ file này khỏi sự điều khiển:

.gitignore

.env

File database sẽ thiết lập như sau:

config/database.yml

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

Bây giờ chúng ta tạo một bảng dữ liệu và thêm vào dữ liệu mẫu. Ở đây, tôi chỉ đơn giản sẽ tạo ra một bảng users với name và surname:

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

Các mẫu user phải có tên riêng biệt để chúng tôi có thể test tính năng tìm kiếm. Vì vậy, tôi sẽ sử dụng gem Faker :

Gemfile

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

Cài gem:

$ bundle install
end

Chỉnh sửa file seeds.rb để tạo 50 user với name và surname ngẫu nhiên:

db/seeds.rb

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

Chạy script:

$ rails db:seed

Cuối cùng, thêm một route root, controller với action tương ứng và view. Hiện tại, nó sẽ hiển thị tất cả user:

config/routes.rb

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

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

Mọi việc chuẩn bị đã hoàn tất! Bây giờ chúng ta có thể tiến hành phần tiếp theo và thêm một tính năng tìm kiếm vào ứng dụng.

Tìm kiếm User

Những gì tôi muốn làm là hiển thị một trường tìm kiếm ở trên cùng của trang chủ nói rằng: "Enter user’s name or surname" với một nút "Search!". Khi form được gửi, chỉ những user phù hợp với cụm từ được nhập sẽ được hiển thị.

Bắt đầu với form:

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

Bây giờ chúng ta cần lấy cụm từ được nhập và thực hiện tìm kiếm. Đó là khi gem pg_search xuất hiện:

Gemfile

# ...
gem 'pg_search'

Đừng quên cài đặt:

$ bundle install

Pg_search hỗ trợ 2 mode: multisearchablepg_search_scope. Trong bài viết này, tôi sẽ sử dụng tùy chọn thứ 2 (multisearchable hỗ trợ tương tự, nhưng cho phép tìm kiếm toàn cầu được tạo trên nhiều model). Ý tưởng khá đơn giản: chúng ta tạo một phạm vi tìm kiếm. Nó có một tên và một loạt các lựa chọn. Trước khi làm điều đó, module PgSearch phải được include trong model:

models/user.rb

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

Trong ví dụ này, cột name và surname sẽ được sử dụng để tìm kiếm.

Bây giờ tuỳ chỉnh action trong controller sử dụng phạm vi tìm kiếm mới`

users_controller.rb

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

Bây giờ bạn có thể khởi động máy chủ, truy cập trang chủ và nhập name hoặc surname của một số user và mọi thứ cần hoạt động bình thường.

Bổ sung các tuỳ chọn

Việc tìm kiếm đã làm việc, nhưng nó không được thuận tiện. Ví dụ: nếu tôi chỉ nhập một phần của name hoặc surname, nó sẽ không trả lại bất kỳ kết quả nào. Điều này có thể dễ dàng sửa chữa bằng cách cung cấp các thông số bổ sung.

Trước hết, bạn cần chọn một kỹ thuật tìm kiếm để sử dụng. Một là mặc định tìm kiếm full text của Postgres, mà tôi sẽ sử dụng trong bài báo này. Hai loại khác là tìm kiếm kiểu trigram và metaphone, yêu cầu phải cài đặt phần mở rộng PG. Để thiết lập kỹ thuật, sử dụng tuỳ chọn :using :

pg_search_scope :search_by_full_name, against: [:name, :surname], using: [:tsearch]

Mỗi một kỹ thuật có các lựa chọn khác nhau. Ví dụ: cho phép tìm kiếm một phần của từ:

models/user.rb

class User < ApplicationRecord
  include PgSearch
  pg_search_scope :search_by_full_name, against: [:name, :surname],
    using: {
      tsearch: {
        prefix: true
      }
    }

Bây giờ nếu bạn nhập "jay" làm cụm từ tìm kiếm, bạn sẽ thấy tất cả người dùng có tên đầy đủ chứa "jay". Lưu ý rằng tính năng này chỉ được hỗ trợ bắt đầu từ phiên bản 8.4 của Postgres.

Một lựa chọn thú vị là :negation, cho phép loại trừ từ được cung cấp bằng cách thêm ký tự "!""vào trước. Ví dụ: Bob !Jones. Bật tùy chọn này như sau:

models/user.rb

class User < ApplicationRecord
  include PgSearch
  pg_search_scope :search_by_full_name, against: [:name, :surname],
    using: {
      tsearch: {
        prefix: true,
        negation: true
      }
    }

Tuy nhiên, hãy lưu ý rằng đôi khi tính năng này có thể cung cấp kết quả không mong muốn.

Nó cũng sẽ làm nổi bật các từ tìm thấy. Điều này có thể thực hiện với tuỳ chọn :highlight:

models/user.rb

# ...
include PgSearch
pg_search_scope :search_by_full_name, against: [:name, :surname],
  using: {
    tsearch: {
      prefix: true,
      negation: true,
      highlight: {
        start_sel: '<b>',
        stop_sel: '',
      }
    }
  }

Ở đây chúng tôi đang nói rằng selection được bọc bằng các thẻ b. Tính năng highlight có một số tùy chọn khác có sẵn nữa.

Để tính năng highlighting hoạt động, chúng ta cần phải thực hiện thêm hai thay đổi. Đầu tiên, thêm phương thức with_pg_search_highlight như sau:

users_controller.rb

# ...
@users = User.search_by_full_name(params[:term]).with_pg_search_highlight

Thứ hai chỉnh sửa partial:

views/users/_user.html.erb

<li>
  < % if user.respond_to?(:pg_search_highlight) %>
    < %= user.pg_search_highlight.html_safe %>
  < % else %>
    < %= user.name %> < %= user.surname %>
  < % end %>
</li>

Phương thức pg_search_highlight trả về các giá trị được highlights các từ giống từ tìm kiếm và làm nổi bật kết quả tìm thấy.

Thêm tính năng Autocomplete

Một tính năng phổ biến khác được tìm thấy trên nhiều trang web là autocomplete. Hãy xem làm thế nào nó có thể được crafted với pg_search và plugin select2. Giả sử chúng ta có trang "Send message", nơi người dùng có thể chọn ai để ghi vào.

Thêm 2 gem Gemfile:

Gemfile

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

javascripts/application.js

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

File messages.coffee sẽ được tạo ra trong giây lát. Select2 cũng requires một số styles:

stylesheets/application.scss

@import 'select2';

Bây giờ hãy nhanh chóng thêm một route, controller và view mới:

config/routes.rb

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

messages_controller.rb

class MessagesController < ApplicationController
  def new
  end
end

views/messages/new.html.erb

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

Form trên chưa hoàn chỉnh, bởi vì nó sẽ không được gửi được. Lưu ý rằng ban đầu trường chọn không có bất kỳ giá trị, chúng sẽ được hiển thị động dựa trên đầu vào của người dùng.

CoffeeScript:

javascripts/messages.coffee

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

Vì bài viết này không dành cho Select2 và JavaScript. Tuy nhiên, chúng ta hãy nhanh chóng xem lại các phần chính:

  • dataType trả về là JSON
  • delay: 500 nghĩa là yêu cầu sẽ bị trì hoãn tới 0.5 giây sau khi người dùng nhập xong.
  • processResults giải thích cách xử lý dữ liệu nhận được từ máy chủ. Tôi đang xây dựng một mảng của các đối tượng có id người dùng và tên đầy đủ. Lưu ý rằng thuộc tính id là bắt buộc.
  • escapeMarkup ghi đè lên chức năng escapeMarkup mặc định để tránh bất kỳ sự thoát ra.
  • minimumInputLength quy định người dùng nên nhập ít nhất 2 ký tự.
  • templateResult định nghĩa giao diện của các tùy chọn được hiển thị trong trình đơn thả xuống.
  • templateSelection xác định giao diện của tùy chọn đã chọn.

Bước tiếp theo là tuỳ chỉnh users#index cho phép nó phản hồi với dạng JSON:

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

Cuối cùng là view:

views/users/index.json.jbuilder

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

Lưu ý rằng để làm việc này phải có gem jBuilder. Đối với các ứng dụng Rails 5, gem này mặc định có trong Gemfile.

Tính năng highlight đã được thêm vào trong phần trước. Hãy sử dụng tốt nó ở đây! Những gì cần phải làm là làm nổi bật các từ tìm thấy trong danh sách dropdown, nhưng không phải khi lựa chọn đã được chọn. Do đó, chúng ta cần giữ name và surname trong phản hồi JSON và cũng thêm trường full_name:

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

Chỉnh sửa CoffeeScript phần processResults:

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

phần templateResult:

templateResult: (item) -> item.name_highlight

Kết luận

Trong bài này, chúng ta đã dùng gem pg_search và select2 plugins. Với sự hỗ trợ của chúng, chúng tôi đã xây dựng chức năng tìm kiếm và thêm một tính năng autocomplete vào ứng dụng, làm cho ứng dụng thêm thân thiện với người dùng. Pg_search có rất nhiều thứ thú vị hơn để cung cấp, vì vậy hãy tìm hiểu thêm tài liệu về nó.

Thank you!

Tài liệu dịch: https://www.sitepoint.com/search-autocomplete-rails-apps/