Một trong những ấn tượng của mình khi làm việc với Rails là về ActiveRecord's scope. Dựa vào độ hiệu quả và dễ dàng tái sử dụng thì ActiveRecord chỉ đơn giản là tuyệt vời. Sau đây mình sẽ giới thiệu với các bạn về một vài tricks nhỏ khi làm việc với scope cùng ActiveRecord.
1. Join query with condition on the associated table
Giả sử model User có quan hệ với model Company
class User < ApplicationRecord
belong_to :company
...
end
class Company < ApplicationRecord
has_many :users
...
end
Và lúc này yêu cầu của khách hàng là chọn ra những users thuộc company còn hoạt động (actived: true). Việc này khá là đơn giản:
scope :actived, -> {joins(:company).where(companies: {actived: true})}
Tuy nhiên, việc này sẽ vi phạm về tính đóng gói (encapsulation) của hướng đối tượng (OOP) vì 1 phần code bên company đã leak sang bên user. Vậy để giải quyết việc này query sẽ chuyển thành như sau.
#Company model
scope :actived, -> where{actived: true}
#User model
scope :actived, -> {joins(:company).merge(Company.actived)}
Như vậy logic và những phần liên quan (concerns) được tách ra độc lập.
2. Different nested joins
Hãy cẩn thận với việc sử dụng query joins trong Rails. Mặc định khi viết scopes đơn lẻ nếu sử dụng joins thì ActiveRecord sẽ khi lại dưới dạng INNER JOIN tuy nhiên trong trường hợp dưới đây lại không phải vậy:
Giả sử có thêm model Course như sau:
class Course < ApplicationRecord
belong_to: company
end
class Company < ApplicationRecord
has_many :users
has_many :courses
Khi sử dụng câu lệnh joins
User.joins(:comapny).merge(Company.joins(:courses))
=>SELECT *
INNER JOIN companies ON companies.user_id = users.id
LEFT OUTER JOIN courses ON companies.course_id = courses.id
Để khắc phục tình huống này khá đơn giản với câu query: User.joins(company: :courses)
=>SELECT *
INNER JOIN companies ON companies.user_id = users.id
INNER JOIN courses ON companies.course_id = courses.id
3. Subqueries
Ví dụ bạn cần lấy list companies có chứa courses tạo trong tuần này. Đôi lúc bạn sẽ xử lý như sau:
Company.where(course_id: Course.created_in_week.pluck(:id))
Tuy nhiên, lúc này chúng ta lại phải chạy 2 câu query 1 câu để lấy subset course_ids và 1 câu where course_id từ company. Trong những trường hợp như vậy, ActiveRecord đã có xử lý cho chúng ta và bạn không cần phải pluck(:id) nữa, lúc này đây số lượng câu query sẽ chuyển từ 2 sang 1.
Company.where(course_id: Course.created_in_week)
4. Back to basics
ActiveRecord queries có thể chuyển hóa sang dạng raw SQL với câu lệnh .tosql, và .explain để có thể lấy được thông tin về performance, chi tiết câu query, ...
5. Conclusion
Trên đây là một vài tricks khi làm việc với ActiveRecord trong Rails mà mình rút ra, chúc các bạn thành công! Happy coding!