Tìm hiểu về Ransack Gem và ứng dụng trong tìm kiếm

I.Tổng quan

Ransack là một gem được viết lại từ gem MetaSearch được dùng để tìm kiếm dữ liệu. Nó hỗ trợ nhiều tính năng tương tự như MetaSearch nhưng khác nhau khá nhiều so với MetaSearch trong cách thức thực hiện tìm kiếm, và tính tương thích không phải là mục tiêu thiết kế của nó.

Ransack cho phép tạo ra cả hai hình thức tìm kiếm đơn giản và tìm kiếm nâng cao tùy theo các mô hình ứng dụng trong chương trình.

II.Sử dụng Ransack (Getting started)

1. Cài đặt

Ransack khả dụng với Rails 3.x, 4.0, 4.1 và 4.2.

Thêm dòng sau vào Gemfile:

gem "ransack”

Hoặc nếu bạn muốn dùng bản mới nhất của Ransack:

gem "ransack", :git => "git://github.com/ernie/ransack.git"

2. Cách sử dụng (Usage)

Ransack có thể được sử dụng theo một trong hai chế độ, đơn giản hoặc nâng cao.

2.1. Simple Mode (Mô hình đơn giản)

Mô hình này hoạt động giống như Meta Search, với những người đã quen thuộc với nó thì đòi hỏi những thiết lập rất nhỏ.

Nếu đã biết Meta Search, những điều cần lưu ý :

  • Từ khóa mặc định cho param tìm kiếm bây giờ là :q, thay vì :search. Mục đích là để rút ngắn chuỗi truy vấn query strings trên trình duyệt.

  • form_for chuyển thành search_form_for, và xác nhận (chắc chắn) rằng một Ransack::Search object được truyền cho nó.

  • Các method Common ActiveRecord::Relation không còn được phân cấp bởi các đối tượng tìm kiếm như trước. Thay vào đó, bạn sẽ nhận được kết quả tìm kiếm thông qua việc gọi Search#result.

  • Nếu được thông qua (distinct: true), result sẽ tạo ra một truy vấn SELECT DISTINCT để tránh trả về các bản ghi trùng lặp ngay cả khi các điều kiện trong 1 join sẽ không dẫn đến trùng lặp.

Lưu ý đối với nhiều cơ sở dữ liệu, distinct: true không khả dụng. Trong trường hợp đó sử dụng #to_a.uniq với tập kết quả để lấy được các bản ghi duy nhất.

Controller:

def index
  @q = Person.search(params[:q])
  @people = @q.result(distinct: true)
end

//hoặc

def index
  @q = Person.search(params[:q])
  @people = @q.result.includes(:articles).page(params[:page])
  # hoặc sử dụng `to_a.uniq` để xóa các bản ghi trùng lặp như dưới đây (có thể dùng cả trong view):
  @people = @q.result.includes(:articles).page(params[:page]).to_a.uniq
end

View:

Sử dụng search_form_for thay vì form_for để tạo form search trong view :

<%= search_form_for @q do |f| %>

  # Tìm kiếm theo tên ...
  <%= f.label :name_cont %>
  <%= f.text_field :name_cont %>

  # Search if an associated articles.title starts with...
  <%= f.label :articles_title_start %>
  <%= f.text_field :articles_title_start %>

  # Tìm kiếm theo nhiều attr cho 1 giá trị ...
  <%= f.label :name_or_description_or_email_or_articles_title_cont %>
  <%= f.text_field :name_or_description_or_email_or_articles_title_cont %>

  <%= f.submit %>
<% end %>

cont (contains)start (starts with) là 2 phương thức tìm kiếm mặc định trong ransack.Xem thêm thông tin có thể vào Constants hoặc wiki.

Advanced Mode

Tìm kiếm Advanced sử dụng thuộc tính lồng nhau của Rails để tạo ra các truy vấn phức tạp với nhiều model khác nhau trong ứng dụng.

Thiết lập Routes:

resources :people do
  collection do
    match 'search' => 'people#search', :via => [:get, :post], :as => :search
  end
end

… thêm action trong controller …

def search
  index
  render :index
end

… cập nhật search_form_for trong view …

<%= search_form_for @q, :url => search_people_path,
                        :html => {:method => :post} do |f| %>

Ransack #search method

Ransack sẽ tạo #search phù hợp trong model, trong trường hợp #search đã được định nghĩa thì có thể sử dụng #ransack. Ví dụ :

Article.search(params[:q])
Article.ransack(params[:q])

Associations

Có thể dễ dàng sử dụng Ransack để tìm kiếm các object liên kết với nhau bằng quan hệ has_manybelongs_to. Giả sử có mối liên kết như sau:

class Employee < ActiveRecord::Base
  belongs_to :supervisor
end

class Department < ActiveRecord::Base
 has_many :supervisors
end

class Supervisor < ActiveRecord::Base
  belongs_to :department
  has_many :employees
end

… và controller

class SupervisorsController < ApplicationController
  def index
   @search = Supervisor.search(params[:q])
    @supervisors = @search.result(:distinct => true)
  end
end

… form view được thiết lập

<%= search_form_for @search do |f| %>
  <%= f.label :last_name_cont %>
  <%= f.text_field :last_name_cont %>

  <%= f.label :department_title_cont %>
  <%= f.text_field :department_title_cont %>

  <%= f.label :employees_first_name_or_employees_last_name_cont %>
  <%= f.text_field :employees_first_name_or_employees_last_name_cont %>

  <%= f.submit "search" %>
<% end %>
...
<%= content_tag :table %>
  <%= content_tag :th, sort_link(@q, :last_name) %>
  <%= content_tag :th, sort_link(@q, "departments.title") %>
  <%= content_tag :th, sort_link(@q, "employees.last_name") %>
<% end %>

Using Scopes/Class Methods

Tìm bởi scope yêu cầu định nghĩa 1 whitelist của ransackable_scopes trong model class, whitelist nên là 1 mảng các ký tự. Mặc định tất cả các class method được bỏ qua,. Scope sẽ applied cho matching các giá trị true hoặc các giá trị mà scope chấp nhận :

class Employee < ActiveRecord::Base
  scope :active, ->(boolean = true){(where active: boolean)}
  scope :salary_gt, ->(amount){where('salary > ?', amount)}

  # Scopes are just syntactical sugar for class methods, which may also be used:
  def self.hired_since(date)
    where('start_date >= ?', date)
  end
  private
  def self.ransackable_scopes(auth_object = nil)
    if auth_object.try(:admin?)
      # allow admin users access to all three methods
      %i(active hired_since salary_gt)
    else
      # allow other users to search on active and hired_since only
      %i(active hired_since)
    end
  end
end
Employee.search({active: true, hired_since: '2013-01-01'})

Employee.search({salary_gt: 100_000 }, { auth_object: current_user})

3. Lời kết

Link trên github : https://github.com/nguyenhoa/brs_2/commit/aa71c0c3ba4f375b57510c2ad50fa3208e26d947

Link trên heroku : http://brs2.herokuapp.com/

admin account : [email protected]

user account : [email protected]

password : foobar

Chỉ dẫn : đăng nhập -> View -> Users (search form trong users index)

Nguồn tham khảo:

https://github.com/ernie/ransack

https://github.com/railscasts/370-ransack

http://railscasts.com/episodes/370-ransack?autoplay=true