12/08/2018, 10:49

Advance search with Ransack Gem

I. Tổng quan Ransack là một gem được sử dụng để tìm kiếm dữ liệu, 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. Trong bài viết Tìm hiểu về Ransack Gem và ứng dụng trong tìm kiếm đã hướng dẫn cách sử dụng Ransack với ...

I. Tổng quan

Ransack là một gem được sử dụng để tìm kiếm dữ liệu, 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.

Trong bài viết Tìm hiểu về Ransack Gem và ứng dụng trong tìm kiếm đã hướng dẫn cách sử dụng Ransack với hai phương thức tìm kiếm trên.

Bài viết này sẽ bổ sung thêm một vài cách sử dụng và tùy biến Ransack cho các vấn đề tìm kiếm khác nhau.


II. Các vấn đề bổ sung

1. Các hàm search trong Ransack

Phương thức search cơ bản trong Ransack được biết đến như là các hậu tố (predicates)

Các hậu tố được dùng trong các truy vấn tìm kiếm trong Ransack để xác định thông tin đối chiếu.

Dưới đây là một vài hậu tố của Ransack hay được dùng:

eq (equals)

Hậu tố eq trả về tất cả các record có một trường bằng đúng với gía trị tìm kiếm:

 User.ransack(first_name_eq: "Ryan").result.to_sql
=> SELECT "users".* FROM "users" WHERE "users"."first_name" = "Ryan"

not_eq: thì ngược lại eq

matches

Trả về các record có trường giống với một gía trị tìm kiếm:

User.ransack(first_name_matches: "Ryan").result.to_sql
=> SELECT "users".* FROM "users" WHERE ("users"."first_name" LIKE "Ryan")

does_not_match ngược lại matches

lt(less than)

Trả về tất cả các record có trường nhỏ hơn gía trị tìm kiếm

User.ransack(age_lt: 25).result.to_sql
SELECT "users".* FROM "users" WHERE ("users"."age" < 25)

gt (greater than) ngược lại lt

lteq (less than equal to)

Trả về tất cả các record nhỏ hơn hoặc bằng gía trị tìm kiếm

User.ransack(age_lteq: 25).result.to_sql
=> SELECT "users".* FROM "users" WHERE ("users"."age" <= 25)

gteq (greater than or equal to) ngược lại với lteq

in

Hậu tố in trả về các record có trường nằm trong một danh sách có sẵn:

User.ransack(age_in: 20..25).result.to_sql
=> SELECT "users".* FROM "users" WHERE "users"."age" IN (20, 21, 22, 23, 24, 25)

Ngược lại với innot_in

cont

Hậu tố cont trả về tất cả các record có trường chứa gía trị tìm kiếm

User.ransack(first_name_cont: 'Rya').result.to_sql
=> SELECT "users".* FROM "users"  WHERE ("users"."first_name" LIKE '%Rya%')

Ngược lại là not_cont

start (start with)

Trả về các record có trường bắt đầu bằng gía trị tìm kiếm

User.ransack(first_name_start: 'Rya').result.to_sql
=> SELECT "users".* FROM "users"  WHERE ("users"."first_name" LIKE 'Rya%')

Ngược lại là not_start

end (ends with) Trả về các record có trường kết thúc bằng gía trị tìm kiếm

User.ransack(first_name_end: 'yan').result.to_sql
=> SELECT "users".* FROM "users"  WHERE ("users"."first_name" LIKE '%yan')

Ngược lại là not_end


2. Sắp xếp với Ransack

Trong Ransack sử dụng helper sort_link để tạo bảng có các header được gẵn với link sort

<%= sort_link(@q, :name) %>

Có thể bổ sung thêm tên cột hay thứ tự sắp xếp mặc định:

<%= sort_link(@q, :name, 'Last Name', default_order: :desc) %>

Với quan hệ polymorphic, cần chỉ rõ tên model:

<%= sort_link(@q, :xxxable_of_Ymodel_type_some_attribute, 'Attribute Name') %>

Có thể sort nhiều trường thông qua mảng thứ tự cụ thể:

<%= sort_link(@q, :last_name, [:last_name, 'first_name asc'], 'Last Name') %>

Ngoài ra, có thể định nghĩa một thứ tự sort mặc định trong controller:

@search = Post.ransack(params[:q])
@search.sorts = 'name asc' if @search.sorts.empty?
@posts = @search.result.paginate(page: params[:page], per_page: 20)

3 Trộn Search

Để tìm các record phù hợp với đa tìm kiếm, thì có thể trộn tất cả các điều kiện search vào quan hệ ActiveRecord để thực hiện một truy vấn đơn. Để tránh bị conflict giữa tên các bảng, cần tạo một bối cảnh chung lưu lại tên định danh bảng được dùng cho tất cả các điều kiện trước khi khởi tạo qúa trình search

shared_context = Ransack::Context.for(Person)

search_parents = Person.ransack(
  { parent_name_eq: "A" }, context: shared_context
)

search_children = Person.ransack(
  { children_name_eq: "B" }, context: shared_context
)

shared_conditions = [search_parents, search_children].map { |search|
  Ransack::Visitor.new.accept(search.base)
}

Person.joins(shared_context.join_sources)
  .where(shared_conditions.reduce(&:or))
  .to_sql

từ đó sẽ sinh ra câu truy vấn

SELECT "people".*
FROM "people"
LEFT OUTER JOIN "people" "parents_people"
  ON "parents_people"."id" = "people"."parent_id"
LEFT OUTER JOIN "people" "children_people"
  ON "children_people"."parent_id" = "people"."id"
WHERE (
  ("parents_people"."name" = 'A' OR "children_people"."name" = 'B')
  )
ORDER BY "people"."id" DESC




4 Tìm kiếm đa hình (Polymorphic searches)

Khi tạo ra các tìm kiếm từ các polymorphic models thì cần phải chỉ rõ kiểu model đang tìm

Vd: với 2 models:

class House < ActiveRecord::Base
  has_one :location, as: :locatable
end

class Location < ActiveRecord::Base
  belongs_to :locatable, polymorphic: true
end

Bình thường nếu không sử dụng quan hệ polymorphic, ta sẽ search theo:

Location.ransack(locatable_number_eq: 100).result

Tuy nhiên, dòng lệnh trên sẽ báo lỗi:

ActiveRecord::EagerLoadPolymorphicError: Can not eagerly load the polymorphic association :locatable

Để có thể search location theo house number khi quan hệ là polymorphic thì phải sử dụng:

Location.ransack(locatable_of_House_type_number_eq: 100).result

với _of_House_type_ được thêm vào search key. Điều đó cho phép Ransack chỉ rõ tên bảng trong join querries.


III Lời kết

Ransack là một gem rất hữu ích, hỗ trợ đầy đủ để tùy biến search và sort dữ liệu dễ dàng.

Nguồn tham khảo:

https://github.com/activerecord-hackery/ransack

http://www.sitepoint.com/advanced-search-ransack

0