sequel-privacy 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c7ef079bb3498aa1006aa327a138bcb1735b78ae02a65b11d2ee4976d9871978
4
- data.tar.gz: 95000199db0cbb08908c1a261152c641b71ceb7898875a9f732936662df9334e
3
+ metadata.gz: e06f6f240ee6789aee3cd1881290df4cdc503d0be754c94e9b2bdd516607bf22
4
+ data.tar.gz: a280c975257f069cf01db2ef9189a10e2ed2fb5af279bdb365da211485e47c71
5
5
  SHA512:
6
- metadata.gz: 6ea2e6410976a3e325264faa698a2faaf323c1aafdfe9eb3444c8cc18f51c887155227a1fe68ec119408d9679b1c4a39cc70e69611db4e6ed94edf9b93857ef2
7
- data.tar.gz: 0271f5b12a6d376e04b5feae0eb9ee604da1258871092578878571c877b86b9ef388a60441962b31b36588c9c3e4fc9642c9b71cd0f90a8890030903e2a1bb21
6
+ metadata.gz: d70745ff2e176a8d6e4ba3ad5f1c8ade864b44647392085ba86df00f5f3bdd78ca306d79776731a97c4c9aa769b5fd9009260b3f9b470c88b2ec985fde21aa46
7
+ data.tar.gz: d6f85561a19cc324e3ea99e0d0cfaef30ecb405dc6eecedeb349adb55c18596de0d8384624431a1857bbe8dda11866043093be22d4cbd34e7b8bf220b28ba3a9
data/CHANGELOG.md CHANGED
@@ -3,6 +3,9 @@
3
3
 
4
4
  ## [Unreleased]
5
5
 
6
+ - Added `Sequel::Privacy.with_cache_scope` for nested, exception-safe policy
7
+ caches isolated between concurrent threads and fibers.
8
+
6
9
  ## [0.7.0] – 2026-08-26
7
10
  - Removed `#for_vc` on `Sequel::Model` instances. Instead, call `#reset_viewer_context(vc, reason)`.
8
11
  - Removed `#viewer_context=` from `Sequel::Model` instances.
data/README.md CHANGED
@@ -402,7 +402,9 @@ Sequel::Privacy.logger = SemanticLogger['Privacy']
402
402
 
403
403
  ## Cache Management
404
404
 
405
- Policy results are cached per-request to avoid redundant evaluation. Clear between requests:
405
+ Policy results are cached per operation to avoid redundant evaluation. Wrap
406
+ each request, job, or other independent operation in a cache scope. Scopes are
407
+ isolated between concurrent threads and fibers:
406
408
 
407
409
  ```ruby
408
410
  # In Rack middleware
@@ -412,13 +414,14 @@ class PrivacyCacheMiddleware
412
414
  end
413
415
 
414
416
  def call(env)
415
- Sequel::Privacy.clear_cache!
416
- @app.call(env)
417
+ Sequel::Privacy.with_cache_scope { @app.call(env) }
417
418
  end
418
419
  end
419
420
  ```
420
421
 
421
- Or somewhere manually, like at the top of your Roda or Sinatra app:
422
+ Scopes may be nested and are restored after exceptions. `clear_cache!` clears
423
+ only the active scope. Applications not yet using scopes retain the legacy
424
+ process-global cache and can clear it manually:
422
425
 
423
426
  ```ruby
424
427
  Sequel::Privacy.clear_cache!
@@ -2,28 +2,82 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Sequel
5
+ # Privacy enforcement and operation-scoped policy caching.
5
6
  module Privacy
6
- # In-memory cache for policy evaluation results. Clear between
7
- # requests (e.g. via Rack middleware).
7
+ # One policy cache scope. Kept separate from ViewerContext because a
8
+ # long-lived context may serve many independent operations.
9
+ class CacheScope
10
+ extend T::Sig
11
+
12
+ sig { returns(T::Hash[Integer, Symbol]) }
13
+ attr_reader :cache
14
+
15
+ sig { returns(T::Hash[Integer, Integer]) }
16
+ attr_reader :single_matches
17
+
18
+ sig { void }
19
+ def initialize
20
+ @cache = T.let({}, T::Hash[Integer, Symbol])
21
+ @single_matches = T.let({}, T::Hash[Integer, Integer])
22
+ end
23
+
24
+ sig { void }
25
+ def clear!
26
+ @cache = {}
27
+ @single_matches = {}
28
+ end
29
+ end
30
+ private_constant :CacheScope
31
+
32
+ # In-memory cache for policy evaluation results. Concurrent applications
33
+ # should wrap each independent operation in with_cache_scope.
8
34
  class << self
9
35
  extend T::Sig
10
36
 
37
+ CACHE_SCOPE_KEY = :sequel_privacy_cache_scope
38
+ private_constant :CACHE_SCOPE_KEY
39
+
40
+ sig do
41
+ type_parameters(:U)
42
+ .params(block: T.proc.returns(T.type_parameter(:U)))
43
+ .returns(T.type_parameter(:U))
44
+ end
45
+ def with_cache_scope(&block)
46
+ previous = Thread.current[CACHE_SCOPE_KEY]
47
+ Thread.current[CACHE_SCOPE_KEY] = CacheScope.new
48
+ block.()
49
+ ensure
50
+ Thread.current[CACHE_SCOPE_KEY] = previous
51
+ end
52
+
11
53
  sig { returns(T::Hash[Integer, Symbol]) }
12
54
  def cache
13
- @cache ||= T.let({}, T.nilable(T::Hash[Integer, Symbol]))
55
+ active_cache_scope&.cache || (@cache ||= T.let({}, T.nilable(T::Hash[Integer, Symbol])))
14
56
  end
15
57
 
16
58
  # Tracks single-match optimization state.
17
59
  # Key: [policy, actor, viewer_context].hash → Value: subject.hash
18
60
  sig { returns(T::Hash[Integer, Integer]) }
19
61
  def single_matches
20
- @single_matches ||= T.let({}, T.nilable(T::Hash[Integer, Integer]))
62
+ active_cache_scope&.single_matches ||
63
+ (@single_matches ||= T.let({}, T.nilable(T::Hash[Integer, Integer])))
21
64
  end
22
65
 
23
66
  sig { void }
24
67
  def clear_cache!
25
- @cache = {}
26
- @single_matches = {}
68
+ if (scope = active_cache_scope)
69
+ scope.clear!
70
+ else
71
+ @cache = {}
72
+ @single_matches = {}
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ sig { returns(T.nilable(CacheScope)) }
79
+ def active_cache_scope
80
+ T.cast(Thread.current[CACHE_SCOPE_KEY], T.nilable(CacheScope))
27
81
  end
28
82
  end
29
83
  end
@@ -3,6 +3,6 @@
3
3
 
4
4
  module Sequel
5
5
  module Privacy
6
- VERSION = '0.7.0'
6
+ VERSION = '0.7.1'
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel-privacy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Austin Bales
@@ -152,7 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
152
  - !ruby/object:Gem::Version
153
153
  version: '0'
154
154
  requirements: []
155
- rubygems_version: 4.0.14
155
+ rubygems_version: 4.0.21
156
156
  specification_version: 4
157
157
  summary: Privacy enforcement plugin for Sequel models
158
158
  test_files: []