Thêm/sửa các điều kiện search cho Ransack

Chắc hẳn các bạn lập trình viên Ruby on Rails (RoR) đã không còn xa lạ gì với gem tìm kiếm nổi tiếng Ransack, tuy nhiên, đây là một gem khá linh hoạt và có nhiều tùy biến hay. Hôm nay tôi sẽ tổng hợp lại và giới thiệu đến các bạn một số cách tùy biến/ thêm các trường trong một ô tìm kiếm với Ransack.

Ransack

Đầu tiên tôi xin nói sơ qua về ransack( nếu bạn đã dùng và thông thạo bước cài đặt thì có thể bỏ qua bước này!) Cài đặt: Trong Gemfile:

gem "ransack'"

Hoặc

gem "ransack", github: "activerecord-hackery/ransack"

Sau đó chạy bundle install

Cách định nghĩa và thêm các trường cho một ô tìm kiếm

controllers

Ta định nghĩa một biến search cho params search của ransack:

    @q = User.search(params[:q])
    @users = @q.result.includes(:articles).page(params[:page]).to_a.uniq

views

Thông thường trong views mọi người sẽ kết hợp các trường search với tên các trường (muốn search) kết hợp luôn trên views

<%= search_form_for @q do |f| %>
    <%= f.label :name_or_description_or_email_or_articles_title_cont %>
    <%= f.search_field :name_or_description_or_email_or_articles_title_cont %>
    <%= f.submit %>
<% end %>

Tuy nhiên, nếu muốn định nghĩa thêm trường search thì search_filed kia sẽ là một chuỗi dài dằng dặc, một pha xử lý code khá "cồng kềnh".

Ransack Aliases

bằng cách sử dụng alias ta có thể rút gọn bớt đi các trường search trong views:

class Post < ActiveRecord::Base
  belongs_to :author

  # Abbreviate :author_first_name_or_author_last_name to :author
  ransack_alias :author, :author_first_name_or_author_last_name
end
<%= search_form_for @q do |f| %>
  <%= f.label :author_cont %>
  <%= f.search_field :author_cont %>
<% end %>

bằng cách này cũng tiết kiệm được kha khá độ dài của cách đặt tên các trường search trong views.

Authorization (whitelisting/blacklisting)

Bằng cách định nghĩa whitelisting/blacklisting chúng ta có thể định nghĩa lại được các trường search của form từ trong model tương ứng với object thuộc class của model đó. Theo mặc định, tât cả các columns của model được cho phép searching và sorting, nghĩa là các columns đều nằm trong whitelist, và không có class methods/scopes nào nằm trong whitelisted. Ransack có thêm 4 medthods vào ActiveRecord::Base để bạn có thể redefine lại các class methods trong model có thể được cho phép sorting hoặc searching, đó là: ransackable_attributes, ransackable_associations, ransackable_scopesransortable_attributes. Dưới đây là cách triển khai cho 4 methods để ta có thể override lại:

ransackable_attributes

# `ransackable_attributes` mặc định trả về tất cả các column_names

def ransackable_attributes(auth_object = nil)
    column_names + _ransackers.keys
  end

Ta có thể định nghĩa chỉ những trường nào mà ta muốn:

def ransackable_attributes(auth_object = nil)
   %w(column_name1 column_name2 ...)
 end

ransackable_associations

Dùng để định nghĩa (ghi đè) những trường có thể search từ association.

  # `ransackable_associations` mặc định  returns  names
  # của tất cả associations như là một array of strings.
  # Để ghi đè một whitelist array of strings.
  #
  def ransackable_associations(auth_object = nil)
    reflect_on_all_associations.map { |a| a.name.to_s }
  end

Ta có thể định nghĩa lại các trường bên trong ransackable_associations một cách tương tự như ransackable_attributes bên trên.

ransortable_attributes

# mặc định trả về theo names
# tất cả các attributes có thể sắp xếp như một array strings
# dùng để override whitelist `ransortable_attributes`  mặc định

  def ransortable_attributes(auth_object = nil)
    ransackable_attributes(auth_object)
  end

Ta có thể định nghĩa lại các trường trong hàm ransortable_attributes tương tự như ransackable_attributes ở bên trên.

ransackable_scopes

Ta có thể thêm các scopes vào whitelist của các trường có thể search. Vì mặc định không có scope nào nằm trong whitelist, nên những scope được liệt kê ở đây đều là thêm vào :v

  def ransackable_scopes(auth_object = nil)
    %w(scope1, scope2 ...)
  end

Using Scopes/Class Methods

Bên trên tôi đã nói qua về add các scopes vào whitelist có thể search cho ransack, dưới đây là một ví dụ cụ thể cho trường hợp add scopes vào whitelist như thế. Và công việc add class methods vào whitelist cũng có thể được làm tương tự.

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

  def self.hired_since(date)
    where('start_date >= ?', date)
  end

  def self.ransackable_scopes(auth_object = nil)
    if auth_object.try(:admin?)
      # allow admin users access to all three methods
      %i(activated hired_since salary_gt)
    else
      # allow other users to search on `activated` and `hired_since` only
      %i(activated hired_since)
    end
  end

  private_class_method :ransackable_scopes
end

Một lưu ý đối với scope/class methods có sử dụng query select đó là: Nếu kết hợp với gem "kaminari" thì sẽ gặp vấn đề, bởi vì kaminari có override lại query select nên tác dụng của select trong scopes/ class methods sẽ bị mất tác dụng (do bị override). Thay vì dùng scopes/class methods thì bạn có thể dùng ransacker.

ransacker

Việc cài đặt bạn có thể xem tại ransacker để rõ hơn. Ở đây mình chỉ trình bày một số điểm khi tạo một ransacker. Ví dụ:

# in the model:
ransacker :created_at do
  Arel.sql('date(created_at)')
end
  • có thể gán câu truy vấn sql vào bên trong Arel.sql.
  • có thể truyền type cho kết quả của ransacker (mặc định sẽ là string) ex:
    ransacker :created_at, type: :date do
      # do anything
    end
    

Kết luận

Các vấn đề về ransack và ransacker khá rộng, trên đây chỉ là những tổng hợp của tôi về cách thêm/sửa các điều kiện, hay đúng hơn là các trường có thể search với ransack, trên cơ sở là vấn đề đã gặp trong công việc thực tế, và có tham khảo các tài liệu của ransack, ransacker. Bài viết còn có nhiều thiếu sót mong được các bạn đóng góp và góp ý thêm. Happy coding!

All Rights Reserved