Scope trong rails cách sử dụng và điểm khác biệt giữa class method
Xin chào các bạn. Hôm nay mình xin viết về Scope trong ruby on rails. Các scope được hỗ trợ bởi rails, giúp định nghĩa các điều kiện truy vấn, chúng ta có thể kết nối nhiều scope với nhau mà không tạo ra nhiều câu truy vấn. Về bản chất thì scope là 1 class method (có thể gọi là class method động) ...
Xin chào các bạn. Hôm nay mình xin viết về Scope trong ruby on rails. Các scope được hỗ trợ bởi rails, giúp định nghĩa các điều kiện truy vấn, chúng ta có thể kết nối nhiều scope với nhau mà không tạo ra nhiều câu truy vấn. Về bản chất thì scope là 1 class method (có thể gọi là class method động)
class Shirt < ActiveRecord::Base def self.red where(color: 'red') end end
mình có thể viết scope thành:
class Shirt < ActiveRecord::Base scope :red, -> {where(color: 'red')} end
Trước khi nói đến sự khác nhau giữa scope và class method mình xin được nói đến 1 vài cách viết scope đơn giản nhất
Đầu tiên ta có 1 model HistoryUpload
class CreateHistoryUploads < ActiveRecord::Migration def change create_table :history_uploads do |t| t.integer :type_id t.string :file_path t.timestamps null: false end end end class HistoryUpload < ActiveRecord::Base end
Lấy ra những record type_id có trong bảng history_uploads
scope :search_type_id, -> type_id {where type_id: type_id}
Lấy những record type_id có file_path trong bảng history_uploads
scope :search_type_id_and_type_path, -> type_id, type_path {where type_id: type_id, type_path: type_path}
Điều kiện OR trong scope
scope :search_type_id_or_type_path, -> type_id, type_path {'type_id IN ? OR type_path IN ?' type_id, type_path}
hoặc có thể viết ntn nếu không thích viết chuỗi string trong scope
scope :search_type_id_or_type_path, ->type_id, type_path do ransack( g: { type_id: {type_id_in: type_id}, type_path: {type_path_in: file_path} }, m: "or").result end
Với việc sắp xếp bằng scope: đơn giản như:
scope :sort_by_created_at, ->{order created_at: :desc}
dùng scope để join bảng thì giờ mình sẽ tạo 1 bảng nữa : file_upload
class CreateFileUploads < ActiveRecord::Migration def change create_table :file_uploads do |t| t.integer :history_upload_id t.timestamps null: false end end end class FileUpload < ActiveRecord::Base belongs_to :history_upload end
scope :join_historyupload, -> {joins(:history_uploads)}
Vậy tại sao sử dụng scope thay class method. cho dù nó chỉ là cách viết khác của class method. Tất nhiên khi sử dụng scope sẽ có cái khác so với class method.
Đầu tiên phải nói đến method chain luôn luôn được đảm bảo thực hiện.
Giả sử ta sẽ so sánh scope:
#scope class HistoryUpload < ActiveRecord::Base scope :search_type_id, -> type_id {where type_id: type_id} end
#class_method class HistoryUpload < ActiveRecord::Base def self.search_type type_id where(type_id: type_id) end end
kết quả chúng ta thu được là khi type_id là nil or ""
#scope HistoryUpload Load (0.2ms) SELECT `history_uploads`.* FROM `history_uploads` WHERE `history_uploads`.`type_id` = NULL => #<ActiveRecord::Relation []>
#class_method HistoryUpload Load (0.2ms) SELECT `history_uploads`.* FROM `history_uploads` WHERE `history_uploads`.`type_id` = NULL => #<ActiveRecord::Relation []>
Vậy để đảm bảo khả năng method_chain ta sẽ thêm điều kiện type_id.present?
#scope class HistoryUpload < ActiveRecord::Base scope :search_type_id, -> type_id {where type_id: type_id type_id.present?} end
#class_method class HistoryUpload < ActiveRecord::Base def self.search_type type_id where(type_id: type_id) type_id.present? end end
kết quả thu được khi chúng ta đã thay đổi
#scope SELECT `history_uploads`.* FROM `history_uploads` => #<ActiveRecord::Relation [#<HistoryUpload id: 46, type_id: 1,....]
#class_method nil
KQ: Với cách viết như trên, các scope chắc chắn sẽ thực hiện chain mà không gặp phải vấn đề gì. Tức là bạn luôn luôn thu được object chứ ko phải 1 giá trị nil
1 điều nữa là scope luôn luôn diễn đạt mục đích 1 cách rõ ràng mang lại nhiều thông điệp hơn 1 class method.
điểm hạn chế của scope là nhiều lúc sẽ trở nên phức tạp, rắc rối hơn. khi viết code của mình trong 1 class method tường tận và rõ ràng.
thử hình dùng 1 scope có cả sort, select, join hay được viết mạnh lạc trong class method và có thể xử lý những logic phức tập + sử dụng được những scope sẵn có.
scope :test_scope, -> file_path { joins(:history_uploads).where('file_path: file_path').order("created_at: :desc") }
Cá nhân mình nghĩ việc sử dụng class method or scope là do mỗi người. vì nhìn chung scope cũng chỉ là 1 class method mà thôi.