在Rails中通过JSON POST实现多表更新及库存校验扣减
Hey there! Let's tackle your two requirements step by step—stock validation and inventory deduction when creating an order via that POST request. Here's how to implement this properly in your Rails app, following best practices for MVC and data integrity:
We'll keep most logic in the models (per Rails conventions) to keep controllers lean, and use database transactions to ensure no partial inventory updates if order creation fails.
1. Quick Note on Model Naming (Optional but Recommended)
Your line item model is named OrderIten—that's likely a typo for OrderItem. If you can rename it (and run the necessary migration/rename commands), it'll make your code more readable. We'll stick with OrderIten here to match your existing codebase.
2. Add Stock Availability Validation to the Order Model
First, we'll add a custom validation to check if the subsidiary has enough stock for all items in the order. This handles cases where the same product is added multiple times (like your example with two entries for product 1) by summing total quantity needed per product.
Update your Order model:
class Order < ApplicationRecord belongs_to :user belongs_to :subsidiary has_many :order_itens accepts_nested_attributes_for :order_itens # Custom validation to check stock levels validate :ensure_sufficient_stock private def ensure_sufficient_stock # Group order items by product to calculate total quantity needed product_needs = order_itens.group(:product_id).sum(:quantity) product_needs.each do |product_id, total_needed| # Find the inventory record for this product and order's subsidiary inventory = Inventory.find_by(product_id: product_id, subsidiary_id: subsidiary_id) if inventory.nil? errors.add(:base, "Product ##{product_id} is not available at this subsidiary") elsif inventory.quantity < total_needed errors.add(:base, "Insufficient stock for product ##{product_id}: requested #{total_needed}, available #{inventory.quantity}") end end end end
3. Add Inventory Deduction with Atomic Transactions
Next, we'll use a before_create callback wrapped in a transaction to deduct stock only if the order is valid and about to be saved. This ensures that if any part of the order creation fails, inventory levels stay unchanged.
Update the Order model to include deduction logic:
class Order < ApplicationRecord belongs_to :user belongs_to :subsidiary has_many :order_itens accepts_nested_attributes_for :order_itens validate :ensure_sufficient_stock before_create :deduct_inventory_stock private def ensure_sufficient_stock product_needs = order_itens.group(:product_id).sum(:quantity) product_needs.each do |product_id, total_needed| inventory = Inventory.find_by(product_id: product_id, subsidiary_id: subsidiary_id) if inventory.nil? errors.add(:base, "Product ##{product_id} is not available at this subsidiary") elsif inventory.quantity < total_needed errors.add(:base, "Insufficient stock for product ##{product_id}: requested #{total_needed}, available #{inventory.quantity}") end end end def deduct_inventory_stock # Use a transaction to make all deductions atomic Order.transaction do product_needs = order_itens.group(:product_id).sum(:quantity) product_needs.each do |product_id, total_needed| inventory = Inventory.lock.find_by(product_id: product_id, subsidiary_id: subsidiary_id) # Use update! to raise an error if the update fails (rolls back transaction) inventory.update!(quantity: inventory.quantity - total_needed) end end end
We added
lockto the inventory query to prevent race conditions in production—this acquires a row-level lock so no other transaction can modify the inventory record until this one completes.
4. Controller Adjustment (Minor)
Your existing controller code is mostly fine—we just need to make sure it handles any exceptions gracefully (though the validation should catch most issues before we reach the deduction step). Here's the cleaned-up version:
class OrdersController < ApplicationController # POST /orders # POST /orders.json def create @order = Order.new(order_params) if @order.save render json: @order, include:[:order_itens], status: :created, location: @order else render json: @order.errors, status: :unprocessable_entity end end private def order_params params.require(:order).permit( :user_id, :subsidiary_id, :amount, :start_time, :arrive_time, :delivered_time, :cancel_time, order_itens_attributes: [:quantity,:product_id] ) end end
5. How It Works with Your Example Request
Let's test with your sample JSON request:
{ "order": { "subsidiary_id":1, "user_id":1, "amount":220, "order_itens_attributes": [ { "quantity":22, "product_id": 1 }, { "quantity":22, "product_id": 1 } ] } }
- Validation: The order sums the two line items to 44 units needed for product 1 in subsidiary 1. It checks the
Inventoryrecord—if stock is >=44, validation passes; if not, errors are returned. - Deduction: If validation passes, the transaction deducts 44 units from the inventory. The order saves successfully, and you get the same success response you shared earlier.
内容的提问来源于stack exchange,提问作者Javier Joasimar Palos Flores

