Một vài cách để viết scope đa dạng hơn
Bài đăng này đã không được cập nhật trong 8 năm
Trong việc code ruby, đôi khi bạn phải viết những đoạn scope, tip nhỏ sau đây hi vọng giúp bạn phần nào trong việc viết scope, giả sử ta có model class_room :
create_table "class_rooms", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
class ClassRoom < ActiveRecord::Base
end
Scope đơn giản nhất để lấy ra các phần tử có id nằm trong mảng ids:
scope :by_ids, ->ids{where id: ids}
Phức tạp hơn 1 tẹo là lấy ra các phần tử có id thuộc ids và có name thuộc mảng names
scope :by_ids_and_name, ->ids, names{where id: ids, name: names}
Còn trong trường hợp lấy ra các phần tử có id thuộc ids hoặc name thuộc mảng names, ở đây là điều kiện hoặc (OR) nên câu scope là:
scope :by_ids_or_name, ->ids, name{where 'id IN ? OR name IN ?', ids, names}
Nếu như không muốn đưa string vào câu query thì ta dùng ransack (link tham khảo https://github.com/activerecord-hackery/ransack) scope sẽ là:
scope :by_ids_or_names_ransack, ->ids, names do
ransack(
g: {
by_ids_condition: {id_in: ids},
by_name_condition: {name_in: names}
}, m: "or").result
end
Chú ý m: “or” là đưa vào điều kiện OR trong câu query
Giả sử bây giờ thêm trường student_names trong class_room
create_table "class_rooms", force: :cascade do |t|
t.string "name"
t.text "student_names"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
chứa dãy các name của student trong class_room phân biệt bởi dấu ',', VD 'Nguyen_Van_A, Nguyen_Van_B, Nguyen_Van_C', tên mỗi người được viết liền, scope lấy ra các class_room có tên 1 vài student nằm trong mảng tên các student_names
scope :by_student_names, ->student_names do
where create_sql_command student_names
end
Tạo thêm method create_sql_command:
class << self
def create_sql_command student_names
student_names.map do |student_name|
[
"(student_names LIKE '#{student_name}')",
"(student_names LIKE '#{student_name},%')",
"(student_names LIKE '%, #{student_name}')",
"(student_names LIKE '%, #{student_name},%')",
]
end.join " OR "
end
end
Thực chất đây là xét các trường hợp mà student_names chứa string là 1 phần tử có trong mảng đưa vào. Viết bằng ransack sẽ là:
scope :by_student_names_ransack, ->student_names do
ransack(g: create_ransack_hash(student_names), m: "or").result
end
Thêm method create_ransack_hash
def create_ransack_hash student_names
student_names.each_with_index.inject({}) do |ransack_hash, (student_name, index)|
ransack_hash.merge! "student_name_#{index}_condition" => {student_names_cont: " #{student_name},"}
end
end
(Đối với trường hợp này trước lúc đẩy data vào cần thêm dấu cách ở đầu và dấu , ở cuối).
Đối với việc sắp xếp, scope quen thuộc là
scope :sort_by_created_at, ->{order created_at: :asc}
Giả sử ta có thêm bảng time_period
class TimePeriod < ActiveRecord::Base
belongs_to :class_room
end
create_table "time_periods", force: :cascade do |t|
t.integer "class_room_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Để sort class_room theo thời điểm tạo time_period
scope :by_first_time_period, -> do
joins(:time_periods)
.where("time_periods.created_at =
(SELECT Min(time_periods.created_at)
FROM time_periods WHERE time_periods.class_room_id = class_rooms.id)")
.order "time_periods.created_at ASC"
end
Dùng ransack thì sẽ là:
scope :by_first_time_period_ransack, -> do
joins(:time_periods)
.ransack(g: {a: {name_cont: "name"}}, sorts: 'time_periods_created_at asc').result.group(:id)
end
(Chỗ này thêm 1 option name có chứa chữ 'name')
Cảm ơn bạn đã đọc và hi vọng bài viết giúp ích trong công việc của bạn, nhất là đối với những người tiếp xúc với ruby chưa nhiều như tôi.
All rights reserved