Giới thiệu searchkick - gem hỗ trợ tìm kiếm trong Rails
Tìm kiếm là tính năng không thể thiếu của một trang web thời nay, và ElasticSearch là cái tên quá nổi tiếng. Tuy nhiên, trong bài viết này, mình muốn đề cập đén searchkick - gem hỗ trợ tìm kiếm rất tốt, dễ sử dụng hơn ES và còn quen thuộc hơn với Ruby dev. Link gem Searchkick. Cùng tìm hiểu qua ...
Tìm kiếm là tính năng không thể thiếu của một trang web thời nay, và ElasticSearch là cái tên quá nổi tiếng. Tuy nhiên, trong bài viết này, mình muốn đề cập đén searchkick - gem hỗ trợ tìm kiếm rất tốt, dễ sử dụng hơn ES và còn quen thuộc hơn với Ruby dev.
Link gem Searchkick. Cùng tìm hiểu qua đôi chút về gem này.
Intelligent search made easy
it gets smarter and the results get better. It’s friendly for developers - and magical for your users
Theo như lời giới thiệu thì searchkick có thể giúp chúng ta xử lý một số vấn đề như:
- Tìm kiếm từ cùng một nguồn gốc: tomato, tomatoes...
- Ký tự đặc biệt: jalapeño.
- Xử lý khoảng trắng: hiểu dishwasher là dish washer.
- Sai chính tả: hiểu zuchini là zucchini.
- Từ đồng nghĩa: nhập qtip thì có thể tìm kiếm cả cotton swab
Cài đặt.
Để sử dụng, bạn cần phải cài đặt ElasticSearch, có thể tham khảo cách đơn giản bằng câu lệnh như sau:
sudo apt-get update sudo apt-get install elasticsearch sudo service elasticsearch start sudo service elasticsearch restart
Hoặc có thể cài đặt và chạy qua brew:
brew install elasticsearch brew services start elasticsearch
Cài đặt gem searchkick thì đơn giản chỉ cần thêm khai báo vào Gemfile và tiến hành bundle
# Gemfile gem 'searchkick'
Ở trong model nào cần dùng để tìm kiếm thì chỉ cần khai báo searchkick vào là được.
- Chú ý là phiên bản mới nhất của searchkick làm việc được với ElasticSearch 2 và 5. Còn với ElasticSearch 1 thì phải sử dụng phiên bản 1.5.1
Hãy cùng nhau tìm hiểu qua một chút về gem này.
Query
Query trong searchkick tương tự như trong SQL. ex:
Product.search( 'apples', fields: [:name, :brand], where: { in_stock: true, store_id: { not: [25,30, 35] } #store_id not in [25,30,35] }, limit: 10, offset: 50, order: { _score: :desc } )
Nếu muốn tìm kiếm tất cả thì dùng search với '*'.
Nếu làm việc với SQL rồi thì chắc hẳn bạn sẽ rất dễ hiểu.
Kết quả tìm kiếm.
Sau khi tiến hành tìm kiếm bằng searchkick, ta sẽ có một đối tượng Searchkick::Results, đối tượng này có những method gần giống như một array.
results = Product.search('milk') results.size results.any? results.each { |result| ... }
Vì ElasticSearch đã tiến hành đánh index lại các bản ghi (trường id được đánh index), nên các id sẽ được lấy về từ ElasticSearch còn nội phần còn lại của bản ghi thì sẽ được lấy ra từ database. Nếu bạn muốn lấy về mọi thứ từ ElasticSearch thì hãy thêm key load: false vào method `search
Ngoài ra còn có một số method khác với đối tượng results như:
results.total_count # Get total results results.took # Get the time the search took (in milliseconds) results.response # Get the full response from Elasticsearch
Boosting
Cũng tương tự như trong ElasticSearch
# Tìm kiếm với trường title được ưu tiên hơn description Product.search('milk', fields: ['title^10', 'description']) Product.search('milk', boost_by: { orders_count: { factor: 10 } })
Phân trang.
Kết quả tìm kiếm có thể dễ dàng được phân trang với key page và per_page
Tìm kiếm từng phần
Mặc định, kết quả tìm kiếm phải khớp với tất cả các từ trong đầu vào (mặc định sử dụng phép toán AND). ex:
Product.search 'fresh honey' # fresh AND honey
Để tìm kiếm được từng phần của đầu vào thì bạn hãy thêm key operator: 'or' vào phương thức search.
Với từng từ trong chuỗi đầu vào, kết quả tìm kiếm phải khớp với toàn bộ từ đó. Ví dụ đầu vào là back thì sẽ không tìm kiếm được backpack. Để cải thiện việc này thì bạn phải thêm option word_start (mặc định là word) với tên trường vào khai báo searchkich. ex:
class Product < ActiveRecord::Base searchkick word_start: [:name] end
Và thêm vào cả method search (sau khi bạn đã reindex - cái này mình sẽ đề cập sau) như sau:
Product.search 'back', fields: [:name], match: :word_start
Còn một số option khác mà searchkick cũng hỗ trợ như: word, word_start, word_middle, word_end, text_start,...
Ngôn ngữ
searchkick mặc định tìm kiếm với tiếng Anh, nhưng bạn cũng có thể sử dụng với ngôn ngữ khác bằng cách khai báo key language trong model.
Tham khảo danh sách các ngôn ngữ được hỗ trợ ở đây, tiếc là chưa có tiếng Việt.
Tìm kiếm từ đồng nghĩa.
Việc này sẽ phải thực hiện hơi thủ công hơn một chút bằng cách thêm key synonyms vào khai báo searchkick. Có 2 cách:
- Khai báo trực tiếp synonyms là một mảng chứa các mảng con các cặp từ đồng nghĩa, ex:
class Product < ActiveRecord::Base searchkick synonyms: [["scallion", "green onion"], ["qtip => cotton swab"]] end
- Khai báo synonyms là đường dẫn đến file chứa các cặp từ đồng nghĩa.
Sau khi khai báo synonyms thì bạn phải tiến hành dánh index lại bằng method .reindex
Lỗi chính tả.
searchkick xử lý việc sai chính tả bằng cách tính khoảng cách Levenshtein. Mặc định, text sẽ khớp nếu như khoảng cách đó là 1.
ex khoảng cách giữa kitten và sitting là 3, vì bạn cần ít nhất 3 thay đổi để biến đổi từ này thành từ kia:
-
kitten → sitten (thay thế ký tựk bằng s)
-
sitten → sittin (thay thế ký tự e bằng i)
-
sittin → sitting (chèn thêm ký tự g vào cuối)
Bạn có thể customer lại khoảng cách tính toán mà bạn muốn bằng key misspellongs: { edit_distance: 2 }
Kết quả không mong muốn.
Bạn có thể nạp vào key exclude để loại bỏ những kết quả không mong muốn tương ứng với từng đầu vào, ex:
Product.search 'cream', exclude: ['ice cream', 'whipped cream']
Đánh index.
Để kiểm soát những dữ liệu nào sẽ được đánh index thì ta dùng hàm search_data, ex:
class Product < ActiveRecord::Base belongs_to :department def search_data { name: name, department_name: department.name, on_sale: sale_price.present? } end end
searchkick dùng method find_in_batches để load dữ liệu, nên bạn cũng có thể tiến hành eager load các quan hệ để tăng tốc độ cũng như tránh tình trạng N+1 query, sử dụng scope search_import như sau:
class Product < ActiveRecord::Base scope :search_import, -> { includes(:department) } end
Mặc định, searchkick sẽ đánh index cho tất cả bản ghi. Để tránh việc phải tiến hành đánh index cho cả những bản ghi không cần thiết thì bạn phải sử dụng method should_index? cùng với scope search_import như sau:
class Product < ActiveRecord::Base scope :search_import, -> { where(active: true) } def should_index? active # only index active records end end
Sau những bước này thị bạn cũng cần tiến hành đánh index lại cho model.
Khi nào cần đánh index lại.
Trong một số trường hợp thì bạn cần phải tiến hành đánh lại index cho model của mình bằng method .reindex:
-
Khi bạn cài đặt hoặc nâng cấp phiên bản searchkick.
-
Khi thay đổi method search_data như đã đề cập ở trên.
-
Khi bạn thay đổi method khai báo searchkick.
Ngoài ra thì seachkick còn hỗ trợ một số tính năng khác cho tìm kiếm như autocomplete, gợi ý... Mình sẽ cố gắng làm một cái demo đơn giản sử dụng những tính năng này sau nhé.
Cảm ơn bạn đã đọc bài viết.
Tham khảo.
- https://github.com/ankane/searchkick
- https://en.wikipedia.org/wiki/Levenshtein_distance
- https://www.elastic.co/guide/index.html