Files
openfoodnetwork/app/services/current_order_locker.rb
Maikel Linke 50093c325a Move checkout locking to its own service
It gives this complex logic more space and allows for better structure
and more comments at the right places.
2019-11-19 18:18:01 +11:00

41 lines
1.4 KiB
Ruby

# Locks a controller's current order including its variants.
#
# It should be used when making major changes like checking out the order.
# It can keep stock checking in sync and prevent overselling of an item.
class CurrentOrderLocker
# This interface follows the ActionController filters convention:
#
# https://guides.rubyonrails.org/action_controller_overview.html#filters
#
def self.around(controller)
lock_order_and_variants(controller.current_order) { yield }
end
# Locking will not prevent all access to these rows. Other processes are
# only waiting if they try to lock one of these rows as well.
#
# https://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html
#
def self.lock_order_and_variants(order)
return yield if order.nil?
order.with_lock do
lock_variants_of(order)
yield
end
end
private_class_method :lock_order_and_variants
# There are many places in which stock is stored in the database. Row locking
# on variant level ensures that there are no conflicts even when an item is
# sold through multiple shops.
def self.lock_variants_of(order)
variant_ids = order.line_items.select(:variant_id)
# Ordering the variants by id prevents deadlocks. Plucking the ids sends
# the locking query without building Spree::Variant objects.
Spree::Variant.where(id: variant_ids).order(:id).lock.pluck(:id)
end
private_class_method :lock_variants_of
end