Bang method trong ActiveRecord Transaction
Chúng ta có 2 đoạn code như sau:
def create
@user = User.new params[:user]
@user.save!
redirect_to user_path(@user)
rescue ActiveRecord::RecordNotSaved
flash[:notice] = 'Unable to create user'
render :action => :new
end
và:
def create
@user = User.new params[:user]
if @user.save
redirect_to user_path(@user)
else
flash[:notice] = 'Unable to create user'
render :action => :new
end
end
Sự khác biệt ở đây là method save!
(bang method) và save
. Trong rails, bang method sẽ ném ra một ngoại lệ nếu như phương thức đó có lỗi, còn ở cách thứ hai, rails đơn thuần trả về true
hoặc false
. Vậy thì với 2 cách trên, chúng ta nên sử dụng cách nào?
Exceptions should not be expected
Có thể hiểu là “Ngoại lệ là không lường trước được.”
Khi viết một ứng dụng, bạn có thể lường trước được việc người dùng có thể không nhập hoặc nhập sai vào một trường nào đó. Do đó, chúng ta KHÔNG NÊN dùng exception để xử lý vì exception chỉ nên dùng ở những người hợp chúng ta không biết trước được, như là:
- Mất kết nối đến cơ sở dữ liệu
- Hết bộ nhớ
- Lỗi truy xuất (IO/socket)
Nếu những lỗi trên xảy ra, tốt hơn hết là trả về cho người dùng trang thông báo lỗi (500) và lập trình viên sẽ được thông báo về những lỗi đó thông qua email chẳng hạn.
Ví dụ khi đọc dữ liệu từ file:
File.open('testfile') do |file|
while line = file.gets
puts line
end
end
Phương thức IO#gets trả về String
hoặc nil
. Nếu như trả về nil
nghĩa là đã đọc đến cuối file và điều này hoàn toàn hợp lý. Đó không phải là một ngoại lệ và nên xử lý bằng các lệnh điều kiện.
Nếu ruby ném ngoại lệ EOFError:
File.open('testfile') do |file|
begin
while line = file.readline
puts line
end
rescue EOFError
# do nothing
end
end
Ném ngoại lệ khi đọc đến cuối file nghĩa là điều đó không được lường trước. Thật điên rồ!
Vậy thì trong Rails, bang method của ActiveRecord
như save!
hay update_attibutes!
nên được loại bỏ khỏi API chăng? Vì chúng ta có thể xử lý thông qua giá trị true/false trả về của save
hoặc update_attributes
.
Tuy nhiên, các bang method vẫn được sử dụng, trong transaction. Cách duy nhất đề roll back một transaction là ném ra một ngoại lệ.
Trong ruby, ý nghĩa của bang method là chỉnh sửa từ đối tượng nguồn. Còn trong rails, !
trong save!
… có nghĩa là làm điều gì đó nguy hiểm nên cần phải ném ra ngoại lệ.
ActiveRecord::Base.transaction do
david.update_attributes!(:amount => -100)
mary.update_attributes!(:amount => 100)
end
Ví dụ trên thực hiện chuyển 100$ từ tài khoản của david
sang mary
. Nếu một trong 2 thao tác trên thất bại thì toàn bộ giao dịch thất bại, do đó chúng ta cần dùng đến transaction và exception từ bang method sẽ là dấu hiệu để roll back lại dữ liệu nếu có xảy ra lỗi.
Một vài trường hợp KHÔNG NÊN dùng transaction:
- Sử dụng transaction cho một record đơn lẻ.
- Những transaction lồng nhau không cần thiết.
- Block code của transaction không ném ra ngoại lệ.
- Sử dụng transaction trong controller.