Sử dụng Arel trong Rails

Hầu hết các developer Rails đều quen thuộc sử dụng các truy vấn Active Record, rất ít sử dụng truy vấn với Arel. Nếu không biết đến Arel thì đây là một thiếu xót không hề nhỏ đối với các nhà phát triển. Bởi không có gì hoàn hảo cả. Active Record cũng có nhược điểm: trong mệnh đề WHERE Active Record chỉ hỗ trợ truy vấn AND mà không hỗ trợ OR (cho đến tận rails 5), hay việc so sánh các chỉ cho các toán tử =, IN mà không có các toán tử >, <, >=, .... Để khắc phục nhược điểm này mình phải viết lại phần code này bằng SQL thuần. Với nhược điểm này nó đã được khắc phục bởi Arel. Bản thân Arel cũng có những điểm mạnh:

  • Đơn giản hóa việc tạo ra các truy vấn SQL phúc tạp
  • Tương thích với nhiều loại DB

Arel có rất nhiều powerfull và feature-rich technology. Nhưng trong bài viết tôi sẽ tập trung các tính năng mà bạn sử dụng thường xuyên nhất:

Arel Table

Để get Arel::Table instance của một model, ta có 2 option:

  • Sử dụng class method của ActiveRecord:
users = User.arel_table
  • Sử dụng constructor của class Arel::Table:
users = Arel::Table.new(:users)

SelectManager

users.project(users[:id])
# => SELECT users.id FROM users

Comparison operators =, !=, <, >, <=, >=, IN:

User.where(users[:age].eq(10))
# => SELECT * FROM "users"  WHERE "users"."age" = 10

User.where(users[:age].not_eq(10))
# => SELECT * FROM "users"  WHERE "users"."age" != 10

User.where(users[:age].lt(10))
# => SELECT * FROM "users"  WHERE "users"."age" < 10

User.where(users[:age].gt(10))
# => SELECT * FROM "users"  WHERE "users"."age" > 10

User.where(users[:age].lteq(10))
# => SELECT * FROM "users"  WHERE "users"."age" <= 10

User.where(users[:age].gteq(10))
# => SELECT * FROM "users"  WHERE "users"."age" >= 10

User.where(users[:age].in([20, 16, 17]))
# => SELECT * FROM "users"  WHERE "users"."age" IN (20, 16, 17)

Joins

User.joins(:photos).on(users[:id].eq(photos[:user_id]))
# => SELECT * FROM users INNER JOIN photos ON users.id = photos.user_id
User.joins(:devices).where(users[:created_at].gt(1.day.ago))

Left joins

User.join(photos, Arel::Nodes::OuterJoin).on(users[:id].eq(photos[:user_id]))
# => SELECT FROM users LEFT OUTER JOIN photos ON users.id = photos.user_id

Function AVG, SUM, COUNT, MIN, MAX, HAVING

photos.group(photos[:user_id]).having(photos[:id].count.gt(5))
# => SELECT FROM photos GROUP BY photos.user_id HAVING COUNT(photos.id) > 5

users.project(users[:age].sum)
# => SELECT SUM(users.age) FROM users

users.project(users[:age].average)
# => SELECT AVG(users.age) FROM users

users.project(users[:age].maximum)
# => SELECT MAX(users.age) FROM users

users.project(users[:age].minimum)
# => SELECT MIN(users.age) FROM users

users.project(users[:age].count)
# => SELECT COUNT(users.age) FROM users

OR, AND queries

User.where(users[:name].eq("Bob").or(users[:surname].eq("Bob")))
# => SELECT "users".* FROM "users" WHERE ("users"."name" = 'Bob' OR "users"."surname" = 'Bob')

name_clause = arel[:name].eq("Bob").and(arel[:surname].eq(nil))
surname_clause = arel[:name].eq(nil).and(arel[:surname].eq("Bob"))
User.where(name_clause.or(surname_clause))
# => SELECT "users".* FROM "users" WHERE ("users"."name" = 'Bob' AND "users"."surname" IS NULL OR "users"."name" IS NULL AND "users"."surname" = 'Bob')
users = User.arel_table
name_clause = arel.grouping(users[:name].eq("Bob").and(users[:surname].eq(nil)))
surname_clause = users.grouping(arel[:name].eq(nil).and(users[:surname].eq("Bob")))
User.where(name_clause.or(surname_clause))
# => SELECT "users".* FROM "users" WHERE (("users"."name" = 'Bob' AND "users"."surname" IS NULL) OR ("users"."name" IS NULL AND "users"."surname" = 'Bob'))

LIKE queries

User.where(User.arel_table[:name].lower.matches("Bob".downcase))
# => SELECT "users".* FROM "users" WHERE (LOWER("users"."name") ILIKE 'bob')

Summary

Trên đây mình đã giới thiệu về arel với những câu truy vấn thường gặp. Để tìm hiều thêm về Arel bạn có thể đọc và tìm hiểu thêm tại: