Tạo api search sử dụng Grape api và Ransack gem
Đề bài: Tạo 2 model User và Address như schema bên dưới: Viết một api tìm kiếm user theo các trường: full name, giới tính, email, địa chỉ đường, quận, thành phố 1. Giới thiệu Để giải quyết bài toán trên, mình sẽ sử dụng 2 gem Grape API và Ransack. Trong bài này mình chỉ tập trung giới ...
Đề bài: Tạo 2 model User và Address như schema bên dưới:
Viết một api tìm kiếm user theo các trường: full name, giới tính, email, địa chỉ đường, quận, thành phố
1. Giới thiệu
Để giải quyết bài toán trên, mình sẽ sử dụng 2 gem Grape API và Ransack. Trong bài này mình chỉ tập trung giới thiệu về Ransack, Grape API mình sẽ dành cho bài khác
-
Mô tả
Ransack is a rewrite of MetaSearch created by Ernie Miller and developed/maintained for years by Jon Atack and Ryan Bigg with the help of a great group of contributors. Ransack's logo is designed by Anıl Kılıç. While it supports many of the same features as MetaSearch, its underlying implementation differs greatly from MetaSearch, and backwards compatibility is not a design goal. (Tiếng anh cho dễ hiểu nhé, mình dịch gà lắm)
-
Cú pháp
Cú pháp để gọi search Ransack:
Model.ransack(query).result
Vậy query này là gì, build ra sao?
query là một Hash, tập hợp các key-value nhằm mục đích cung cấp cho Ransack các thông tin về trường cần tìm kiếm, dữ liệu cần tìm kiếm và điều kiện cần tìm kiếm
Key trong query có cấu trúc như thế nào? Cấu trúc chung của các key có dạng <attribute_name>_<operator>.
attribute_name build như sau
- Nếu tìm kiếm các trường của Model thì attribute_name chính là tên các trường cần tìm kiếm.
- Nếu cần tìm kiếm từ các trường của assocical liên kết trực tiếp (Không sử dụng through) thì attribute_name có dạng <assocical_name>_<attr_name_of_assocical>. VD: addresses_id, addresses_street...
- Nếu cần tìm kiếm các trường từ assocical liên kết gián tiếp (though qua assocical khác) thì attibute_name có dạng <accocical_name_through><assocical_name_focus><attr_name_of_assocical_focus>. VD:
class Author has_many :posts has_many :comments, through: posts end
thì attibute_name sẽ là posts_comments_id, posts_comments_content...
Có một key đặc biệt là m, key này có 2 value: 'or' hoặc 'and'. Mục đích của key-value này là nhóm các key-value còn lại theo điều kiện or hoặc and. Ví dụ như tìm người có tên chứa ký tự A hoặc có email là B...bla bla
operator là các toán tử như cont(chứa), eq(bằng), gt(lớn hơn)... xem danh sách các toán tử ở đây
2. Thực hiện
-
Cài đặt gems
Thêm 2 dòng sau vào Gemfile và chạy bundle install để cài đặt chúng
gem "ransack", github: "activerecord-hackery/ransack" gem "grape"
-
Code
Tạo 2 file migrate cho 2 model User và Address như sau và nhớ chạy rails db:migrate nhé:
# db/migrate/20181009101901_create_user.rb class CreateUser < ActiveRecord::Migration[5.2] def change create_table :users do |t| t.string :first_name, null: false t.string :last_name, null: false t.string :email t.string :address t.datetime :date_of_birth t.string :phone t.integer :gender t.timestamps end end end
# db/migrate/20181009104754_create_address.rb class CreateAddress < ActiveRecord::Migration[5.2] def change create_table :addresses do |t| t.references :user, foreign_key: true t.string :province t.string :district t.string :street t.timestamps end end end
Tạo 2 model trên:
# app/models/user.rb class User < ApplicationRecord has_many :addresses enum gender: [:male, :female] def full_name "#{last_name} #{first_name}" end end
# app/models/address.rb class Address < ApplicationRecord belongs_to :user end
Như đề bài yêu cầu search full name, do vậy ta cần ghép 2 cột last name và first name lại. Vì data user là người Việt nên mình ghép last name rồi tới first name nhé. Thêm đoạn code sau
# app/models/user.rb .... ransacker :full_name, formatter: proc{|v| v.mb_chars.downcase.to_s} do |parent| Arel::Nodes::NamedFunction.new("lower", [Arel::Nodes::NamedFunction.new("concat_ws", [Arel::Nodes.build_quoted(" "), parent.table[:last_name], parent.table[:first_name]])]) end
Cách viết câu query search của Ransack rất dễ hiểu. Để search các trường trực tiếp từ model chỉ cần đặt theo cấu trúc <attr_name>_<operator> (operator như cont-chứa, eq-bằng...), search theo các trường từ assocical chỉ cần gắn thêm assocical name ở phía trước nữa là được. Nhiều khi gắn assocical name với attr name làm cho code thêm dài thườn thượt, Ransack hỗ trợ đặt alias cho chúng ta. Như ví dụ hiện tại sẽ thử luôn với assocical addresses
# app/models/user.rb ... ransack_alias :addr_province, :addresses_province ransack_alias :addr_district, :addresses_district ransack_alias :addr_street, :addresses_street
Tạo Api search user
#app/api/v1/user_api.rb class V1::UserAPI < Grape::API resources :users do desc "Search user" params do optional :full_name, type: String optional :gender, type: String, values: User.genders.keys optional :email, type: String optional :street, type: String optional :district, type: String optional :province, type: String end get "search" do query = Hash.new.tap do |q| q[:full_name_cont] = params[:full_name] if params[:full_name].present? q[:gender_eq] = params[:gender] if params[:gender].present? q[:email_cont] = params[:email] if params[:email].present? q[:addr_street_cont] = params[:street] if params[:street].present? q[:addr_district_cont] = params[:district] if params[:district].present? q[:addr_province_cont] = params[:province] if params[:province].present? end users = User.includes(:addresses).ransack(query).result {result: Entities::V1::User.represent(users)} end end end
-
Test
Code xong rồi, test thôi, và đây là kq chạy test
Tìm kiếm theo tên
Tìm kiếm theo email
Tìm kiếm theo giới tính
Tìm kiếm theo tên đường
Tìm kiếm theo tên quận
Note: Nếu kèo source code mình về nhớ chạy rails db:seed để import data nhé
-
3. Tổng kết
Mình vừa hướng dẫn các bạn cách viết api search sử dụng gem Ransack và Grape api, source ở đây. Chúc các bạn thành công!