Module: OpenLoam::RecordLocks
- Defined in:
- lib/open_loam/record_locks.rb
Overview
Advisory record locks (OpenLoam::RecordLock) — a "who's editing this" courtesy for the multi-user admin.
OpenLoam::RecordLocks.acquire(record, by: current_actor) # take/refresh, or nil if held
OpenLoam::RecordLocks.holder(record) # the user holding it, or nil
OpenLoam::RecordLocks.release(record, by: current_actor) # give it up
OpenLoam::RecordLocks.force_release(record) # manager override
THIS IS ADVISORY: a held lock warns, it does not hard-block — optimistic locking (lock_version) is the actual guarantee that two edits can't clobber. Every query is tenant-scoped by OpenLoam::RecordLock, so a lock is invisible and unaffectable from another tenant.
Constant Summary collapse
- DEFAULT_TTL =
5.minutes
Class Method Summary collapse
-
.acquire(record, by:, ttl: DEFAULT_TTL) ⇒ Object
Take or refresh the lock if it is free (or already yours).
-
.active_lock(record) ⇒ Object
The live lock on a record, or nil — cleaning up a lock that is expired or whose record is gone (an auto-free), so orphaned rows don't linger.
-
.force_release(record) ⇒ Object
Manager override: drop whoever holds it.
- .holder(record) ⇒ Object
- .release(record, by:) ⇒ Object
Class Method Details
.acquire(record, by:, ttl: DEFAULT_TTL) ⇒ Object
Take or refresh the lock if it is free (or already yours). The same holder re-acquiring extends the TTL (heartbeat). Returns the lock, or nil when someone else holds a live one. A record that no longer exists (hard- or soft-deleted) cannot be locked.
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/open_loam/record_locks.rb', line 22 def acquire(record, by:, ttl: DEFAULT_TTL) return nil unless present?(record) lock = lock_row(record) return nil if lock && !lock.expired? && lock.locked_by_id != by.id lock ||= OpenLoam::RecordLock.new(lockable_type: type_of(record), lockable_id: record.id) # A fresh session token when the lock is newly created or taken over from # an expired holder; kept as-is on a heartbeat by the same user. lock.token = SecureRandom.hex(16) if lock.new_record? || lock.locked_by_id != by.id lock.locked_by_id = by.id lock.expires_at = Time.current + ttl lock.save! lock rescue ActiveRecord::RecordNotUnique # Lost the create race — someone else got it first, which is exactly the # nil contract. Advisory honesty over cleverness. nil end |
.active_lock(record) ⇒ Object
The live lock on a record, or nil — cleaning up a lock that is expired or whose record is gone (an auto-free), so orphaned rows don't linger.
44 45 46 47 48 49 50 51 52 53 |
# File 'lib/open_loam/record_locks.rb', line 44 def active_lock(record) lock = lock_row(record) return nil unless lock if lock.expired? || !present?(record) lock.destroy return nil end lock end |
.force_release(record) ⇒ Object
Manager override: drop whoever holds it. The controller gates the role.
66 67 68 69 |
# File 'lib/open_loam/record_locks.rb', line 66 def force_release(record) lock_row(record)&.destroy true end |
.holder(record) ⇒ Object
55 56 57 |
# File 'lib/open_loam/record_locks.rb', line 55 def holder(record) active_lock(record)&.locked_by end |
.release(record, by:) ⇒ Object
59 60 61 62 63 |
# File 'lib/open_loam/record_locks.rb', line 59 def release(record, by:) lock = lock_row(record) lock.destroy if lock && lock.locked_by_id == by.id true end |