Module: ConcernsOnRails::Models::Lockable::ClassMethods

Includes:
Support::ColumnGuard
Defined in:
lib/concerns_on_rails/models/lockable.rb

Instance Method Summary collapse

Methods included from Support::ColumnGuard

#column_migration_hint, #ensure_columns!, #ensure_columns_on!, #schema_reachable?

Instance Method Details

#lockable_by(attempts: DEFAULT_ATTEMPTS_FIELD, locked_at: DEFAULT_LOCKED_AT_FIELD, max_attempts: DEFAULT_MAX_ATTEMPTS, unlock_in: nil, prefix: nil, suffix: nil) ⇒ Object

Configure the lockout columns and policy. See the module docs.



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/concerns_on_rails/models/lockable.rb', line 72

def lockable_by(attempts: DEFAULT_ATTEMPTS_FIELD, locked_at: DEFAULT_LOCKED_AT_FIELD,
                max_attempts: DEFAULT_MAX_ATTEMPTS, unlock_in: nil, prefix: nil, suffix: nil)
  attempts = attempts.to_sym
  locked_at = locked_at.to_sym
  validate_lockable!(attempts, locked_at, max_attempts: max_attempts, unlock_in: unlock_in)

  self.lockable_attempts_field = attempts
  self.lockable_locked_at_field = locked_at
  self.lockable_max_attempts = max_attempts
  self.lockable_unlock_in = unlock_in
  ensure_columns!(LABEL, attempts, locked_at,
                  types: { attempts => :integer, locked_at => :datetime })
  validate_lockable_attempts_column!(attempts)
  define_lockable_scopes(prefix, suffix)
end

#unlock_expiredObject

Unlock every row whose lock window has fully elapsed, clearing locked_at and zeroing the attempts counter exactly as unlock_access! does. Returns the Integer count.

Nothing expires when unlock_in is nil (manual unlock only), so that case returns 0 without touching the database. The boundary instant counts as expired, matching lock_expired? and the scopes.



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/concerns_on_rails/models/lockable.rb', line 95

def unlock_expired
  unlock_in = lockable_unlock_in
  return 0 unless unlock_in

  locked_field = lockable_locked_at_field
  attempts_field = lockable_attempts_field
  # `lteq` on a NULL locked_at is NULL, so never-locked rows are
  # excluded without an extra predicate.
  expired = all.where(arel_table[locked_field].lteq(Time.zone.now - unlock_in))

  # Ownership-only check (no validations gate, and no updated_at on the
  # bulk write): `unlock_access!` writes via update_columns, which
  # already skips validations and timestamps, so the two paths agree.
  if ConcernsOnRails::Support::BatchOps.unoverridden?(self, ConcernsOnRails::Models::Lockable,
                                                      :before_unlock, :after_unlock, :unlock_access!)
    return expired.update_all(locked_field => nil, attempts_field => 0)
  end

  ConcernsOnRails::Support::BatchOps.run(
    expired,
    label: LABEL,
    message: "failed to unlock record",
    &:unlock_access!
  )
end