Giới thiệu searchkick - gem hỗ trợ tìm kiếm trong Rails
Bài đăng này đã không được cập nhật trong 3 năm
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ớiElasticSearch
2 và 5. Còn vớiElasticSearch
1 thì phải sử dụng phiên bản1.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ằngs
) -
sitten → sittin (thay thế ký tự
e
bằngi
) -
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.
All rights reserved