Module: LcpRuby::Authorization::Cache
- Defined in:
- lib/lcp_ruby/authorization/cache.rb
Overview
Per-request memoization for parent policy classes and resolved parent scopes used by ‘inherits_from`. Storage lives on `LcpRuby::Current`, which Rails resets automatically at request boundaries via ActionDispatch::Executor and at the start of each ActiveJob perform. No manual around_action wiring required.
Pattern: explicit user threading. The cache **does not read** ‘LcpRuby::Current.user`; the user that defines what gets cached is the one passed explicitly to `parent_policy_scope(parent_name, user)`. Cache keys include `user&.id` so impersonated and real views never collide.
Defined Under Namespace
Classes: ScopeError
Constant Summary collapse
- RESOLVING =
:__resolving__
Class Method Summary collapse
-
.clear! ⇒ Object
Test/console helper.
-
.parent_policy_scope(parent_name, user) ⇒ Object
Resolves the parent’s policy scope and memoizes per (parent, user).
-
.policy_for(model_name) ⇒ Object
Per-request memoization of PolicyFactory.policy_for.
Class Method Details
.clear! ⇒ Object
Test/console helper. Production code should rely on Rails’ automatic CurrentAttributes reset at request boundaries.
68 69 70 71 |
# File 'lib/lcp_ruby/authorization/cache.rb', line 68 def self.clear! LcpRuby::Current.authz_scopes = nil LcpRuby::Current.authz_policies = nil end |
.parent_policy_scope(parent_name, user) ⇒ Object
Resolves the parent’s policy scope and memoizes per (parent, user). Uses a re-entry sentinel: if a code path triggers re-entry on the same key during its own resolution (cycle escaped validation, DSL flow, dynamic edits) we raise rather than recurse infinitely.
If the underlying resolver raises, the sentinel is cleared so the SAME error surfaces on subsequent calls instead of a misleading “recursion detected” message about the leftover sentinel.
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/lcp_ruby/authorization/cache.rb', line 42 def self.parent_policy_scope(parent_name, user) store = (LcpRuby::Current.authz_scopes ||= {}) key = [ parent_name.to_s, user&.id ] existing = store[key] if existing == RESOLVING raise ScopeError, "inheritance recursion detected for parent " \ "#{parent_name.inspect} (user_id=#{user&.id}); " \ "inherits_from cycle escaped validation" end return existing if store.key?(key) && existing != RESOLVING store[key] = RESOLVING begin store[key] = resolve_parent_scope(parent_name, user) rescue # Roll back the sentinel so a retry surfaces the original error # rather than a "recursion detected" red herring. store.delete(key) raise end end |
.policy_for(model_name) ⇒ Object
Per-request memoization of PolicyFactory.policy_for.
NB: PolicyFactory itself has a class-level cache, so this is technically a double cache. The Current-backed layer is kept deliberately for test isolation: ‘Cache.clear!` resets both `authz_scopes` AND `authz_policies` to nil, so test setups that want a clean policy-class slate per example get it via one call instead of also having to remember `PolicyFactory.clear!`. The runtime cost is one hash lookup per request, dwarfed by AR query overhead in any save path.
29 30 31 32 |
# File 'lib/lcp_ruby/authorization/cache.rb', line 29 def self.policy_for(model_name) store = (LcpRuby::Current.authz_policies ||= {}) store[model_name.to_s] ||= PolicyFactory.policy_for(model_name) end |