foobara 0.0.129 → 0.0.131

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: 15b1706d7c689286e46b908cf33a5cbb73150f3c74111b9b5b7ebb18ca44bc64
4
- data.tar.gz: b28594fe6a5e374f3c2b857b5f90c3af65b7f5e6e2b7f2ebaef806bf2c39fa39
3
+ metadata.gz: c95217bebc01ab27c28cdf572b5e19eb2902fd68b1bb4d7328c093b02cfe8dbf
4
+ data.tar.gz: 76d32046c22f96ebf9e73d738aa4154fea3edb740e66fab71954abbd8594a35c
5
5
  SHA512:
6
- metadata.gz: 7fdd98642fe183f25a31d680c0362828d7cd8d68a9f0e02d80235991a3bf67a7c4e8671fc9552da3ed50e991bca009912e8de6fa688c8039eef61a4d752b52f6
7
- data.tar.gz: 63ad9eadd3b77791cc6a04f63831cacfd11cc06b04691d93bba68ab309a506326c44dcccd664bc14d57914e7c4bca1425c8a99ea36e93e3259f0e173f681779e
6
+ metadata.gz: 148fe1548cc4731673ffa299fe1d5ef96a8377cdbc613e3aeff989b16efbc250beea9b58f1b3b1727d0b399b5f1925186c1ac97908c8e2bf6113f4ff0cba1835
7
+ data.tar.gz: e49bad06d6f305b4e1716fd56fa47298028c6b699977e95417e9013c95f3a25a90ca8a9b2d96b8a11c9fb5989b56587ee87d7b719d2397b90d95210168a594ce
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ # [0.0.131] - 2025-06-16
2
+
3
+ - Extract InMemoryMinimal crud driver specs to foobara-crud-driver-spec-helpers gem
4
+ - Better support for nested transactions
5
+ - Fix buggy actions carried out when committing/rolling back transactions
6
+ - Fix thread leaks in WeakObjectSet and test suite
7
+
8
+ # [0.0.130] - 2025-06-06
9
+
10
+ - Support using a proc as an attributes default for lazy evaluation of default values
11
+
1
12
  # [0.0.129] - 2025-05-30
2
13
 
3
14
  - Store the raw, unprocessed, command result for debugging/introspection
data/README.md CHANGED
@@ -3,7 +3,7 @@ domain operations in commands, and automatically expose machine-readable formal
3
3
  commands so that integration code can be decoupled and abstracted away.
4
4
 
5
5
  This, as well as some other features of foobara, help manage domain complexity and produce
6
- more flexible systems with better management of domain complexity.
6
+ more flexible systems.
7
7
 
8
8
  You can watch a video that gives a good overview of what Foobara is and its goals here:
