foobara 0.1.14 → 0.1.15

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: c7ae8222d0453f7195938553867b443d09f106ebc9f5c3206313d463621e343a
4
- data.tar.gz: db414f4c72eadfd51b35d876c4f3f1c4c74e91680e1e3276164b18ed7d3e4ecd
3
+ metadata.gz: 8dd944e7f4507fadcfec80d3a79c7050f3cdce69b5dddbc7869183e865e0e1fc
4
+ data.tar.gz: a5b8bead81dabbc63f239f9c51ef792f9a8e536f516266bd0c79a5ab05ed38b4
5
5
  SHA512:
6
- metadata.gz: ee2feca5f583800b2e1ec869b5a6c3bfb530c50735e285745b8bf1fa349d3b94b36f0ec62d361180f97432dcd4fb38731a5ec4556b6dcd9c7ba707d51b772512
7
- data.tar.gz: e6cb702a8667bae3b64a93213b000c094da67a0d735ad874c94a641fcd8d1d529fab15b9c0ad80429e04a253029d154c0623abf3e6a3e9f5c91754cead1750da
6
+ metadata.gz: ac1a42bcc81cfdabfdb7b8af603f4992c273cf14ac58b974d03ec0e1cec4f997234e4a80ddd5bdfee016448c354c4f5e3b6fafff66282922b51e9fce8c94f609
7
+ data.tar.gz: c728063369dd04a29c30fa13a98a15c47ecf896b49ab920091c70dfebecc054c9df7a21674069e3873de628884a31e020bc33f0c64bfd156dca7206de92ef578
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ # [0.1.15] - 2025-10-02
2
+
3
+ - Make some more methods private
4
+ - Clear transformed command class cache and entity class paths cache on Namespace changes
5
+ - Implement Namespace.on_change
6
+ - Add WeakObjectHash project
7
+ - Remove command-named function concept and Command@all
8
+ - Don't store all domain modules created, allowing more garbage collection
9
+
1
10
  # [0.1.14] - 2025-09-27
2
11
 
3
12
  - When using aggregate/atom_entities connector options, remove default serializer/pre-commit transformers of the other
@@ -7,31 +7,6 @@ module Foobara
7
7
  def install!
8
8
  Namespace.global.foobara_add_category_for_subclass_of(:command, self)
9
9
  end
10
-
11
- def reset_all
12
- Foobara.raise_if_production!("reset_all")
13
- to_delete = []
14
-
15
- all.each do |command_class|
16
- if command_class.name.include?("::")
17
- parent_name = Util.parent_module_name_for(command_class.name)
18
-
19
- if Object.const_defined?(parent_name)
20
- command_class.undefine_command_named_function
21
- else
22
- to_delete << command_class
23
- end
24
- else
25
- command_class.undefine_command_named_function
26
- end
27
- end
28
-
29
- to_delete.each do |command_class|
30
- all.delete(command_class)
31
- end
32
-
33
- super
34
- end
35
10
  end
36
11
  end
37
12
  end
@@ -1,7 +1,5 @@
1
1
  module Foobara
2
2
  class Command
3
3
  include CommandPatternImplementation
4
- # Maybe make ShortcutForRun optional and maybe even move it to a different repository?
5
- include Concerns::ShortcutForRun
6
4
  end
7
5
  end
@@ -6,19 +6,7 @@ module Foobara
6
6
  module Callbacks
7
7
  include Concern
8
8
 
9
- inherited_overridable_class_attr_accessor :subclass_defined_callbacks
10
-
11
9
  module ClassMethods
12
- def inherited(subclass)
13
- super
14
-
15
- subclass_defined_callbacks.runner.callback_data(subclass).run
16
- end
17
-
18
- def after_subclass_defined(&)
19
- subclass_defined_callbacks.register_callback(:after, &)
20
- end
21
-
22
10
  def state_machine_callback_registry
23
11
  return @state_machine_callback_registry if defined?(@state_machine_callback_registry)
24
12
 
@@ -100,10 +88,6 @@ module Foobara
100
88
  def state_machine_callback_registry
101
89
  state_machine.callback_registry
102
90
  end
103
-
104
- on_include do
105
- self.subclass_defined_callbacks ||= Foobara::Callback::Registry::SingleAction.new
106
- end
107
91
  end
108
92
  end
109
93
  end
@@ -21,7 +21,6 @@ module Foobara
21
21
  end
22
22
 
23
23
  def entity_class_paths
