Cập nhật dữ liệu trong ActiveRecord: chọn API theo invariant, không theo độ ngắn

ActiveRecord có nhiều API update vì mỗi API đánh đổi validation, callback, timestamp và exception khác nhau. Chúng không phải các cách viết đồng nghĩa để chọn theo tâm trạng.

updateupdate!

1
2
user.update(name: "Hudson")
user.update!(name: "Hudson")

Cả hai chạy validation, callback và cập nhật timestamps. update trả false khi validation fail; update! raise ActiveRecord::RecordInvalid.

Trong service/transaction, bản bang thường tốt hơn vì failure không bị biến thành một boolean rồi lặng lẽ đi lạc.

update_attributeupdate_attribute!

1
user.update_attribute(:last_seen_at, Time.current)

Method này bỏ validation nhưng vẫn chạy callback và cập nhật timestamp. Vì callback vẫn chạy, nó không phải thuốc chữa mặc định cho callback recursion.

Nếu Rails version hỗ trợ, update_attribute! raise khi callback abort thao tác.

update_columnupdate_columns

1
2
user.update_column(:last_seen_at, Time.current)
user.update_columns(last_seen_at: Time.current, online: true)

Hai method này ghi trực tiếp xuống database:

  • bỏ validation;
  • bỏ callback;
  • mặc định không tự cập nhật updated_at trừ khi API/version và option được dùng phù hợp;
  • vẫn serialize dữ liệu theo attribute type.

Chỉ dùng khi bạn cố ý bypass business invariant. Nhanh hơn không có nghĩa đúng hơn; xe mất phanh cũng giảm được khá nhiều overhead.

update_all

1
User.where(active: false).update_all(archived: true)

Update nhiều record bằng một câu SQL, không instantiate model, không validation/callback. Phù hợp cho bulk operation đã được kiểm soát.

Callback recursion

Nếu after_save lại gọi update, callback có thể tự kích hoạt:

1
after_save :recalculate

Đừng vội thay bằng update_columns để làm vòng lặp im tiếng. Hãy hỏi:

  • logic có thể chuyển sang before_validation/before_save bằng assignment không?
  • callback có cần điều kiện if: hoặc dirty tracking không?
  • side effect có nên nằm trong service/job sau transaction không?
1
before_save :normalize_name, if: :will_save_change_to_name?

Chọn API dựa trên invariant cần bảo vệ. Nếu không biết callback nào bị bỏ qua, bạn chưa tối ưu—bạn chỉ vừa tắt chuông báo cháy.

Reference