通过 callback 关联的 2 个 model,rails 会自动把所有的数据库操作语句封装为一个事务。如果整个事务执行过程中有异常抛出,事务将自动回滚,从而保证了关联模型的数据一致性。
在电商系统中,有 2 个模型,Order 和 OrderItem。其中 Order 模型用来存储订单信息,OrderItem 存储订单项。这 2 个表都有一个orderID字段
,通过一个生成的orderID
来进行关联,注意这里的orderID
并不是 ActiveRecord 自动维护的 order.id 或 order_item.id,而是后生成的并且同时插入这 2 个表中,也就是说关联关系并没有用到原来的id
字段。所以不能通过 has_many 关联。
数据库 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
业务逻辑是:
使用 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|
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 中,以避免对外公开。