Giới thiệu về Arel trong Rails
Bài đăng này đã không được cập nhật trong 3 năm
Mở đầu
Trước đây, khi xây dựng các ứng dụng web có kết tới cơ sở dữ liệu, mình thường phải tự viết những câu lệnh SQL bằng tay, chỉ định rõ những cột cần lấy dữ liệu, phải viết rất nhiều câu lệnh để xử lý việc CRUD,.. Điều này cực kỳ nhàm chán gây khó để maintenance hay tìm bug khi có lỗi xảy ra.
Rồi mình biết tới Ruby on Rails
đi cùng với đó là ActiveRecord
API và dường như mọi khó khăn trước đó mình gặp phải biến mất. Giờ thì việc lấy dữ liệu từ cột nào trở nên cực kỳ đơn giản bởi chỉ cần chỉ định field và các câu lệnh SQL sẽ được gen ra tự động; update, insert, delete có thể thực hiện chỉ với một hai method đã được xây dựng trước hay việc sử dụng scope giúp việc truy vấn dữ liệu cực kỳ linh động.
Tuy nhiên, không có gì là hoàn hảo cả, ActiveRecord
cũng có nhược điểm như là: trong mệnh đề "WHERE" (where method); ActiveRecord chỉ hỗ trợ truy vấn AND
mà không hỗ trợ truy vấn OR
(cho tới tận Rails 5.0); hay việc so sánh chỉ cho toán tử =
và 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 những phần code này bằng SQL thuần (kidding?). May mắn thay, từ phiên bản 3.0 trở đi, Rails đã giới thiệu Arel và nó đã cứu rỗi cuộc đời mình ..
Arel
Arel là gì
Arel
là một thư viện bao gồm tập hợp các thành phần cho phép truy vấn dựa trên đại số quan hệ (Hiện tại các RDBMS đều là dựa vào truy vấn đại số quan hệ). Về cơ bản đại số quan hệ gồm các phép toán: phép chiếu (projection), phép chọn (điều kiện), phép kết (join),.. và nhiều phép toán khác
Arel đơn giản chỉ sinh ra SQL chứ ko thực sự truy cập và truy vấn data từ database. Hay hiểu đơn giản, Arel là nền tảng bên dưới của Active Record, biến những lời gọi hàm của ActiveRecord trở thành những câu SQL thực sự. Arel giúp chúng ta có thể build bất kỳ một hệ thống truy vấn dữ liệu nào.
Arel sử dụng cơ chế node để xây dựng. Tập hợp các node được phân nhánh sẽ được Arel duyệt qua và xử lý, kết quả cuối cùng tạo nên câu truy vấn
Arel có gì hot ?
Để thấy được khả năng cũng như cách sử dụng của arel trong rails, mình sẽ trình bày thông qua các ví dụ cụ thể sau
Arel Table
Class mà ta phải sử dụng chủ yếu khi làm việc với Arel đó là Arel::Table. Instance của class này trừu tượng hóa và đại diện cho bảng trong vật lý trong cơ sở dữ liệu. Một khi đã get được instance của class này, ta có thể chỉ định các field cần truy xuất data, join với các bảng khác hay lọc theo các điều kiện where
Để get Arel::Table instance của một model, ta có 2 option:
- sử dụng class method của ActiveRecord
User.arel_table
- sử dụng constructor của class Arel::Table
users = Arel::Table.new(:users)
Arel Attribute
Nếu như Arel::Table đại diện cho bảng trong CSDL thì instance của Arel::Attribute đại diện cho cột trong bảng đó.
Arel attributes có thể khởi tạo từ instance của Arel::Table bằng cách truyền tên cột là symbol thông qua hàm [] như get value từ hash thông thường:
User.arel_table[:created_at] # trả về instance của Arel::Attributes::Attribute của cột created_at trong bảng users
Query với Arel
- Query 1 cột, tất cả các cột:
users = User.arel_table
users.project(users[:domain_id]) # SELECT users.id FROM users
users.project(Arel.sql(‘*’)) # SELECT * FROM users
- So sánh với toán tử:
>=, <=, >, <
users.where(users[:sign_in_count].gteq(69)).projects(Arel.sql(*)) # SELECT * FROM users WHERE ussers.sign_in_count >= 69
users.where(users[:sign_in_count].lteq(69)).projects(Arel.sql(*)) # <=
users.where(users[:sign_in_count].gt(69)).projects(Arel.sql(*)) # >
users.where(users[:sign_in_count].lt(69)).projects(Arel.sql(*)) # <
- Toán tử
OR
users.where(users[:id].gt(10)).or(users[:sign_in_count].lt(20))
- Join bảng:
Inner join
users.join(photos).on(users[:id].eq(photos[:user_id])) # => SELECT * FROM users INNER JOIN photos ON users.id = photos.user_id
Left join
users.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
- Các 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
- toán tử
LIKE
:
users.project(users[:id]).where(users[:name].matches('%Ozawa%')) # SELECT users.id FROM users WHERE name LIKE '%Ozawa%'
Tại sao nên dùng Arel thay vì raw SQL
Một câu hỏi được đặt ra là: Nếu như sự khác biệt giữa việc truyền một chuỗi chứa raw SQL và truyền đối tượng của Arel vào Active Record query là không đáng kể, vậy sử dụng Arel để làm cái khỉ gì? Thì những đặc tính nổi bật của Arel sẽ giúp mình trả lời câu hỏi này:
Tính đáng tin cậy
: Khi join nhiều bảng có nhiều cột trùng tên (vd: id, created_at, updated_at,..) rất dễ xảy ra lỗi “ambiguous column reference”. Arel giảm thiểu tối đa việc xảy ra lỗi này.Tính đơn giản
: Arel cho phép câu query được chia nhỏ thành những đoạn code ngắn và dễ maintenance hơnTính linh hoạt
: Việc build các câu query bằng các method đã được tạo sẵn của Arel sẽ đơn giản và dễ dàng nhẹ nhàng hơn rất nhiều cho người viết so với việc ngồi cộng chuỗi (string concatenation) hay nội suy chuỗi ( string interpolation) để tạo nên chuỗi raw SQL
Tổng kết
Trên đây mình đã giới thiệu về arel và cách sử dụng cơ bản của nó trong rails. Hy vọng bài viết sẽ hữu dụng phần nào đó trong công việc nói chung cũng như trong việc truy vấn data nói riêng của bạn!!
Nguồn tham khảo
All rights reserved