5 phương thức của ActiveRecord mà bạn nên dùng
Bài đăng này đã không được cập nhật trong 7 năm
Đây là bài viết chia sẻ về các hàm trong ActiveRecord, chúng rất hữu ích trong một số trường hợp và sẽ giúp các bạn giảm thiểu tối đa code trong việc viết sql và các hàm do mình tự nghĩ ra.
Giả sử chúng ra có 3 model là Books
, Authors
và Reviews
được cài đặt như sau:
class Book < ActiveRecord::Base
belongs_to :author
has_many :reviews
end
class Author < ActiveRecord::Base
has_many :books
end
class Review < ActiveRecord::Base
belongs_to :book
end
1. Pluck
Được giới thiệu và đưa vào sử dụng ở phiên bản Ruby on rails 4.0, hàm pluck
giúp phân bổ bộ nhớ ở mức tối thiểu khi trả về kế quả từ ActiveRecord. Một trường hợp mà rất tuyệt vời để ta sử dụng pluck
đó là khi có một bảng cơ sở dữ liệu có thể dùng ActiveRecord để thao tác và nó có rất nhiều cột. Trong trường hợp này nếu ta trả về một tập đầy đủ của đối tượng có thể gây lãng phí bộ nhớ, chúng ta không nên làm như vậy. Ví dụ trong trường hợp này, chúng ta muốn lấy danh sách book_id
ta có thể làm như sau:
Book.where.pluck(:id)
# SELECT "books"."id" FROM "books"
=> [1, 3, 45, ...]
Một yếu tố quan trọng để bạn nhận ra phương thức pluck
đó là các giá trị trả về của nó. Không giống như người anh select
, pluck
không trả về các thể hiện của ActiveRecord.
Nhiều tên cột có thể được trả về bằng pluck
và có thể nằm trong các Array
lồng nhau. Array trả về sẽ sắp xếp các cột theo thứ tự được yêu cầu:
Book.where.pluck(:id, :title)
# SELECT "books"."id", "books"."title" FROM "books"
=> [[1, 'A Title'], [3, 'Another One']]
Có thể không phải lúc nào pluck
đượn chọn là sự lựa chọn tốt nhất trong việc lấy nhiều cột. Nếu ta muốn lấy quá nhiều cột bằng phương thức pluck
có thể tạo ra một hậu quả không thể lường trước được và hiệu năng khi sử dụng sẽ không tốt.
2. Transaction
Một khía cạnh quan trọng của cơ sở dữ liệu quan hệ là các hành vi mang tính chất phần tử. Khi tạo ra các đối tượng ActiveRecord, chúng ta có thể duy trì việc này thông quan transaction
.
Ví dụ: nếu một ngoại lệ xảy ra trong quá trình chúng ta tạo hoặc cập nhật tất cả các Reviews
của Books
, một transaction
sẽ giúp chúng ta loại bỏ các ngoại lệ khi chạy.
book = Book.find(1)
book.reviews.each do |review|
review.meaningful_update!
end
Nếu hàm meaningful_update!
bắn ra một ngoại lệ, tất cả các bài Reviews
nằm ở trước các bài Reviews
lỗi sẽ được cập nhật một cách bình thường nhưng các bài Reviews
ở sau nó sẽ không được cập nhật. Với một transaction
, nó sẽ xử lí cho chúng ta vấn đề này một cách đơn giản.
ActiveRecord::Base.transaction do
book = Book.find(1)
book.reviews.each do |review|
review.meaningful_update!
end
end
Sau khi thêm transaction
vào, các ActiveRecord lỗi trong quá trình cập nhật sẽ bị bỏ qua và các ActiveRecord khác sẽ được cập nhật bình thường.
3. after_commit
callbacks
trong Ruby on Rails là một sự lựa chọn để giải quyết nhiều vấn đề. Các callbacks
của ActiveRecord được tạo vào những thời điểm khác nhau tùy thuộc vào hoạt động mà model thể hiện của chúng đang trải qua.
Một trường hợp sử dụng khá phổ biến của callbacks
đó là xoay quanh các việc cần làm sau việc model tiếp tục tồn tại. Model có thể được lưu trữ, tạo hoặc cập nhật.
Ví dụ để thêm 1 cuốn sách mới vào hàng đợi sau khi việc đánh giá nó được lưu:
class Book < ActiveRecord::Base
after_save :enqueue_for_review
def enqueue_for_review
ReviewQueue.add(book_id: self.id)
Logger.info("Added #{self.id} to ReviewQueue")
end
end
Chúng ta giả sử rằng ReviewQueue
hỗ trợ key/value nhằm mục đích đặt các sách mới vào hàng đợi để các nhà phê bình xem xét.
Khi mọi thứ rõ ràng, callback sẽ hoạt động như mong muốn:
Book.create!(
title: 'A New Book',
author_id: 3,
content: 'Blah blah...'
)
#=> Added 4 to ReviewQueue
#=> <Book id: 4, title: 'A New Book' ..>
ReviewQueue.size
#=> 1
Tuy nhiên nếu đoạn code này được gói trong một transaction
và transaction
lỗi. Book trong trường hợp trên sẽ không tồn tại nhưng nó vẫn tồn tại trong ReviewQueue
được chạy bởi redis hay cái gì đó tương tự.
ActiveRecord::Base.transaction do
Book.create!(
title: 'A New Book',
author_id: 3,
content: 'Blah blah...'
)
raise StandardError, 'Something Happened'
end
#=> Added 4 to ReviewQueue
#=> Error 'Something Happened'
ReviewQueue.size
#=> 1
Đoạn mã này đã tạo ra một phần tử trên ReviewQueue
cho một Book không tồn tại; tuy nhiên nếu áp dụng after_commit
callback, vấn đề này sẽ được giải quyết. Ta thay after_save
bằng after_commit
:
class Book < ActiveRecord::Base
after_commit :enqueue_for_review
# ...
end
Nó cùng là đoạn mã trả về nhưng mong muốn sẽ điều nhiều hơn nữa:
ActiveRecord::Base.transaction do
Book.create!(
title: 'A New Book',
author_id: 3,
content: 'Blah blah...'
)
raise StandardError, 'Something Happened'
end
#=> Error 'Something Happened'
ReviewQueue.size
#=> 0
Tuyệt vời, callback after_commit
được chạy ngay sau khi bản ghi được tồn tại trên cơ sở dữ liệu, nó chính xác là những gì bạn muốn.
4. touch
Để cập nhật một cột dấu thời gian duy nhất trên 1 ActiveRecord, touch
hỗ trợ tuyệt vời trong việc này. Phương pháp này có thể chấp nhận tên một cột mà bạn muốn cập nhật nằm ngoài đối tượng, ví dụ như updated_at
. Nếu bảng Reviews
có cột last_viewed_at
thuộc lại datetime, touch
có thể cập nhật dễ dàng:
class ReviewsController < ApplicationController
def show
@review = Review.find(params[:id])
@review.touch(:last_viewed_at)
end
end
Nó dẫn đến truy vấn:
UPDATE "reviews"
SET "updated_at" = '2016-03-28 00:43:43.616367',
"last_viewed_at" = '2016-03-28 00:43:43.616367'
WHERE "reviews"."id" = 1
Không giống như việc thiết lập các thuộc tính khác, touch
sẽ gọi truy vấn và cập nhật ngay lập tức.
5. changes
Hãy nhìn vào đoạn mã sau:
review = Review.find(1)
review.book_id = 5
review.changes
# => {"book_id"=>[4, 5]}
Nhìn vào kết quả trả về bạn có thể đoán được rằng, changes
sẽ lưu những gì thay đổi sau khi bản khi được cập nhật.
Một phương thức cũng thường được sử dụng để trả về kết quả trước khi bản ghi thay đổi, đó là _was
:
review = Review.find(1)
review.status = 'Approved'
review.status_was
# => 'Pending'
Thật tuyệt vời, đây sẽ là một gợi ý nho nhỏ cho các bạn khi mà Mysql5.7 của chúng đã hỗ trợ kiểu json
trong database, ta có thể dử dụng changes
và _was
để lưu logs
kiểu json
, vì bản thân json
cũng không khác Hash
mà changes
trả về là mấy.
All rights reserved