Một vài cách để viết scope đa dạng hơn
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 ...
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.