Class: SpreeCmCommissioner::InventoryItems::AdjustAvailableQuantity
- Inherits:
-
Object
- Object
- SpreeCmCommissioner::InventoryItems::AdjustAvailableQuantity
- Extended by:
- ServiceModuleThrowable
- Includes:
- Spree::ServiceModule::Base
- Defined in:
- app/services/spree_cm_commissioner/inventory_items/adjust_available_quantity.rb
Overview
Moves quantity_available on a single inventory item, in the database and in Redis, without
touching max_capacity.
Used by seat locking: locking a seat deducts 1, unlocking returns 1. max_capacity is left alone
because it is the physical vehicle capacity and InventoryItems::Reset recomputes it from
variant.total_on_hand — folding locks into it would be silently wiped by any later reset.
No defensive clamping: locking a date whose quantity_available is already 0 must raise ActiveRecord::RecordInvalid (the model validates >= 0) so the caller can report it as a skip, rather than silently losing the deduction.
Instance Method Summary collapse
-
#call(inventory_item:, delta:, caller_source: nil, lock: true, warm_redis: true) ⇒ Object
lock: pass false ONLY when the caller already holds this row's lock (Seats::LockBlocks and UnlockBlocks both wrap each pair in item.with_lock).
Methods included from ServiceModuleThrowable
Instance Method Details
#call(inventory_item:, delta:, caller_source: nil, lock: true, warm_redis: true) ⇒ Object
lock: pass false ONLY when the caller already holds this row's lock (Seats::LockBlocks and UnlockBlocks both wrap each pair in item.with_lock). Nesting a second with_lock on the same row costs a SAVEPOINT + a redundant SELECT ... FOR UPDATE + a RELEASE per call — about a third of all queries in a bulk lock. Defaults to true so a standalone caller is still safe.
warm_redis: pass false when the caller has already warmed this item's key in a batch. Warming is SET-NX, so doing it once up front is equivalent to doing it per call and saves one Redis round-trip per seat-date.
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'app/services/spree_cm_commissioner/inventory_items/adjust_available_quantity.rb', line 25 def call(inventory_item:, delta:, caller_source: nil, lock: true, warm_redis: true) return success(inventory_item) if delta.zero? # CRITICAL: warm the Redis key before adjusting it. # # adjust_quantity_in_redis runs `GET key or 0` then applies the delta and clamps at 0. Against a # key that does not exist yet, a -1 becomes `0 + (-1) = -1` -> clamped to 0 -> SET 0 with a # one-year TTL. The date would then advertise 0 available while the database says 38, and # AvailabilityChecker reads only Redis — so locking one seat on a not-yet-cached date would # silently take the whole departure off sale. # # CachedInventoryItemsBuilder SET-NXs the database value into a cold key, which is the same # thing RedisStock::Unstock and InventoryHolds::Convert do before their own Lua scripts. warm_redis_key!(inventory_item) if warm_redis apply = lambda do before = inventory_item.quantity_available after = before + delta inventory_item.update!(quantity_available: after) inventory_item.adjust_quantity_in_redis(delta) CmAppLogger.log( label: "#{self.class.name}#call", data: { caller_source: caller_source, inventory_item_id: inventory_item.id, quantity_available_before: before, delta: delta, quantity_available_after: after } ) end lock ? inventory_item.with_lock(&apply) : apply.call success(inventory_item) rescue ActiveRecord::RecordInvalid => e CmAppLogger.error( label: "#{self.class.name}#call failed", data: { caller_source: caller_source, inventory_item_id: inventory_item.id, quantity_available_current: inventory_item.quantity_available, delta: delta, errors: inventory_item.errors. } ) raise e end |