9
9
  [Introduction to the Foobara software framework](https://youtu.be/SSOmQqjNSVY)
@@ -27,7 +27,7 @@ You can watch a video that gives a good overview of what Foobara is and its goal
27
27
  * [HTTP Command Connectors](#http-command-connectors)
28
28
  * [Rack Connector](#rack-connector)
29
29
  * [Rails Connector](#rails-connector)
30
- * [MCP Command Connector](#mcp-command-connector)
30
+ * [MCP Command Connector](#mcp-command-connector)
31
31
  * [Async Command Connectors](#async-command-connectors)
32
32
  * [Scheduler Command Connectors](#scheduler-command-connectors)
33
33
  * [Intermediate Foobara](#intermediate-foobara)
@@ -834,7 +834,7 @@ end
834
834
 
835
835
  This has the same effect as the previous code and is just a stylistic alternative.
836
836
 
837
- ### MCP Command Connector
837
+ #### MCP Command Connector
838
838
 
839
839
  We can have an MCP server for free for our commands. Let's try it!
840
840
 
@@ -20,11 +20,11 @@ module Foobara
20
20
  allow_nil = parent_declaration_data[:element_type_declarations][attribute_name][:allow_nil]
21
21
 
22
22
  unless allow_nil
23
- to_apply[attribute_name] = default
23
+ to_apply[attribute_name] = default.is_a?(Proc) ? default.call : default
24
24
  end
25
25
  end
26
26
  else
27
- to_apply[attribute_name] = default
27
+ to_apply[attribute_name] = default.is_a?(Proc) ? default.call : default
28
28
  end
29
29
  end
30
30
 
@@ -1,3 +1,5 @@
1
+ require "yaml"
2
+
1
3
  module Foobara
2
4
  module CommandConnectors
3
5
  module Serializers
@@ -86,7 +86,10 @@ module Foobara
86
86
 
87
87
  defaults = attributes_type.declaration_data[:defaults]
88
88
  if defaults && !defaults.empty?
89
- record.write_attributes_without_callbacks(defaults)
89
+ resolved_defaults = defaults.transform_values do |value|
90
+ value.is_a?(Proc) ? value.call : value
91
+ end
92
+ record.write_attributes_without_callbacks(resolved_defaults)
90
93
  end
91
94
 
92
95
  record.write_attributes_without_callbacks(attributes)
@@ -20,7 +20,7 @@ module Foobara
20
20
  # TODO: all multiple record methods should return enumerators and code further up should only use
21
21
  # the lazy enumerator interface... to encourage that/catch bugs we will return lazy enumerators in these
22
22
  # built-in crud drivers
23
- def all
23
+ def all(page_size: nil)
24
24
  records.each_value.lazy
25
25
  end
26
26
 
@@ -32,16 +32,6 @@ module Foobara
32
32
  Util.deep_dup(records[record_id])
33
33
  end
34
34
 
35
- def find!(record_id)
36
- attributes = find(record_id)
37
-
38
- unless attributes
39
- raise CannotFindError.new(record_id, "does not exist")
40
- end
41
-
42
- attributes
43
- end
44
-
45
35
  def insert(attributes)
46
36
  attributes = Util.deep_dup(attributes)
47
37
 
@@ -273,7 +273,7 @@ module Foobara
273
273
  filter, method, bang = _filter_from_method_name(method_name)
274
274
 
275
275
  if filter
276
- method = "foobara_#{method}#{bang ? "!" : ""}"
276
+ method = "foobara_#{method}#{"!" if bang}"
277
277
  send(method, *, **, filter:, &)
278
278
  else
279
279
  # :nocov:
@@ -4,6 +4,12 @@ module Foobara
4
4
  class EntityAttributesCrudDriver
5
5
  attr_accessor :raw_connection, :tables
6
6
 
7
+ class << self
8
+ def has_real_transactions?
9
+ false
10
+ end
11
+ end
12
+
7
13
  def initialize(connection_or_credentials = nil)
8
14
  self.raw_connection = open_connection(connection_or_credentials)
9
15
  self.tables = {}
@@ -31,7 +37,7 @@ module Foobara
31
37
  def rollback_transaction(_raw_tx)
32
38
  end
33
39
 
34
- def close_transaction(_raw_tx)
40
+ def commit_transaction(_raw_tx)
35
41
  end
36
42
 
37
43
  def table_for(entity_class)
@@ -91,7 +97,7 @@ module Foobara
91
97
  # :nocov:
92
98
  end
93
99
 
94
- def all
100
+ def all(page_size: nil)
95
101
  # :nocov:
96
102
  raise "subclass responsibility"
97
103
  # :nocov:
@@ -107,10 +113,14 @@ module Foobara
107
113
  # :nocov:
108
114
  end
109
115
 
110
- def find!(_record_id)
111
- # :nocov:
112
- raise "subclass responsibility"
113
- # :nocov:
116
+ def find!(record_id)
117
+ attributes = find(record_id)
118
+
119
+ unless attributes
120
+ raise CannotFindError.new(record_id, "does not exist")
121
+ end
122
+
123
+ attributes
114
124
  end
115
125
 
116
126
  def find_many!(record_ids)
@@ -157,39 +167,22 @@ module Foobara
157
167
 
158
168
  def matches_attributes_filter?(attributes, attributes_filter)
159
169
  attributes_filter.all? do |attribute_name_or_path, value|
160
- value = normalize_attribute_filter_value(value)
170
+ # get the model-free type?
171
+ attribute_type = entity_class.attributes_type.type_at_path(attribute_name_or_path)
172
+
173
+ value = restore_attributes(value, attribute_type)
161
174
 
162
175
  if attribute_name_or_path.is_a?(::Array)
163
176
  values = DataPath.values_at(attribute_name_or_path, attributes)
164
177
 
165
178
  values.any? do |attribute_value|
166
- normalize_attribute_filter_value(attribute_value) == value
179
+ restore_attributes(attribute_value, attribute_type) == value
167
180
  end
168
181
  else
169
- attribute_value = attributes[attribute_name_or_path]
170
- normalize_attribute_filter_value(attribute_value) == value
171
- end
172
- end
173
- end
174
-
175
- def normalize_attribute_filter_value(value)
176
- case value
177
- when ::Array
178
- value.map { |v| normalize_attribute_filter_value(v) }
179
- when ::Hash
180
- value.to_h do |k, v|
181
- [normalize_attribute_filter_value(k), normalize_attribute_filter_value(v)]
182
- end
183
- when DetachedEntity
184
- if value.persisted?
185
- normalize_attribute_filter_value(value.primary_key)
186
- else
187
- value
182
+ attribute_value = DataPath.value_at(attribute_name_or_path, attributes)
183
+ attribute_value = restore_attributes(attribute_value, attribute_type)
184
+ attribute_value == value
188
185
  end
189
- when Model
190
- normalize_attribute_filter_value(value.attributes)
191
- else
192
- value
193
186
  end
194
187
  end
195
188
 
@@ -258,6 +251,50 @@ module Foobara
258
251
  def primary_key_attribute
259
252
  entity_class.primary_key_attribute
260
253
  end
254
+
255
+ def restore_attributes(object, type = entity_class.attributes_type)
256
+ if type.extends?(BuiltinTypes[:attributes])
257
+ object.to_h do |attribute_name, attribute_value|
258
+ attribute_type = type.type_at_path(attribute_name)
259
+ [attribute_name.to_sym, restore_attributes(attribute_value, attribute_type)]
260
+ end
261
+ elsif type.extends?(BuiltinTypes[:tuple])
262
+ # TODO: test this code path
263
+ # :nocov:
264
+ object.map.with_index do |value, index|
265
+ element_type = type.element_types[index]
266
+ restore_attributes(value, element_type)
267
+ end
268
+ # :nocov:
269
+ elsif type.extends?(BuiltinTypes[:array])
270
+ element_type = type.element_type
271
+ object.map { |value| restore_attributes(value, element_type) }
272
+ elsif type.extends?(BuiltinTypes[:entity])
273
+ if object.is_a?(Model)
274
+ if object.persisted?
275
+ object = object.primary_key
276
+ restore_attributes(object, type.target_class.primary_key_type)
277
+ else
278
+ object
279
+ end
280
+ else
281
+ restore_attributes(object, type.target_class.primary_key_type)
282
+ end
283
+ elsif type.extends?(BuiltinTypes[:model])
284
+ if object.is_a?(Model)
285
+ object = object.attributes
286
+ end
287
+ restore_attributes(object, type.element_types)
288
+ else
289
+ outcome = type.process_value(object)
290
+
291
+ if outcome.success?
292
+ outcome.result
293
+ else
294
+ object
295
+ end
296
+ end
297
+ end
261
298
  end
262
299
  end
263
300
  end
@@ -15,12 +15,20 @@ module Foobara
15
15
  end
16
16
  end
17
17
 
18
+ def open_nested!(outer_tx)
19
+ state_machine.open_nested! do
20
+ self.is_nested = true
21
+ self.raw_tx = outer_tx.raw_tx
22
+ end
23
+ end
24
+
18
25
  def flush!
19
26
  state_machine.flush! do
20
27
  each_table(&:validate!)
21
28
  each_table(&:flush_created!)
22
29
  each_table(&:flush_updated_and_hard_deleted!)
23
30
  end
31
+ entity_attributes_crud_driver.flush_transaction(raw_tx)
24
32
  rescue => e
25
33
  # :nocov:
26
34
  rollback!(e)
@@ -34,6 +42,7 @@ module Foobara
34
42
  state_machine.revert! do
35
43
  each_table(&:revert!)
36
44
  end
45
+ entity_attributes_crud_driver.revert_transaction(raw_tx)
37
46
  rescue => e
38
47
  # :nocov:
39
48
  rollback!(e)
@@ -42,11 +51,25 @@ module Foobara
42
51
  end
43
52
 
44
53
  def commit!
54
+ return commit_nested! if nested?
55
+
45
56
  state_machine.commit! do
46
- each_table(&:validate!)
47
- each_table(&:flush_created!)
48
- each_table(&:flush_updated_and_hard_deleted!)
49
- entity_attributes_crud_driver.close_transaction(raw_tx)
57
+ each_table(&:commit!)
58
+ entity_attributes_crud_driver.commit_transaction(raw_tx)
59
+ each_table(&:transaction_closed)
60
+ end
61
+ rescue => e
62
+ # :nocov:
63
+ rollback!(e)
64
+ raise
65
+ # :nocov:
66
+ end
67
+
68
+ def commit_nested!
69
+ state_machine.commit_nested! do
70
+ each_table(&:commit!)
71
+ entity_attributes_crud_driver.flush_transaction(raw_tx)
72
+ each_table(&:transaction_closed)
50
73
  end
51
74
  rescue => e
52
75
  # :nocov:
@@ -61,13 +84,32 @@ module Foobara
61
84
  end
62
85
 
63
86
  def rollback!(because_of = nil)
87
+ return rollback_nested!(because_of) if nested?
88
+
64
89
  state_machine.rollback! do
65
90
  # TODO: raise error if already flushed and if crud_driver doesn't support true transactions
66
91
  entity_attributes_crud_driver.rollback_transaction(raw_tx)
67
92
  each_table(&:rollback!)
68
- entity_attributes_crud_driver.close_transaction(raw_tx)
69
93
  end
70
94
 
95
+ each_table(&:transaction_closed)
96
+
97
+ if !because_of && (self == entity_base.current_transaction)
98
+ raise RolledBack, "intentionally rolled back"
99
+ end
100
+ rescue
101
+ state_machine.error! if state_machine.currently_open?
102
+ raise
103
+ end
104
+
105
+ def rollback_nested!(because_of = nil)
106
+ state_machine.rollback_nested! do
107
+ entity_attributes_crud_driver.revert_transaction(raw_tx)
108
+ each_table(&:revert!)
109
+ end
110
+
111
+ each_table(&:transaction_closed)
112
+
71
113
  if !because_of && (self == entity_base.current_transaction)
72
114
  raise RolledBack, "intentionally rolled back"
73
115
  end
@@ -7,6 +7,7 @@ module Foobara
7
7
  set_transition_map({
8
8
  unopened: {
9
9
  open: :open,
10
+ open_nested: :open,
10
11
  close: :closed
11
12
  },
12
13
  open: {
@@ -17,7 +18,9 @@ module Foobara
17
18
  # TODO: should we have intermediate states to quickly get out of the open state?
18
19
  rollback: :closed,
19
20
  commit: :closed,
20
- error: :closed
21
+ error: :closed,
22
+ commit_nested: :closed,
23
+ rollback_nested: :closed
21
24
  }
22
25
  })
23
26
  end
@@ -6,7 +6,7 @@ module Foobara
6
6
  include Concerns::EntityCallbackHandling
7
7
  include Concerns::TransactionTracking
8
8
 
9
- attr_accessor :state_machine, :entity_base, :raw_tx, :tables
9
+ attr_accessor :state_machine, :entity_base, :raw_tx, :tables, :is_nested
10
10
 
11
11
  def initialize(entity_base)
12
12
  self.entity_base = entity_base
@@ -162,6 +162,10 @@ module Foobara
162
162
  def perform(&)
163
163
  entity_base.using_transaction(self, &)
164
164
  end
165
+
166
+ def nested?
167
+ is_nested
168
+ end
165
169
  end
166
170
  end
167
171
  end
@@ -83,10 +83,25 @@ module Foobara
83
83
  end
84
84
 
85
85
  def rolled_back
86
+ closed
87
+ end
88
+
89
+ def committed
90
+ closed
91
+ end
92
+
93
+ def closed
86
94
  marked_hard_deleted.clear
87
95
  marked_updated.clear
88
96
  marked_created.clear
89
- tracked_records.clear
97
+ marked_loading.clear
98
+ end
99
+
100
+ # We need to clear this one separately. That's because otherwise a different table
101
+ # might flush and create a thunk if it has an association to this table but we've stopped
102
+ # tracking the record.
103
+ def transaction_closed
104
+ tracked_records.close
90
105
  end
91
106
 
92
107
  def reverted
@@ -86,6 +86,7 @@ module Foobara
86
86
  # :nocov:
87
87
  end
88
88
 
89
+ # rubocop:disable Lint/IdentityComparison
89
90
  if entity &&
90
91
  (!entity.equal?(entity_or_record_id) || entity.object_id != entity_or_record_id.object_id)
91
92
  # :nocov:
@@ -93,6 +94,7 @@ module Foobara
93
94
  "Try passing in the primary key instead of constructing an unloaded entity to pass in."
94
95
  # :nocov:
95
96
  end
97
+ # rubocop:enable Lint/IdentityComparison
96
98
 
97
99
  record_id = entity.primary_key
98
100
  else
@@ -630,6 +632,14 @@ module Foobara
630
632
  rolled_back
631
633
  end
632
634
 
635
+ def commit!
636
+ validate!
637
+ flush_created!
638
+ flush_updated_and_hard_deleted!
639
+
640
+ committed
641
+ end
642
+
633
643
  def revert!
634
644
  # TODO: could pause record tracking while doing this as a performance boost
635
645
  marked_updated.each(&:restore_without_callbacks!)
@@ -52,7 +52,31 @@ module Foobara
52
52
  Thread.inheritable_thread_local_var_set(transaction_key, transaction)
53
53
  end
54
54
 
55
- VALID_MODES = [:use_existing, :open_nested, :open_new, nil].freeze
55
+ # What types of transaction scenarios are there?
56
+ # 1. If a transaction is already open, use it as a "nested transaction", otherwise, open a new one.
57
+ # A nested transaction means that "rollback" is the same as "revert" and "commit" is the same as "flush".
58
+ # For a true
59
+ # 2. If a transaction is already open, raise an error. otherwise, open a new one.
60
+ # 3. Open a new, independent transaction, no matter what.
61
+ # 4. If a transaction is already open, use it, otherwise, open a new one.
62
+ # 5. We are outside of a transaction but have a handle on one. We want to set it as the current transaction.
63
+ # and do some work in that transaction.
64
+ # Which use cases do we probably need at the moment?
65
+ # 1. If we are running a command calling other commands, we will open transactions when needed but
66
+ # inherit any already-open transactions. Commands don't commit or flush to the already-open transactions
67
+ # that they inherit. So this feels like a "use existing" situation or a situation where we don't even
68
+ # bother calling open_transaction at all. This is the most important use-case. It can be helpful to raise
69
+ # in this situation because it is not expected that there's an existing transaction yet we're opening another.
70
+ # 2. We might have a situation where we are in one transaction but definitely want to open a new one and
71
+ # commit it ourselves and have its results committed and visible independent of the current transaction.
72
+ # So this feels like a "open new" situation where we don't want to raise an error if a transaction is
73
+ # already open.
74
+ VALID_MODES = [
75
+ :use_existing,
76
+ :open_nested,
77
+ :open_new,
78
+ nil
79
+ ].freeze
56
80
 
57
81
  def using_transaction(existing_transaction, &)
58
82
  transaction(existing_transaction:, &)
@@ -67,18 +91,28 @@ module Foobara
67
91
 
68
92
  old_transaction = current_transaction
69
93
 
70
- if old_transaction&.closed?
94
+ if old_transaction && !old_transaction.currently_open?
71
95
  old_transaction = nil
72
96
  end
73
97
 
74
- if old_transaction&.currently_open?
98
+ open_nested = false
99
+
100
+ if old_transaction
75
101
  if mode == :use_existing || existing_transaction == old_transaction
76
102
  if block_given?
77
103
  return yield old_transaction
78
104
  else
79
105
  return old_transaction
80
106
  end
81
- elsif mode != :open_nested && mode != :open_new
107
+ elsif mode == :open_nested
108
+ open_nested = true
109
+ elsif mode == :open_new
110
+ if existing_transaction
111
+ # :nocov:
112
+ raise ArgumentError, "Cannot use mode :open_new with existing_transaction:"
113
+ # :nocov:
114
+ end
115
+ else
82
116
  # :nocov:
83
117
  raise "Transaction already open. " \
84
118
  "Use mode :use_existing if you want to make use of the existing transaction. " \
@@ -96,16 +130,27 @@ module Foobara
96
130
  tx = existing_transaction
97
131
  else
98
132
  tx = Transaction.new(self)
99
- tx.open!
133
+
134
+ if open_nested
135
+ tx.open_nested!(old_transaction)
136
+ else
137
+ tx.open!
138
+ end
100
139
  end
101
140
 
102
141
  set_current_transaction(tx)
142
+
103
143
  result = yield tx
104
- tx.commit! if tx.currently_open? && !existing_transaction
144
+
145
+ if tx.currently_open? && !existing_transaction
146
+ tx.commit!
147
+ end
105
148
  result
106
149
  rescue Foobara::Persistence::EntityBase::Transaction::RolledBack # rubocop:disable Lint/SuppressedException
107
150
  rescue => e
108
- tx.rollback!(e) if tx.currently_open?
151
+ if tx.currently_open?
152
+ tx.rollback!(e)
153
+ end
109
154
  raise
110
155
  ensure
111
156
  set_current_transaction(old_transaction)
@@ -4,8 +4,6 @@ module Foobara
4
4
  # TODO: a possible optimization: have a certain number of records before the Weakref approach kicks in
5
5
  # that way we don't just immediately clear out useful information without any actual memory burden
6
6
  class WeakObjectSet
7
- class InvalidWtf < StandardError; end
8
-
9
7
  class GarbageCleaner
10
8
  attr_accessor :weak_object_set, :deactivated, :queue, :cleanup_thread
11
9
 
@@ -17,6 +15,12 @@ module Foobara
17
15
  end
18
16
 
19
17
  def cleanup_proc
18
+ if deactivated?
19
+ # :nocov:
20
+ raise "GarbageCleaner has been deactivated"
21
+ # :nocov:
22
+ end
23
+
20
24
  @cleanup_proc ||= begin
21
25
  queue = self.queue
22
26
 
@@ -35,6 +39,8 @@ module Foobara
35
39
  end
36
40
 
37
41
  def start_cleanup_thread
42
+ queue = self.queue
43
+
38
44
  self.cleanup_thread = Thread.new do
39
45
  loop do
40
46
  object_id = queue.pop
@@ -57,9 +63,16 @@ module Foobara
57
63
  end
58
64
 
59
65
  def deactivate
66
+ raise if deactivated?
67
+
60
68
  self.deactivated = true
61
69
  queue.close
70
+ # TODO: don't bother to join here outside of test suite
62
71
  cleanup_thread.join # just doing this for test suite/simplecov
72
+ @cleanup_proc = nil
73
+ @queue = nil
74
+ @weak_object_set = nil
75
+ @cleanup_thread = nil
63
76
  end
64
77
 
65
78
  def deactivated?
@@ -69,7 +82,7 @@ module Foobara
69
82
 
70
83
  include Enumerable
71
84
 
72
- attr_accessor :monitor, :key_method, :key_to_object_id, :object_id_to_key, :objects
85
+ attr_accessor :monitor, :key_method, :key_to_object_id, :object_id_to_key, :objects, :closed
73
86
  attr_writer :garbage_cleaner
74
87
 
75
88
  def initialize(key_method = nil)
@@ -136,19 +149,17 @@ module Foobara
136
149
  @garbage_cleaner ||= begin
137
150
  queue = Queue.new
138
151
 
139
- gc = GarbageCleaner.new(self, queue)
140
-
141
- ObjectSpace.define_finalizer gc do
142
- # :nocov:
143
- queue.close
144
- # :nocov:
145
- end
146
-
147
- gc
152
+ GarbageCleaner.new(self, queue)
148
153
  end
149
154
  end
150
155
 
151
156
  def <<(object)
157
+ if closed?
158
+ # :nocov:
159
+ raise "Cannot add objects to a closed WeakObjectSet"
160
+ # :nocov:
161
+ end
162
+
152
163
  object_id = object.object_id
153
164
 
154
165
  monitor.synchronize do
@@ -235,11 +246,30 @@ module Foobara
235
246
  end
236
247
  end
237
248
 
249
+ def close
250
+ raise if closed?
251
+
252
+ self.closed = true
253
+ stop_garbage_cleaner
254
+ end
255
+
256
+ def stop_garbage_cleaner
257
+ gc = nil
258
+
259
+ monitor.synchronize do
260
+ if @garbage_cleaner
261
+ gc = garbage_cleaner
262
+ self.garbage_cleaner = nil
263
+ end
264
+ end
265
+
266
+ gc&.deactivate
267
+ end
268
+
238
269
  def clear
239
270
  monitor.synchronize do
240
- garbage_cleaner.deactivate
271
+ stop_garbage_cleaner
241
272
 
242
- self.garbage_cleaner = nil
243
273
  self.objects = {}
244
274
 
245
275
  if key_method
@@ -248,5 +278,9 @@ module Foobara
248
278
  end
249
279
  end
250
280
  end
281
+
282
+ def closed?
283
+ closed
284
+ end
251
285
  end
252
286
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foobara
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.129
4
+ version: 0.0.131
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miles Georgi
@@ -41,16 +41,16 @@ dependencies:
41
41
  name: foobara-util
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
- - - "~>"
44
+ - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: 0.0.11
46
+ version: 1.0.0
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - "~>"
51
+ - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: 0.0.11
53
+ version: 1.0.0
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: inheritable-thread-vars
56
56
  requirement: !ruby/object:Gem::Requirement
@@ -486,8 +486,8 @@ licenses:
486
486
  - MPL-2.0
487
487
  metadata:
488
488
  homepage_uri: https://foobara.com
489
- source_code_uri: https://foobara.com
490
- changelog_uri: https://foobara.com/blob/main/CHANGELOG.md
489
+ source_code_uri: https://github.com/foobara/foobara
490
+ changelog_uri: https://github.com/foobara/foobara/blob/main/CHANGELOG.md
491
491
  rubygems_mfa_required: 'true'
492
492
  rdoc_options: []
493
493
  require_paths:
@@ -529,7 +529,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
529
529
  - !ruby/object:Gem::Version
530
530
  version: '0'
531
531
  requirements: []
532
- rubygems_version: 3.6.7
532
+ rubygems_version: 3.6.9
533
533
  specification_version: 4
534
534
  summary: A command-centric and discoverable software framework with a focus on domain
535
535
  concepts and abstracting away integration code