24
- # TODO: bust this cache when changing inputs_type??
25
24
  @entity_class_paths ||= Entity.construct_associations(
26
25
  inputs_type
27
26
  ).transform_values(&:target_class)
@@ -16,6 +16,10 @@ module Foobara
16
16
  remove_instance_variable(:@inputs_association_paths)
17
17
  end
18
18
 
19
+ if defined?(@entity_class_paths)
20
+ remove_instance_variable(:@entity_class_paths)
21
+ end
22
+
19
23
  type = type_for_declaration(...)
20
24
 
21
25
  if type.extends?(BuiltinTypes[:model]) && !type.extends?(BuiltinTypes[:entity])
@@ -5,21 +5,7 @@ module Foobara
5
5
  include Concern
6
6
  include Manifestable
7
7
 
8
- def initialize(...)
9
- self.class.all << self
10
- super
11
- end
12
-
13
8
  module ClassMethods
14
- def all
15
- @all ||= []
16
- end
17
-
18
- def reset_all
19
- Foobara.raise_if_production!("reset_all")
20
- remove_instance_variable("@all") if instance_variable_defined?("@all")
21
- end
22
-
23
9
  def foobara_manifest
24
10
  to_include = TypeDeclarations.foobara_manifest_context_to_include
25
11
 
@@ -74,6 +74,12 @@ module Foobara
74
74
  outcome&.success?
75
75
  end
76
76
 
77
+ def halt!
78
+ raise Halt
79
+ end
80
+
81
+ private
82
+
77
83
  def run_execute
78
84
  self.raw_result = execute
79
85
  result = process_result_using_result_type(raw_result)
@@ -129,12 +135,6 @@ module Foobara
129
135
  # can override if desired, default is a no-op
130
136
  end
131
137
 
132
- def halt!
133
- raise Halt
134
- end
135
-
136
- private
137
-
138
138
  def invoke_with_callbacks_and_transition_in_transaction(transition_or_transitions)
139
139
  Persistence::EntityBase.using_transactions(transactions) do
140
140
  invoke_with_callbacks_and_transition(transition_or_transitions)
@@ -16,6 +16,8 @@ module Foobara
16
16
  is_subcommand
17
17
  end
18
18
 
19
+ private
20
+
19
21
  def run_subcommand!(subcommand_class, inputs = {})
20
22
  domain = self.class.domain
21
23
  sub_domain = subcommand_class.domain
@@ -118,6 +118,12 @@ module Foobara
118
118
  pre_commit_transformers.uniq!
119
119
  end
120
120
  end
121
+
122
+ Namespace.on_change(self, :clear_caches)
123
+ end
124
+
125
+ def clear_caches
126
+ @transformed_command_class = nil
121
127
  end
122
128
 
123
129
  def _has_delegated_attributes?(type)
@@ -12,10 +12,6 @@ module Foobara
12
12
 
13
13
  module DomainModuleExtension
14
14
  class << self
15
- def all
16
- @all ||= []
17
- end
18
-
19
15
  # TODO: rename this
20
16
  def _copy_constants(old_mod, new_class)
21
17
  old_mod.constants.each do |const_name|
@@ -55,10 +51,6 @@ module Foobara
55
51
  include Concern
56
52
  include Manifestable
57
53
 
58
- on_include do
59
- DomainModuleExtension.all << self
60
- end
61
-
62
54
  module ClassMethods
63
55
  attr_writer :foobara_domain_name, :foobara_full_domain_name
64
56
 
@@ -16,7 +16,7 @@ module Foobara
16
16
  # could be independent projects
17
17
  projects "delegate", # Let's just kill delegate
18
18
  "concerns",
19
- "weak_object_set",
19
+ "weak_object_hash",
20
20
  "enumerated",
21
21
  "callback",
22
22
  "state_machine",
@@ -34,6 +34,8 @@ module Foobara
34
34
  "model",
35
35
  "detached_entity",
36
36
  "entity",
37
+ # only used by entity persistence so loading it here
38
+ "weak_object_set",
37
39
  "persistence",
38
40
  "nested_transactionable",
39
41
  "model_attribute_helpers",
@@ -114,6 +114,8 @@ module Foobara
114
114
  # TODO: do we really need to clear the whole cache? Why not just the possible
115
115
  # impacted keys based on scoped.scoped_path ?
116
116
  Namespace.clear_lru_cache!
117
+ Namespace.fire_changed!
118
+
117
119
  if scoped.unregistered_foobara_manifest_reference
118
120
  scoped.unregistered_foobara_manifest_reference = nil
119
121
  end
