singulus 0.1.0 → 0.1.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 +4 -4
- data/.yardopts +11 -0
- data/CHANGELOG.md +14 -1
- data/README.md +20 -0
- data/lib/singulus/internal/configuration.rb +9 -0
- data/lib/singulus/internal/errors.rb +13 -0
- data/lib/singulus/internal/singleton_class_methods.rb +3 -0
- data/lib/singulus/multiton.rb +182 -0
- data/lib/singulus/version.rb +3 -1
- data/lib/singulus.rb +104 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2d1789b73a6f295eb931426f74275d5103ee163683754c29d8566c58f5054bea
|
|
4
|
+
data.tar.gz: af0fdfb83a78959d15d1d866c195926e3c99407ee5e90629ef2e04904ee83294
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fbe8c5420941bedf97c2bf995b3131afa7e2d5777a8c6a46c9851965012acf250a7372b79d8a6721b8a8a4f3dc5f9edd5c919d2654ae14098d14be749e8608ed
|
|
7
|
+
data.tar.gz: 6ae106c3ebbd2efc6cde98511b7ce47691b3c9949707b6f478e2dc2ffe0db98e429bcc644c735b587820bc77419b989d319e02ed88f808a14512d0c62c196c9c
|
data/.yardopts
ADDED
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.1.1] - 2026-09-08
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Organized specs by configuration, Singleton, Multiton lifecycle, retention, and runtime security responsibilities.
|
|
14
|
+
- Fixed the Multiton test helper to apply the requested mode without requiring a retention strategy.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- YARD reference for configuration, Singleton and Multiton APIs, with types, errors, lifecycle semantics, and examples.
|
|
19
|
+
- Local documentation task and CI generation with warnings treated as failures.
|
|
20
|
+
|
|
9
21
|
### Fixed
|
|
10
22
|
|
|
11
23
|
- Completed the final RuboCop cleanup across Multiton and specs.
|
|
@@ -73,5 +85,6 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
|
|
|
73
85
|
- Protect common reflective constructor access paths.
|
|
74
86
|
- Protect captured `Method` and `UnboundMethod` paths in `:runtime` mode.
|
|
75
87
|
|
|
76
|
-
[Unreleased]: https://github.com/Rubcraft/singulus/compare/v0.1.
|
|
88
|
+
[Unreleased]: https://github.com/Rubcraft/singulus/compare/v0.1.1...HEAD
|
|
89
|
+
[0.1.1]: https://github.com/Rubcraft/singulus/compare/v0.1.0...v0.1.1
|
|
77
90
|
[0.1.0]: https://github.com/Rubcraft/singulus/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -241,6 +241,26 @@ bundle exec rake build
|
|
|
241
241
|
|
|
242
242
|
SimpleCov enforces line and branch coverage, while CI tests supported Ruby versions independently from RuboCop.
|
|
243
243
|
|
|
244
|
+
## API documentation
|
|
245
|
+
|
|
246
|
+
Generate the YARD reference locally:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
bundle install
|
|
250
|
+
bundle exec rake yard
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Open `doc/index.html` in a browser. The reference includes configuration,
|
|
254
|
+
include-time options, errors, and the class methods installed by Singleton
|
|
255
|
+
and Multiton. Installed methods appear on their pattern module's page; call
|
|
256
|
+
these on your including class, while calling `.with` on the pattern module.
|
|
257
|
+
The configuration object's reader/writer contract is documented under
|
|
258
|
+
`Singulus.configuration`; its concrete class remains private.
|
|
259
|
+
|
|
260
|
+
`.yardopts` excludes private implementation details. Generated HTML and the
|
|
261
|
+
YARD cache are ignored by Git. Documentation generation fails on YARD warnings
|
|
262
|
+
and runs in the CI quality job and `bundle exec rake ci`.
|
|
263
|
+
|
|
244
264
|
## Releasing
|
|
245
265
|
|
|
246
266
|
Repository initialization, version-control operations, and release-tag creation are managed by the Rubcraft Toolkit. Singulus itself does not prescribe or duplicate those commands.
|
|
@@ -2,15 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
module Singulus
|
|
4
4
|
module Internal
|
|
5
|
+
# Mutable defaults exposed through Singulus.configuration, not a public type.
|
|
6
|
+
# @private
|
|
5
7
|
class Configuration
|
|
6
8
|
MODES = %i[standard strict runtime].freeze
|
|
7
9
|
|
|
10
|
+
# Returns the mode applied to subsequently installed classes.
|
|
11
|
+
# @return [Symbol] :standard, :strict, or :runtime
|
|
8
12
|
attr_reader :default_mode
|
|
9
13
|
|
|
14
|
+
# Initializes defaults in strict mode.
|
|
10
15
|
def initialize
|
|
11
16
|
self.default_mode = :strict
|
|
12
17
|
end
|
|
13
18
|
|
|
19
|
+
# Sets the default for subsequently installed classes.
|
|
20
|
+
# @param mode [Symbol, String] standard, strict, or runtime
|
|
21
|
+
# @return [Symbol] normalized mode
|
|
22
|
+
# @raise [Singulus::Error] if the mode is unsupported
|
|
14
23
|
def default_mode=(mode)
|
|
15
24
|
mode = normalize_mode(mode)
|
|
16
25
|
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Singulus
|
|
4
|
+
# Public base exception for Singulus policy and configuration failures.
|
|
5
|
+
#
|
|
6
|
+
# Rescue this class for invalid modes/retention, locked registry settings,
|
|
7
|
+
# recursive Multiton initialization, and hardening violations. Specialized
|
|
8
|
+
# subclasses are private implementation details. Ruby and application errors
|
|
9
|
+
# (such as ArgumentError or initializer failures) are not wrapped.
|
|
10
|
+
#
|
|
11
|
+
# @example Handle a rejected configuration
|
|
12
|
+
# begin
|
|
13
|
+
# Singulus.configure { |config| config.default_mode = :unknown }
|
|
14
|
+
# rescue Singulus::Error => error
|
|
15
|
+
# warn error.message
|
|
16
|
+
# end
|
|
4
17
|
class Error < StandardError; end
|
|
5
18
|
|
|
6
19
|
module Internal
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
module Singulus
|
|
4
4
|
module Internal
|
|
5
5
|
module SingletonClassMethods
|
|
6
|
+
# See {Singulus::Singleton.instance} for the public contract.
|
|
6
7
|
def instance
|
|
7
8
|
return super unless Internal.locally_hardened?(self)
|
|
8
9
|
|
|
@@ -11,11 +12,13 @@ module Singulus
|
|
|
11
12
|
instance
|
|
12
13
|
end
|
|
13
14
|
|
|
15
|
+
# See {Singulus::Singleton.singulus} for the public contract.
|
|
14
16
|
def singulus(mode:)
|
|
15
17
|
Internal.set_mode!(self, mode)
|
|
16
18
|
self
|
|
17
19
|
end
|
|
18
20
|
|
|
21
|
+
# See {Singulus::Singleton.singulus_mode} for the public contract.
|
|
19
22
|
def singulus_mode
|
|
20
23
|
Internal.mode_for(self)
|
|
21
24
|
end
|
data/lib/singulus/multiton.rb
CHANGED
|
@@ -1,10 +1,153 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Singulus
|
|
4
|
+
# Provides a synchronized registry of instances indexed by normalized keys.
|
|
5
|
+
#
|
|
6
|
+
# Include this module and call `.instance_for` on the including class.
|
|
7
|
+
# Uniqueness lasts only while an entry is retained: deletion, expiration,
|
|
8
|
+
# eviction, or weak-reference collection allows a new object for the same key,
|
|
9
|
+
# even if application code still holds a previously released instance.
|
|
10
|
+
# Strict/runtime modes reject duplication and subclassing with {Error}.
|
|
11
|
+
# Standard-mode subclasses start with independent, default-configured registries.
|
|
12
|
+
#
|
|
13
|
+
# Methods documented below as class methods are installed on the including
|
|
14
|
+
# class, except {.with}, which is called on this module. Configure classes
|
|
15
|
+
# at boot before using them concurrently.
|
|
16
|
+
#
|
|
17
|
+
# @example Reuse a tenant-specific service
|
|
18
|
+
# class TenantService
|
|
19
|
+
# include Singulus::Multiton
|
|
20
|
+
# def initialize(tenant_id)
|
|
21
|
+
# @tenant_id = tenant_id
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
# TenantService.instance_for(10).equal?(TenantService.instance_for(10)) # => true
|
|
4
25
|
module Multiton
|
|
26
|
+
# @!method self.instance_for(identifier, *args, **kwargs, &block)
|
|
27
|
+
# Returns the retained instance for a key, or constructs and stores one.
|
|
28
|
+
#
|
|
29
|
+
# The normalizer determines the registry key, but the original identifier,
|
|
30
|
+
# additional arguments, keywords, and block are forwarded to `initialize` only
|
|
31
|
+
# on a cache miss. A hit ignores the additional initialization inputs.
|
|
32
|
+
# Construction and registry access are synchronized per class. Expired and dead
|
|
33
|
+
# weak entries are purged first; a hit refreshes LRU order, but never the TTL.
|
|
34
|
+
# Initializer and key-normalizer exceptions propagate. Failed initialization
|
|
35
|
+
# is not cached, so a later call may retry.
|
|
36
|
+
#
|
|
37
|
+
# @param identifier [Object] original identifier passed first to initialize
|
|
38
|
+
# @param args [Array<Object>] remaining positional initializer arguments
|
|
39
|
+
# @param kwargs [Hash] initializer keyword arguments
|
|
40
|
+
# @param block [Proc, nil] block forwarded to initialize on a miss
|
|
41
|
+
# @return [Object] instance of the receiving class
|
|
42
|
+
# @raise [Error] if initialization recursively requests the same normalized key
|
|
43
|
+
# @see multiton_key
|
|
44
|
+
# @see multiton_retention
|
|
45
|
+
|
|
46
|
+
# @!method self.instance?(identifier)
|
|
47
|
+
# Checks whether a live entry exists without constructing an instance.
|
|
48
|
+
#
|
|
49
|
+
# Purges expired/dead entries and does not refresh LRU order or TTL.
|
|
50
|
+
# Key-normalizer exceptions propagate.
|
|
51
|
+
#
|
|
52
|
+
# @param identifier [Object] identifier to normalize and look up
|
|
53
|
+
# @return [Boolean] whether the normalized key is registered
|
|
54
|
+
|
|
55
|
+
# @!method self.delete_instance(identifier)
|
|
56
|
+
# Removes registry ownership of a live entry and returns its instance.
|
|
57
|
+
#
|
|
58
|
+
# Purges expired/dead entries first. Existing external references stay valid;
|
|
59
|
+
# a later lookup can construct another instance. Normalizer exceptions propagate.
|
|
60
|
+
#
|
|
61
|
+
# @param identifier [Object] identifier to normalize and remove
|
|
62
|
+
# @return [Object, nil] removed instance, or nil if no live entry exists
|
|
63
|
+
|
|
64
|
+
# @!method self.clear_instances
|
|
65
|
+
# Releases all registry entries after purging expired/dead entries.
|
|
66
|
+
#
|
|
67
|
+
# External references remain valid. The empty registry allows changes to its
|
|
68
|
+
# key normalizer and retention policy.
|
|
69
|
+
#
|
|
70
|
+
# @return [Integer] number of live entries removed
|
|
71
|
+
|
|
72
|
+
# @!method self.instance_count
|
|
73
|
+
# Counts registered entries after purging expired/dead entries.
|
|
74
|
+
# @return [Integer] current registry size
|
|
75
|
+
|
|
76
|
+
# @!method self.instance_keys
|
|
77
|
+
# Returns a frozen snapshot of keys after purging expired/dead entries.
|
|
78
|
+
#
|
|
79
|
+
# Keys are normalized keys, not necessarily the original identifiers. Strings,
|
|
80
|
+
# arrays, and hashes are recursively copied/frozen when registered; other key
|
|
81
|
+
# objects are not copied. This operation does not refresh LRU order or TTL.
|
|
82
|
+
#
|
|
83
|
+
# @return [Array<Object>] frozen array of currently registered keys
|
|
84
|
+
|
|
85
|
+
# @!method self.singulus(mode: nil, retention: nil, ttl: nil, max_size: nil)
|
|
86
|
+
# Updates the class mode and/or retention policy, returning the class.
|
|
87
|
+
#
|
|
88
|
+
# Mode is applied first; a subsequent retention error does not roll it back.
|
|
89
|
+
# TTL/capacity settings are ignored unless retention is provided.
|
|
90
|
+
#
|
|
91
|
+
# @param mode [Symbol, String, nil] hardening mode; nil leaves it unchanged
|
|
92
|
+
# @param retention [Symbol, String, nil] strategy; nil leaves it unchanged
|
|
93
|
+
# @param ttl [Numeric, nil] positive lifetime in seconds for ttl/bounded
|
|
94
|
+
# @param max_size [Integer, nil] positive capacity for lru/bounded
|
|
95
|
+
# @return [Class] receiving class
|
|
96
|
+
# @raise [Error] for invalid settings or retention changes on a nonempty registry
|
|
97
|
+
# @see multiton_retention
|
|
98
|
+
|
|
99
|
+
# @!method self.singulus_mode
|
|
100
|
+
# Returns this class's current hardening mode.
|
|
101
|
+
# @return [Symbol] :standard, :strict, or :runtime
|
|
102
|
+
|
|
103
|
+
# @!method self.multiton_key(&block)
|
|
104
|
+
# Reads or replaces the key normalizer used by identifier-based operations.
|
|
105
|
+
#
|
|
106
|
+
# The block receives the original identifier. Its result becomes the registry
|
|
107
|
+
# key, with strings, arrays, and hashes recursively copied/frozen. Other objects
|
|
108
|
+
# must keep stable `hash` and `eql?` behavior while registered. No block means
|
|
109
|
+
# read the current normalizer; nil means identifiers are used directly.
|
|
110
|
+
# Clear the registry before changing normalization. Block exceptions propagate
|
|
111
|
+
# from subsequent identifier-based operations.
|
|
112
|
+
#
|
|
113
|
+
# @param block [Proc, nil] normalizer to install, or nil to read
|
|
114
|
+
# @yield [identifier] computes a registry key
|
|
115
|
+
# @yieldparam identifier [Object] original identifier
|
|
116
|
+
# @yieldreturn [Object] stable key
|
|
117
|
+
# @return [Class, Proc, nil] class when setting; current normalizer when reading
|
|
118
|
+
# @raise [Error] if setting a normalizer while live entries remain
|
|
119
|
+
# @example Normalize case before creating instances
|
|
120
|
+
# TenantService.multiton_key { |id| id.to_s.downcase }
|
|
121
|
+
|
|
122
|
+
# @!method self.multiton_retention(strategy = nil, ttl: nil, max_size: nil)
|
|
123
|
+
# Reads or replaces the registry retention policy (initially `:forever`).
|
|
124
|
+
#
|
|
125
|
+
# `forever` holds entries until explicit release; `weak` holds weak references.
|
|
126
|
+
# Neither accepts TTL or capacity. `lru` requires only max_size and evicts the
|
|
127
|
+
# least recently accessed entry. `ttl` requires only ttl and expires entries
|
|
128
|
+
# from their creation time using the monotonic clock. `bounded` requires both.
|
|
129
|
+
# Expiration is lazy, checked during registry operations, not by a background
|
|
130
|
+
# worker. TTL never slides on access. All policies can allow new instances
|
|
131
|
+
# after an entry is released. Settings may change only on an empty registry.
|
|
132
|
+
#
|
|
133
|
+
# @param strategy [Symbol, String, nil] forever, weak, lru, ttl, or bounded;
|
|
134
|
+
# nil reads the current strategy and ignores other arguments
|
|
135
|
+
# @param ttl [Numeric, nil] positive seconds (converted with to_f)
|
|
136
|
+
# @param max_size [Integer, nil] positive capacity (converted with to_i)
|
|
137
|
+
# @return [Class, Symbol] class when setting; strategy when reading
|
|
138
|
+
# @raise [Error] for an unknown strategy, incompatible/missing/nonpositive
|
|
139
|
+
# options, or a nonempty registry
|
|
140
|
+
# @example Combine time and capacity bounds
|
|
141
|
+
# TenantService.clear_instances
|
|
142
|
+
# TenantService.multiton_retention(:bounded, ttl: 300, max_size: 100)
|
|
143
|
+
|
|
144
|
+
# Supported internal retention names.
|
|
145
|
+
# @private
|
|
5
146
|
RETENTIONS = %i[forever lru ttl bounded weak].freeze
|
|
6
147
|
private_constant :RETENTIONS
|
|
7
148
|
|
|
149
|
+
# Internal registry entry, optionally holding a weak reference.
|
|
150
|
+
# @private
|
|
8
151
|
Entry = Struct.new(:value, :expires_at, keyword_init: true) do
|
|
9
152
|
def instance
|
|
10
153
|
value.is_a?(WeakRef) ? value.__getobj__ : value
|
|
@@ -19,10 +162,35 @@ module Singulus
|
|
|
19
162
|
private_constant :Entry
|
|
20
163
|
|
|
21
164
|
class << self
|
|
165
|
+
# Installs the pattern when Ruby evaluates `include`.
|
|
166
|
+
# @private
|
|
22
167
|
def included(base)
|
|
23
168
|
install(base)
|
|
24
169
|
end
|
|
25
170
|
|
|
171
|
+
# Builds an independent module with mode and optional retention settings.
|
|
172
|
+
#
|
|
173
|
+
# Defaults are captured now; mode and retention validation happens on inclusion.
|
|
174
|
+
# `ttl` and `max_size` are applied only when `retention` is supplied.
|
|
175
|
+
# See {.multiton_retention} for supported combinations.
|
|
176
|
+
#
|
|
177
|
+
# @param mode [Symbol, String, nil] :standard, :strict, or :runtime;
|
|
178
|
+
# nil uses the configured default
|
|
179
|
+
# @param options [Hash] mode and registry settings
|
|
180
|
+
# @option options [Symbol, String] :mode alternative to positional mode
|
|
181
|
+
# @option options [Symbol, String] :retention registry retention strategy
|
|
182
|
+
# @option options [Numeric] :ttl positive lifetime in seconds
|
|
183
|
+
# @option options [Integer] :max_size positive registry capacity
|
|
184
|
+
# @return [Module] configured module to include in a class
|
|
185
|
+
# @raise [ArgumentError] if both mode forms or unknown options are supplied
|
|
186
|
+
# @raise [Error] on inclusion for invalid mode or retention settings
|
|
187
|
+
# @example Limit registry capacity
|
|
188
|
+
# class TenantService
|
|
189
|
+
# include Singulus::Multiton.with(:strict, retention: :lru, max_size: 100)
|
|
190
|
+
# def initialize(tenant_id)
|
|
191
|
+
# @tenant_id = tenant_id
|
|
192
|
+
# end
|
|
193
|
+
# end
|
|
26
194
|
def with(mode = nil, **options)
|
|
27
195
|
resolved_mode = options.delete(:mode)
|
|
28
196
|
|
|
@@ -43,6 +211,8 @@ module Singulus
|
|
|
43
211
|
end
|
|
44
212
|
end
|
|
45
213
|
|
|
214
|
+
# Installs guards and a fresh registry on a class.
|
|
215
|
+
# @private
|
|
46
216
|
def install(base, mode: Singulus.configuration.default_mode)
|
|
47
217
|
Internal.initialize_managed_class!(base, kind: :multiton, mode: mode)
|
|
48
218
|
|
|
@@ -71,7 +241,10 @@ module Singulus
|
|
|
71
241
|
end
|
|
72
242
|
end
|
|
73
243
|
|
|
244
|
+
# Implementation of methods installed on each Multiton class.
|
|
245
|
+
# @private
|
|
74
246
|
module ClassMethods
|
|
247
|
+
# See {Singulus::Multiton.instance_for} for the public contract.
|
|
75
248
|
def instance_for(identifier, ...)
|
|
76
249
|
key = singulus_multiton_key_for(identifier)
|
|
77
250
|
|
|
@@ -102,6 +275,7 @@ module Singulus
|
|
|
102
275
|
end
|
|
103
276
|
end
|
|
104
277
|
|
|
278
|
+
# See {Singulus::Multiton.instance?} for the public contract.
|
|
105
279
|
def instance?(identifier)
|
|
106
280
|
key = singulus_multiton_key_for(identifier)
|
|
107
281
|
|
|
@@ -111,6 +285,7 @@ module Singulus
|
|
|
111
285
|
end
|
|
112
286
|
end
|
|
113
287
|
|
|
288
|
+
# See {Singulus::Multiton.delete_instance} for the public contract.
|
|
114
289
|
def delete_instance(identifier)
|
|
115
290
|
key = singulus_multiton_key_for(identifier)
|
|
116
291
|
|
|
@@ -120,6 +295,7 @@ module Singulus
|
|
|
120
295
|
end
|
|
121
296
|
end
|
|
122
297
|
|
|
298
|
+
# See {Singulus::Multiton.clear_instances} for the public contract.
|
|
123
299
|
def clear_instances
|
|
124
300
|
singulus_multiton_mutex.synchronize do
|
|
125
301
|
singulus_multiton_purge_expired!
|
|
@@ -129,6 +305,7 @@ module Singulus
|
|
|
129
305
|
end
|
|
130
306
|
end
|
|
131
307
|
|
|
308
|
+
# See {Singulus::Multiton.instance_count} for the public contract.
|
|
132
309
|
def instance_count
|
|
133
310
|
singulus_multiton_mutex.synchronize do
|
|
134
311
|
singulus_multiton_purge_expired!
|
|
@@ -136,6 +313,7 @@ module Singulus
|
|
|
136
313
|
end
|
|
137
314
|
end
|
|
138
315
|
|
|
316
|
+
# See {Singulus::Multiton.instance_keys} for the public contract.
|
|
139
317
|
def instance_keys
|
|
140
318
|
singulus_multiton_mutex.synchronize do
|
|
141
319
|
singulus_multiton_purge_expired!
|
|
@@ -143,16 +321,19 @@ module Singulus
|
|
|
143
321
|
end
|
|
144
322
|
end
|
|
145
323
|
|
|
324
|
+
# See {Singulus::Multiton.singulus} for the public contract.
|
|
146
325
|
def singulus(mode: nil, retention: nil, ttl: nil, max_size: nil)
|
|
147
326
|
Internal.set_mode!(self, mode) if mode
|
|
148
327
|
multiton_retention(retention, ttl: ttl, max_size: max_size) if retention
|
|
149
328
|
self
|
|
150
329
|
end
|
|
151
330
|
|
|
331
|
+
# See {Singulus::Multiton.singulus_mode} for the public contract.
|
|
152
332
|
def singulus_mode
|
|
153
333
|
Internal.mode_for(self)
|
|
154
334
|
end
|
|
155
335
|
|
|
336
|
+
# See {Singulus::Multiton.multiton_key} for the public contract.
|
|
156
337
|
def multiton_key(&block)
|
|
157
338
|
return @__singulus_multiton_key_normalizer__ unless block
|
|
158
339
|
|
|
@@ -161,6 +342,7 @@ module Singulus
|
|
|
161
342
|
self
|
|
162
343
|
end
|
|
163
344
|
|
|
345
|
+
# See {Singulus::Multiton.multiton_retention} for the public contract.
|
|
164
346
|
def multiton_retention(strategy = nil, ttl: nil, max_size: nil)
|
|
165
347
|
return @__singulus_multiton_retention__ unless strategy
|
|
166
348
|
|
data/lib/singulus/version.rb
CHANGED
data/lib/singulus.rb
CHANGED
|
@@ -15,7 +15,19 @@ require_relative "singulus/internal/singleton_class_methods"
|
|
|
15
15
|
require_relative "singulus/internal/runtime_hardening"
|
|
16
16
|
require_relative "singulus/multiton"
|
|
17
17
|
|
|
18
|
+
# Configurable Singleton and keyed Multiton patterns for a Ruby process.
|
|
19
|
+
#
|
|
20
|
+
# Require `singulus`, then include {Singleton} or {Multiton} in a class.
|
|
21
|
+
# The default mode is `:strict`; `:standard` relaxes local guards and
|
|
22
|
+
# `:runtime` adds process-wide Method/UnboundMethod guards for managed classes.
|
|
23
|
+
# Runtime hardening is not a sandbox for code running in the same process.
|
|
24
|
+
#
|
|
25
|
+
# @see Singleton
|
|
26
|
+
# @see Multiton
|
|
27
|
+
# @see Error
|
|
18
28
|
module Singulus
|
|
29
|
+
# Implementation details; not a supported public namespace.
|
|
30
|
+
# @private
|
|
19
31
|
module Internal
|
|
20
32
|
CONSTRUCTORS = %i[new allocate].freeze
|
|
21
33
|
|
|
@@ -137,10 +149,37 @@ module Singulus
|
|
|
137
149
|
private_constant :Internal
|
|
138
150
|
|
|
139
151
|
class << self
|
|
152
|
+
# Returns the mutable process-wide defaults for subsequently installed classes.
|
|
153
|
+
#
|
|
154
|
+
# Use the returned object's `default_mode` reader and `default_mode=` writer;
|
|
155
|
+
# its concrete class is private. The reader returns a Symbol. The writer accepts
|
|
156
|
+
# `:standard`, `:strict`, `:runtime`, or their String forms, and raises
|
|
157
|
+
# {Error} for invalid modes. Existing managed classes keep their mode.
|
|
158
|
+
# Setting the writer directly does not install runtime guards until a runtime
|
|
159
|
+
# class is installed; use {configure} to enable them immediately.
|
|
160
|
+
#
|
|
161
|
+
# @return [#default_mode, #default_mode=] shared configuration object
|
|
162
|
+
# @example Read the default
|
|
163
|
+
# Singulus.configuration.default_mode # => :strict
|
|
140
164
|
def configuration
|
|
141
165
|
@configuration ||= Internal::Configuration.new
|
|
142
166
|
end
|
|
143
167
|
|
|
168
|
+
# Yields the shared configuration and enables runtime guards when requested.
|
|
169
|
+
#
|
|
170
|
+
# Configure during application boot, before defining managed classes or
|
|
171
|
+
# capturing constructor references. Runtime patches are installed once and
|
|
172
|
+
# remain installed for the life of the process. Exceptions from the block
|
|
173
|
+
# propagate; changes already made to the configuration are not rolled back.
|
|
174
|
+
#
|
|
175
|
+
# @yield [config] changes defaults for future class installations
|
|
176
|
+
# @yieldparam config [#default_mode, #default_mode=] mutable configuration
|
|
177
|
+
# @yieldreturn [Object] ignored
|
|
178
|
+
# @return [#default_mode, #default_mode=] shared configuration after the block
|
|
179
|
+
# @raise [ArgumentError] if no block is given
|
|
180
|
+
# @raise [Error] if the block assigns an unsupported default mode
|
|
181
|
+
# @example Enable runtime hardening during boot
|
|
182
|
+
# Singulus.configure { |config| config.default_mode = :runtime }
|
|
144
183
|
def configure
|
|
145
184
|
raise ArgumentError, "a block is required" unless block_given?
|
|
146
185
|
|
|
@@ -149,17 +188,82 @@ module Singulus
|
|
|
149
188
|
configuration
|
|
150
189
|
end
|
|
151
190
|
|
|
191
|
+
# Replaces the shared configuration with a new object in `:strict` mode.
|
|
192
|
+
#
|
|
193
|
+
# Does not change existing classes, clear instances, or uninstall runtime
|
|
194
|
+
# patches. Previously returned configuration objects are no longer shared.
|
|
195
|
+
#
|
|
196
|
+
# @return [#default_mode, #default_mode=] new configuration object
|
|
152
197
|
def reset_configuration!
|
|
153
198
|
@configuration = Internal::Configuration.new
|
|
154
199
|
end
|
|
155
200
|
end
|
|
156
201
|
|
|
202
|
+
# Provides one lazily initialized, thread-safe instance per class and process.
|
|
203
|
+
#
|
|
204
|
+
# Include this module and call `.instance` on the including class. Construction
|
|
205
|
+
# uses a zero-argument initializer. In strict/runtime modes constructors are
|
|
206
|
+
# sealed after the first successful access, and duplication and inheritance
|
|
207
|
+
# raise {Error}. Standard mode delegates singleton semantics to Ruby Singleton,
|
|
208
|
+
# which rejects instance duplication with TypeError.
|
|
209
|
+
#
|
|
210
|
+
# Methods documented below as class methods are installed on the including
|
|
211
|
+
# class, except {.with}, which is called on this module.
|
|
212
|
+
#
|
|
213
|
+
# @example Share one service
|
|
214
|
+
# class Settings
|
|
215
|
+
# include Singulus::Singleton
|
|
216
|
+
# end
|
|
217
|
+
# Settings.instance.equal?(Settings.instance) # => true
|
|
157
218
|
module Singleton
|
|
219
|
+
# @!method self.instance
|
|
220
|
+
# Returns the class's shared instance, constructing it once if needed.
|
|
221
|
+
#
|
|
222
|
+
# Initialization is synchronized by Ruby Singleton. Initializer exceptions
|
|
223
|
+
# propagate and construction can be retried. Strict/runtime modes seal
|
|
224
|
+
# constructors after successful access. Recursive `.instance` calls from the
|
|
225
|
+
# initializer are unsupported by Ruby Singleton.
|
|
226
|
+
#
|
|
227
|
+
# @return [Object] instance of the receiving class
|
|
228
|
+
|
|
229
|
+
# @!method self.singulus(mode:)
|
|
230
|
+
# Changes this class's hardening mode.
|
|
231
|
+
#
|
|
232
|
+
# Switching to runtime installs process-wide guards. Switching away does not
|
|
233
|
+
# uninstall them. A sealed Singleton cannot downgrade to standard mode.
|
|
234
|
+
#
|
|
235
|
+
# @param mode [Symbol, String] :standard, :strict, or :runtime
|
|
236
|
+
# @return [Class] receiving class for chaining
|
|
237
|
+
# @raise [Error] for an invalid mode or a downgrade after constructor sealing
|
|
238
|
+
|
|
239
|
+
# @!method self.singulus_mode
|
|
240
|
+
# Returns this class's current hardening mode.
|
|
241
|
+
# @return [Symbol] :standard, :strict, or :runtime
|
|
242
|
+
|
|
243
|
+
# Inclusion helpers for the public mixin.
|
|
158
244
|
class << self
|
|
245
|
+
# Installs the pattern when Ruby evaluates `include`.
|
|
246
|
+
# @private
|
|
159
247
|
def included(base)
|
|
160
248
|
Internal.install_singleton!(base)
|
|
161
249
|
end
|
|
162
250
|
|
|
251
|
+
# Builds an independent module with an include-time hardening mode.
|
|
252
|
+
#
|
|
253
|
+
# The default is captured when this method is called. Mode validation occurs
|
|
254
|
+
# when the returned module is included in a class.
|
|
255
|
+
#
|
|
256
|
+
# @param mode [Symbol, String, nil] :standard, :strict, or :runtime;
|
|
257
|
+
# nil uses the configured default
|
|
258
|
+
# @param options [Hash] keyword form of the mode
|
|
259
|
+
# @option options [Symbol, String] :mode alternative to positional mode
|
|
260
|
+
# @return [Module] module to include in the managed class
|
|
261
|
+
# @raise [ArgumentError] if both mode forms or unknown options are supplied
|
|
262
|
+
# @raise [Error] on inclusion if the resolved mode is unsupported
|
|
263
|
+
# @example Select a mode for one class
|
|
264
|
+
# class Settings
|
|
265
|
+
# include Singulus::Singleton.with(mode: :runtime)
|
|
266
|
+
# end
|
|
163
267
|
def with(mode = nil, **options)
|
|
164
268
|
resolved_mode = options.delete(:mode)
|
|
165
269
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: singulus
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Juan Furattini
|
|
@@ -18,6 +18,7 @@ executables: []
|
|
|
18
18
|
extensions: []
|
|
19
19
|
extra_rdoc_files: []
|
|
20
20
|
files:
|
|
21
|
+
- ".yardopts"
|
|
21
22
|
- CHANGELOG.md
|
|
22
23
|
- LICENSE.txt
|
|
23
24
|
- README.md
|