Queries trong Active Record Rails

Dưới đây là một số thủ thuật trong việc truy vấn ActiveRecord trong Rails.

Truy vấn JOIN với điều kiện trong table có liên kết.

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.

Nested JOIN

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

Truy vấn với Exist.

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

Subqueries

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. 😄

Cơ bản hơn:

Chúng ta có thể sử dụng ActiveRecord queries bằng cách nối chuổi khi sử dụng .tosql để generate ra chuỗi SQL, và .explain để lấy chi tiết ... Trên đây là một số ActiveRecord queries cơ bản và những lỗi thường mắc phải và cách khác phục nó.