@@ -139,6 +141,7 @@ module Foobara
139
141
  scoped.scoped_unregistered!
140
142
 
141
143
  Namespace.clear_lru_cache!
144
+ Namespace.fire_changed!
142
145
  end
143
146
 
144
147
  def foobara_unregister_all
@@ -148,7 +151,7 @@ module Foobara
148
151
  end
149
152
 
150
153
  def lru_cache
151
- Namespace.lru_cache
154
+ @lru_cache ||= Namespace.lru_cache
152
155
  end
153
156
 
154
157
  def foobara_lookup(path, filter: nil, mode: LookupMode::GENERAL)
@@ -50,14 +50,24 @@ module Foobara
50
50
  end
51
51
  end
52
52
 
53
+ def on_change(object, method_name)
54
+ @on_change ||= WeakObjectHash.new
55
+
56
+ @on_change[object] = method_name
57
+ end
58
+
59
+ def fire_changed!
60
+ @on_change&.each_pair do |object, method_name|
61
+ object.send(method_name)
62
+ end
63
+ end
64
+
53
65
  def lru_cache
54
66
  @lru_cache ||= Foobara::LruCache.new(1000)
55
67
  end
56
68
 
57
69
  def clear_lru_cache!
58
- if @lru_cache
59
- lru_cache.reset!
60
- end
70
+ @lru_cache&.reset!
61
71
  end
62
72
  end
63
73
 
@@ -6,15 +6,6 @@ module Foobara
6
6
  Foobara.raise_if_production!("reset_all")
7
7
  end
8
8
 
9
- # TODO: this doesn't really belong here. I think we need to maybe call reset in reverse order?
10
- Foobara::Domain::DomainModuleExtension.all.each do |domain|
11
- var = "@foobara_type_builder"
12
-
13
- if domain.instance_variable_defined?(var)
14
- domain.remove_instance_variable(var)
15
- end
16
- end
17
-
18
9
  Util.descendants(Error).each do |error_class|
19
10
  if error_class.instance_variable_defined?(:@context_type)
20
11
  error_class.remove_instance_variable(:@context_type)
@@ -155,7 +155,9 @@ module Foobara
155
155
  private
156
156
 
157
157
  def lru_cache
158
- Namespace.lru_cache
158
+ @lru_cache ||= Foobara::LruCache.new(100).tap do |cache|
159
+ Namespace.on_change(cache, :reset!)
160
+ end
159
161
  end
160
162
  end
161
163
  end
