减少产品数量
学习如何记录产品的库存信息。
本章将为订单模型添加一些验证逻辑,确保订单可以被正确创建。我们将重点考虑以下问题:
- 下单时如何减少对应商品的库存数量;
- 当商品缺货时应如何处理?
我们可能稍后还需要对订单的 JSON 输出做些调整,但可以先搁置这个问题。现在让我们开始编码。
为商品模型添加 quantity 字段
定义并执行迁移
我们首先在商品模型中添加库存字段,以确保每个订单都能真实反映可交付的商品数量。目前 Product 模型还没有 quantity 属性,因此我们通过运行以下终端命令来添加它:
1$ rails generate migration add_quantity_to_products quantity:integer
该命令会生成一个迁移文件,用于为 products 表添加 quantity 字段。
在执行迁移之前,我们需要对生成的迁移文件稍作修改。为数据库字段设置默认值是一种良好的实践,有助于避免出现 null 值带来的问题。
这正是一个合适的场景!修改后的迁移文件应如下所示:
No configuration found for ID: 7b48a213c41f840d997cef349d6872ba9db89fecf78778f0ce5a56a25dd63f3a
点击上方“运行”按钮打开终端,然后执行以下命令运行迁移:
1$ rake db:migrate
更新测试固件(fixtures)
我们还需更新测试数据,在其中加入 quantity 字段(此处我们随机设定值为 5)。更新后的 test/fixtures/products.yml 文件内容如下:
1one: 2 title: TV Plosmo Philopps 3 price: 9999.99 4 published: false 5 user: one 6 quantity: 5 7 8two: 9 title: Azos Zeenbok 10 price: 499.99 11 published: false 12 user: two 13 quantity: 5 14 15another_tv: 16 title: Cheap TV 17 price: 99.99 18 published: false 19 user: two 20 quantity: 5
修改订单创建参数
由于现在需要为每种商品指定购买数量,我们必须调整订单的创建方式。我们将接收一个包含多个商品 ID 的列表。为了简化处理,我们采用哈希表结构,包含 product_id 和 quantity 两个键。
这个改动稍显复杂,请耐心跟进。我们首先在 test/models/order_test.rb 中添加一个单元测试:
1# ... 2class OrderTest < ActiveSupport::TestCase 3 # ... 4 test '为订单构建两个商品投放记录' do 5 @order.build_placements_with_product_ids_and_quantities [ 6 { product_id: @product1.id, quantity: 2 }, 7 { product_id: @product2.id, quantity: 3 }, 8 ] 9 assert_difference('Placement.count', 2) do 10 @order.save 11 end 12 end 13end
接下来,我们在 app/models/order.rb 中实现对应逻辑:
1class Order < ApplicationRecord 2 # ... 3 # @param product_ids_and_quantities [Array<Hash>] 示例格式:`[{product_id: 1, quantity: 2}]` 4 # @yield [Placement] 构建的投放对象 5 def build_placements_with_product_ids_and_quantities(product_ids_and_quantities) 6 product_ids_and_quantities.each do |product_id_and_quantity| 7 placement = placements.build(product_id: product_id_and_quantity[:product_id]) 8 yield placement if block_given? 9 end 10 end 11end
build_placements_with_product_ids_and_quantities 方法将构建对应的投放(Placement)对象。当调用订单的 save 方法时,这些数据会被写入数据库。最后一步是同步更新控制器的测试及其具体实现。
我们先更新 test/controllers/api/v1/orders_controller_test.rb 文件:
1# ... 2class Api::V1::OrdersControllerTest < ActionDispatch::IntegrationTest 3 setup do 4 @order = orders(:one) 5 @order_params = { 6 order: { 7 product_ids_and_quantities: [ 8 { product_id: products(:one).id, quantity: 2 }, 9 { product_id: products(:two).id, quantity: 3 }, 10 ] 11 } 12 } 13 end 14 # ... 15 test '应成功创建包含两个商品及对应投放记录的订单' do 16 assert_difference('Order.count', 1) do 17 assert_difference('Placement.count', 2) do 18 post api_v1_orders_url, params: @order_params, headers: { Authorization: JsonWebToken.encode(user_id: @order.user_id) }, as: :json 19 end 20 end 21 assert_response :created 22 end 23end
然后更新 app/controllers/api/v1/orders_controller.rb 文件:
1class Api::V1::OrdersController < ApplicationController 2 # ... 3 def create 4 order = Order.create!(user: current_user) 5 order.build_placements_with_product_ids_and_quantities(order_params[:product_ids_and_quantities]) 6 if order.save 7 OrderMailer.send_confirmation(order).deliver 8 render json: order, status: :created 9 else 10 render json: { errors: order.errors }, status: :unprocessable_entity 11 end 12 end 13 14 private 15 def order_params 16 params.require(:order).permit(product_ids_and_quantities: [:product_id, :quantity]) 17 end 18end
注意:我们也对 OrdersController#order_params 方法进行了相应修改。
测试更新后的代码
代码编辑器中的文件已更新为最新代码。
提示: 点击“运行”按钮可对所有
.rb文件执行错误检查。
No configuration found for ID: a8396954ccb5c256fed4b367a67a68ac613607c05d337babc3ae043c521cab33
接下来,我们需要在订单创建后减少对应商品的库存数量。你可能会第一时间想到在 Order 模型中完成这一操作,但这其实是一个常见的误区。
当我们处理“多对多”关联时,往往忽略了中间关联模型 Placement。实际上,Placement 模型更适合承担这项任务,因为它同时关联了订单和商品,能够更方便地访问并更新商品的库存信息。