Queries trong Active Record Rails
Dưới đây là một số thủ thuật trong việc truy vấn ActiveRecord trong Rails. Giả sử bạn có table User và Profile có liên kết với nhau. Nếu bạn muốn truy vấn 1 User với Profile đã được validated thì bạn làm như sau. # User model scope :activated, ->{ joins(:profile).where(profiles: { ...
Dưới đây là một số thủ thuật trong việc truy vấn ActiveRecord trong Rails.
Giả sử bạn có table User và Profile có liên kết với nhau. Nếu bạn muốn truy vấn 1 User với Profile đã được validated thì bạn làm như sau.
# User model scope :activated, ->{ joins(:profile).where(profiles: { activated: true }) }
Trông có vẻ ổn đúng không, nhưng thật ra thì đây là một cách làm sai, chúng ta đã vô tình để lộ rõ thông tin của Profile model ở User model.
Để làm tốt hơn về điều này thì chúng ta có thể làm như sau:
# Profile model scope :activated, ->{ where(activated: true) } # User model scope :activated, ->{ joins(:profile).merge(Profile.activated) }
Với cách làm như trên thì chúng ta đã có thể giữ được sự tách biệt và logic của 2 model.
Hãy cẩn thận với cách bạn sử dụng JOIN trong ActiveRecord, giả sử chúng ta có User has_one Profile, và Profile thì has_many Skill model. Thứ chúng ta cần sẽ là INNER JOIN cho trường hợp này, nhưng nếu chúng ta thực hiện như sau:
User.joins(:profiles).merge(Profile.joins(:skills))
thì truy vấn của nó sẽ như thế này :
SELECT users.* FROM users INNER JOIN profiles ON profiles.user_id = users.id LEFT OUTER JOIN skills ON skills.profile_id = profiles.id
Chúng ta có thực hiện tốt hơn như thế này :
User.joins(profiles: :skills) => SELECT users.* FROM users INNER JOIN profiles ON profiles.user_id = users.id INNER JOIN skills ON skills.profile_id = profiles.id
Giả sử bạn có User model, và bạn muốn lấy các user có post nổi tiếng. và việc đầu tiên bạn muốn hướng đến là sử dung NOT EXISTS Bạn có thể thực hiện như dưới đây :
# Post scope :famous, ->{ where("view_count > ?", 1_000) } # User scope :without_famous_post, ->{ where(_not_exists(Post.where("posts.user_id = users.id").famous)) } def self._not_exists(scope) "NOT #{_exists(scope)}" end def self._exists(scope) "EXISTS(#{scope.to_sql})" end
Giả sử bạn muốn lấy Post từ User model. Và chúng ta có thể thực như thế này:
Post.where(user_id: User.created_last_month.pluck(:id))
Nhưng với cách trên thì sẽ có 2 cầu truy vấn SQL được chạy đó là, một cái sẽ lấy ids của User, và một cái sẽ thực hiện lấy Post từ ids của user. Bạn có thế thực hiện nó 1 cách tốt hơn, với Subqueries như sau.
Post.where(user_id: User.created_last_month)
ActiveRecord sẽ thực hiện xử lý cho bạn.