@@ -0,0 +1,3 @@
1
+ require "weakref"
2
+
3
+ class Foobara::WeakObjectHash; end
@@ -0,0 +1,225 @@
1
+ require "monitor"
2
+
3
+ module Foobara
4
+ # TODO: a possible optimization: have a certain number of records before the Weakref approach kicks in
5
+ # that way we don't just immediately clear out useful information without any actual memory burden
6
+ class WeakObjectHash
7
+ class ClosedError < StandardError; end
8
+
9
+ def initialize(skip_finalizer: false)
10
+ if skip_finalizer
11
+ self.skip_finalizer = true
12
+ end
13
+ self.monitor = Monitor.new
14
+ self.object_ids_to_weak_refs_and_values = {}
15
+ end
16
+
17
+ def []=(object, value)
18
+ object_id = object.object_id
19
+ weak_ref = WeakRef.new(object)
20
+
21
+ if closed?
22
+ raise ClosedError, "Cannot add objects to a closed WeakObjectHash"
23
+ end
24
+
25
+ monitor.synchronize do
26
+ delete(object)
27
+
28
+ object_ids_to_weak_refs_and_values[object_id] = [weak_ref, value]
29
+
30
+ unless skip_finalizer?
31
+ ObjectSpace.define_finalizer(object, finalizer_proc)
32
+ end
33
+
34
+ value
35
+ end
36
+ end
37
+
38
+ def [](object)
39
+ object_id = object.object_id
40
+
41
+ if closed?
42
+ raise ClosedError, "Cannot retrieve objects from a closed WeakObjectHash"
43
+ end
44
+
45
+ monitor.synchronize do
46
+ pair = object_ids_to_weak_refs_and_values[object_id]
47
+
48
+ return nil unless pair
49
+
50
+ weak_ref, value = pair
51
+
52
+ if weak_ref.weakref_alive?
53
+ if skip_finalizer?
54
+ if weak_ref.__getobj__ == object
55
+ value
56
+ else
57
+ # :nocov:
58
+ object_ids_to_weak_refs_and_values.delete(object_id)
59
+ nil
60
+ # :nocov:
61
+ end
62
+ else
63
+ value
64
+ end
65
+ else
66
+ # Seems unreachable... if it's been garbage collected how could we have a reference to the object
67
+ # to pass it in?
68
+ # :nocov:
69
+ object_ids_to_weak_refs_and_values.delete(object_id)
70
+ nil
71
+ # :nocov:
72
+ end
73
+ end
74
+ end
75
+
76
+ def delete(object)
77
+ object_id = object.object_id
78
+
79
+ monitor.synchronize do
80
+ pair = object_ids_to_weak_refs_and_values.delete(object_id)
81
+
82
+ return nil unless pair
83
+
84
+ weak_ref, value = pair
85
+
86
+ if weak_ref.weakref_alive?
87
+
88
+ if skip_finalizer?
89
+ if weak_ref.__getobj__ == object
90
+ value
91
+ end
92
+ else
93
+ # Hmmm, there's seemingly no safe way to remove the finalizer for the previous entry
94
+ # if it exists. This is because we can only remove all finalizers on object. Not only
95
+ # the ones we've created.
96
+ # We will just do this anyway with that caveat and maybe make this configuratble in the future.
97
+ unless skip_finalizer?
98
+ ObjectSpace.undefine_finalizer(object)
99
+ end
100
+
101
+ value
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ def each_pair
108
+ monitor.synchronize do
109
+ object_ids_to_weak_refs_and_values.each_pair do |object_id, pair|
110
+ weak_ref, value = pair
111
+
112
+ if weak_ref.weakref_alive?
113
+ yield weak_ref.__getobj__, value
114
+ else
115
+ object_ids_to_weak_refs_and_values.delete(object_id)
116
+ end
117
+ end
118
+ end
119
+
120
+ self
121
+ end
122
+
123
+ def values
124
+ values = []
125
+
126
+ monitor.synchronize do
127
+ each_pair do |_key, value|
128
+ values << value
129
+ end
130
+ end
131
+
132
+ values
133
+ end
134
+
135
+ def keys
136
+ keys = []
137
+
138
+ monitor.synchronize do
139
+ each_pair do |key, _value|
140
+ keys << key
141
+ end
142
+ end
143
+
144
+ keys
145
+ end
146
+
147
+ def size
148
+ size = 0
149
+ to_delete = nil
150
+
151
+ monitor.synchronize do
152
+ object_ids_to_weak_refs_and_values.each_pair do |object_id, pair|
153
+ weak_ref = pair.first
154
+
155
+ if weak_ref.weakref_alive?
156
+ size += 1
157
+ else
158
+ to_delete ||= []
159
+ to_delete << object_id
160
+ end
161
+ end
162
+
163
+ to_delete&.each do |object_id|
164
+ object_ids_to_weak_refs_and_values.delete(object_id)
165
+ end
166
+ end
167
+
168
+ size
169
+ end
170
+
171
+ def empty?
172
+ size == 0
173
+ end
174
+
175
+ def close!
176
+ if closed?
177
+ raise ClosedError, "Already closed"
178
+ end
179
+
180
+ monitor.synchronize do
181
+ self.closed = true
182
+ clear
183
+ @finalizer_proc = nil
184
+ self.object_ids_to_weak_refs_and_values = nil
185
+ self.monitor = nil
186
+ end
187
+ end
188
+
189
+ def clear
190
+ monitor.synchronize do
191
+ unless skip_finalizer?
192
+ object_ids_to_weak_refs_and_values.each_value do |pair|
193
+ weak_ref = pair.first
194
+
195
+ if weak_ref.weakref_alive?
196
+ ObjectSpace.undefine_finalizer(weak_ref.__getobj__)
197
+ end
198
+ end
199
+ end
200
+
201
+ object_ids_to_weak_refs_and_values.clear
202
+ end
203
+ end
204
+
205
+ def closed?
206
+ closed
207
+ end
208
+
209
+ def skip_finalizer?
210
+ @skip_finalizer
211
+ end
212
+
213
+ private
214
+
215
+ attr_accessor :object_ids_to_weak_refs_and_values, :monitor, :closed, :skip_finalizer
216
+
217
+ def finalizer_proc
218
+ @finalizer_proc ||= ->(object_id) do
219
+ unless closed?
220
+ object_ids_to_weak_refs_and_values.delete(object_id)
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
@@ -56,6 +56,8 @@ module Foobara
56
56
  end
57
57
  end
58
58
  end
59
+
60
+ cleanup_thread.priority = -2
59
61
  end
