Scope trong rails cách sử dụng và điểm khác biệt giữa class method
Bài đăng này đã không được cập nhật trong 8 năm
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.
All rights reserved