背景
通过callback关联的2个model,rails会自动把所有的数据库操作语句封装为一个事务。如果整个事务执行过程中有异常抛出,事务将自动回滚,从而保证了关联模型的数据一致性。
举例
在电商系统中,有2个模型,Order和OrderItem。其中Order模型用来存储订单信息,OrderItem存储订单项。这2个表通过一个生成的orderID
来进行关联。
数据库schema如下:
create_table "order_items", force: :cascade do |t|
t.string "orderID"
t.integer "product_id"
t.decimal "price", precision: 10, scale: 2
t.decimal "amount", precision: 10, scale: 2
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.index ["product_id"], name: "index_order_items_on_product_id", using: :btree
end
create_table "orders", force: :cascade do |t|
t.integer "user_id"
t.string "orderID"
t.text "message"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "fixed_address"
t.decimal "total_price", precision: 10, scale: 2
t.index ["user_id"], name: "index_orders_on_user_id", using: :btree
end
业务逻辑是:
- 如果创建了订单,则需要将购物车中具体购买的物品添加到order_items表中。
- 如果删除订单,则需要同时删除在order_items表中具有和orderID相同的项目。
使用Callback实现关联操作如下:
class Order < ApplicationRecord
belongs_to :user
belongs_to :address
# 新建order后新建order_items
after_create :create_order_items
# 如果删除订单,则需要同时删除在order_items表中具有和orderID相同的项目。
after_destroy :destroy_all_same_orderID
protected
def create_order_items
current_user = self.user
current_user.carts.each do |cart|
# 这里使用save!,而不是save,因为save只会返回true或false,而不会抛出异常。
OrderItem.new(user_id: current_user.id, orderID: self.orderID, product_id: cart.product_id, price: cart.product.price, amount: cart.amount).save!
end
# raise "crash for test"
Cart.where(user_id: current_user.id).destroy_all
end
def destroy_all_same_orderID
# raise "error"
OrderItem.where(orderID: self.orderID).destroy_all
end
end
结论
这样在orders_controller.rb中的create操作,只进行order新建相关的操作,没有任何order_items相关的操作。如果不使用callback,在controller中操作两个模型的话,代码会不整洁,最主要是数据一直性无法保证。另外,好的做法是将callback方法放在private或protected中,以避免对外公开。
注意
这种做法可能导致callback hell(回调地狱),不要在数据层写入过多的业务逻辑。