60
62
 
61
63
  def track(object)
@@ -68,7 +70,7 @@ module Foobara
68
70
  self.deactivated = true
69
71
  queue.close
70
72
  # TODO: don't bother to join here outside of test suite
71
- cleanup_thread.join # just doing this for test suite/simplecov
73
+ cleanup_thread.join if ENV["FOOBARA_ENV"] == "test" # just doing this for test suite/simplecov
72
74
  @cleanup_proc = nil
73
75
  @queue = nil
74
76
  @weak_object_set = nil
data/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Foobara
2
2
  module Version
3
- VERSION = "0.1.14".freeze
3
+ VERSION = "0.1.15".freeze
4
4
  MINIMUM_RUBY_VERSION = ">= 3.4.0".freeze
5
5
  end
6
6
  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.1.14
4
+ version: 0.1.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miles Georgi
@@ -162,7 +162,6 @@ files:
162
162
  - projects/callback/src/set.rb
163
163
  - projects/command/lib/foobara/command.rb
164
164
  - projects/command/src/command.rb
165
- - projects/command/src/command/concerns/shortcut_for_run.rb
166
165
  - projects/command/src/command_pattern_implementation.rb
167
166
  - projects/command/src/command_pattern_implementation/concerns/callbacks.rb
168
167
  - projects/command/src/command_pattern_implementation/concerns/description.rb
@@ -491,6 +490,8 @@ files:
491
490
  - projects/value/src/processor/selection.rb
492
491
  - projects/value/src/transformer.rb
493
492
  - projects/value/src/validator.rb
493
+ - projects/weak_object_hash/lib/foobara/weak_object_hash.rb
494
+ - projects/weak_object_hash/src/weak_object_hash.rb
494
495
  - projects/weak_object_set/lib/foobara/weak_object_set.rb
495
496
  - projects/weak_object_set/src/weak_object_set.rb
496
497
  - version.rb
@@ -529,6 +530,7 @@ require_paths:
529
530
  - "./projects/type_declarations/lib"
530
531
  - "./projects/types/lib"
531
532
  - "./projects/value/lib"
533
+ - "./projects/weak_object_hash/lib"
532
534
  - "./projects/weak_object_set/lib"
533
535
  required_ruby_version: !ruby/object:Gem::Requirement
534
536
  requirements:
@@ -1,64 +0,0 @@
1
- module Foobara
2
- class Command
3
- module Concerns
4
- module ShortcutForRun
5
- include Concern
6
-
7
- on_include do
8
- Command.after_subclass_defined do |subclass|
9
- Command.all << subclass
10
- # This results in being able to use the command class name instead of .run! if you want.
11
- # So instead of DoIt.run!(inputs) you can just do DoIt(inputs)
12
- # TODO: can we kill this? I don't think anything uses this nor would really need to.
13
- # Calling code could define such helper methods if desired but could be a bad idea since it is nice for
14
- # command calls to stick out as command calls which would be less obvious if they just looked like random
15
- # method calls (except that they are class case instead of underscore case)
16
- subclass.define_command_named_function
17
- end
18
- end
19
-
20
- module ClassMethods
21
- def define_command_named_function
22
- return if name.nil?
23
-
24
- command_class = self
25
- convenience_method_name = Foobara::Util.non_full_name(command_class)
26
- containing_module = Foobara::Util.module_for(command_class) || Object
27
-
28
- if containing_module.is_a?(::Class)
29
- containing_module.singleton_class.define_method convenience_method_name do |*args, **opts, &block|
30
- command_class.run!(*args, **opts, &block)
31
- end
32
-
33
- containing_module.define_method convenience_method_name do |*args, **opts, &block|
34
- command_class.run!(*args, **opts, &block)
35
- end
36
- else
37
- containing_module.module_eval do
38
- module_function
39
-
40
- define_method convenience_method_name do |*args, **opts, &block|
41
- command_class.run!(*args, **opts, &block)
42
- end
43
- end
44
- end
45
- end
46
-
47
- def undefine_command_named_function
48
- command_class = self
49
- convenience_method_name = Foobara::Util.non_full_name(command_class)
50
- containing_module = Foobara::Util.module_for(command_class) || Object
51
-
52
- return unless containing_module.respond_to?(convenience_method_name)
53
-
54
- containing_module.singleton_class.undef_method convenience_method_name
55
-
56
- if containing_module.is_a?(::Class)
57
- containing_module.undef_method convenience_method_name
58
- end
59
- end
60
- end
61
- end
62
- end
63
- end
64
- end