foobara 0.0.119 → 0.0.120

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: 5bcf730a4685cf72bb0af0b04cbf7cd7929faa672d64300444bc24f441e2f1d4
4
- data.tar.gz: edfb3e4998d9adf25271075017167ed1b750ae4bccbdec17eb2196f5c9c9ab93
3
+ metadata.gz: df43aec79b46e3e71fd064c6482bdd5dd6e0af495987d6cea1cdf97311460e91
4
+ data.tar.gz: 800093b7a126bbca27fa300c4df50b568b329c762ad7a2c68925f4a3d5690090
5
5
  SHA512:
6
- metadata.gz: 6484e4c0fe4d50946922b0dedf3cd0a443c89c21294d2b8c32f9eb1433a7ddaab6cbf68715a7ad926ab256822d2c6ef0a317036bab3d5a8879ec861ef7cdddc5
7
- data.tar.gz: 9d17dfacde7d8ad2ac42635ccc11d4c2d2aed619c47ffe6d6ee5e4dfa40ba6eeb7820238fc642fa5324aa113985782e260f6d342289c760065759ddc8e7a04be
6
+ metadata.gz: 6305514bda86178efb70d09cd8ff8d46e404f0407b50189833b9939230cf716a80049afca17ea5071599bfb71f4cd45a660ade570b6bfcec063190e9b3a02096
7
+ data.tar.gz: 84705a7ff3bd306c13e7cdf5e81a3a960b0e769df51dacf38859a003da408926133c434d9d03ba2a58b70e57946b58d8355da338a0e780cab34f35a3e84df97c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # [0.0.120] - 2025-05-11
2
+
3
+ - Fix a race condition in persistence
4
+ - Fix various problems preventing sensitive/private types from being properly removed
5
+ in exposed types
6
+
1
7
  # [0.0.119] - 2025-05-08
2
8
 
3
9
  - Make anonymous transformers more deterministic in manifests
@@ -454,6 +454,13 @@ module Foobara
454
454
  end
455
455
 
456
456
  def foobara_manifest
457
+ Namespace.use command_registry do
458
+ foobara_manifest_in_current_namespace
459
+ end
460
+ end
461
+
462
+ # TODO: try to break this giant method up
463
+ def foobara_manifest_in_current_namespace
457
464
  process_delayed_connections
458
465
 
459
466
  to_include = Set.new
@@ -506,16 +513,20 @@ module Foobara
506
513
  else
507
514
  domain_name = o.foobara_domain.scoped_full_name
508
515
 
509
- unless command_registry.foobara_registered?(domain_name)
516
+ unless command_registry.foobara_registered?(domain_name, mode: Namespace::LookupMode::ABSOLUTE)
510
517
  command_registry.build_and_register_exposed_domain(domain_name)
511
518
 
512
519
  # Since we don't know which other domains/orgs creating this domain might have created,
513
520
  # we will just add them all to be included just in case
514
- command_registry.foobara_all_domain.each do |exposed_domain|
521
+ command_registry.foobara_all_domain(
522
+ mode: Namespace::LookupMode::ABSOLUTE
523
+ ).each do |exposed_domain|
515
524
  additional_to_include << exposed_domain
516
525
  end
517
526
 
518
- command_registry.foobara_all_organization.each do |exposed_organization|
527
+ command_registry.foobara_all_organization(
528
+ mode: Namespace::LookupMode::ABSOLUTE
529
+ ).each do |exposed_organization|
519
530
  additional_to_include << exposed_organization
520
531
  end
521
532
  end
@@ -612,7 +623,7 @@ module Foobara
612
623
  def all_exposed_commands
613
624
  process_delayed_connections
614
625
 
615
- command_registry.foobara_all_command
626
+ command_registry.foobara_all_command(mode: Namespace::LookupMode::ABSOLUTE)
616
627
  end
617
628
  end
618
629
  end
@@ -24,6 +24,8 @@ module Foobara
24
24
  end
25
25
 
26
26
  foobara_add_category_for_instance_of(:command, ExposedCommand)
27
+
28
+ foobara_depends_on_namespaces << Namespace.global
27
29
  end
28
30
 
29
31
  def register(command_class, **)
@@ -32,7 +34,8 @@ module Foobara
32
34
 
33
35
  def create_exposed_command(command_class, **)
34
36
  full_domain_name = command_class.domain.scoped_full_name
35
- exposed_domain = foobara_lookup_domain(full_domain_name) || build_and_register_exposed_domain(full_domain_name)
37
+ exposed_domain = foobara_lookup_domain(full_domain_name, mode: Namespace::LookupMode::ABSOLUTE) ||
38
+ build_and_register_exposed_domain(full_domain_name)
36
39
 
37
40
  exposed_command = create_exposed_command_without_domain(command_class, **)
38
41
 
@@ -75,8 +78,11 @@ module Foobara
75
78
  end
76
79
 
77
80
  full_organization_name = domain_module.foobara_full_organization_name
78
- exposed_organization = foobara_lookup_organization(full_organization_name) ||
79
- build_and_register_exposed_organization(full_organization_name)
81
+
82
+ exposed_organization = foobara_lookup_organization(
83
+ full_organization_name,
84
+ mode: Namespace::LookupMode::ABSOLUTE
85
+ ) || build_and_register_exposed_organization(full_organization_name)
80
86
 
81
87
  exposed_domain = Module.new
82
88
  exposed_domain.foobara_namespace!
@@ -3,12 +3,12 @@ module Foobara
3
3
  # TODO: move this to command connectors project
4
4
  class TransformedCommand
5
5
  class << self
6
+ # TODO: handle errors_transformers!
7
+ attr_writer :result_transformers, :inputs_transformers
6
8
  attr_accessor :command_class,
7
9
  :command_name,
8
10
  :full_command_name,
9
11
  :capture_unknown_error,
10
- :inputs_transformers,
11
- :result_transformers,
12
12
  :errors_transformers,
13
13
  :pre_commit_transformers,
14
14
  # TODO: get at least these serializers out of here...
@@ -20,7 +20,9 @@ module Foobara
20
20
  :request_mutators,
21
21
  :allowed_rule,
22
22
  :requires_authentication,
23
- :authenticator
23
+ :authenticator,
24
+ :subclassed_in_namespace,
25
+ :suffix
24
26
 
25
27
  def subclass(
26
28
  command_class,
@@ -40,28 +42,6 @@ module Foobara
40
42
  suffix: nil,
41
43
  capture_unknown_error: false
42
44
  )
43
- result_type = command_class.result_type
44
-
45
- if result_type&.has_sensitive_types?
46
- remover_class = Foobara::TypeDeclarations.sensitive_value_remover_class_for_type(result_type)
47
-
48
- remover = Namespace.use scoped_namespace do
49
- transformed_result_type = result_type_from_transformers(result_type, result_transformers)
50
-
51
- path = if transformed_result_type.scoped_path_set?
52
- transformed_result_type.scoped_full_path
53
- else
54
- [*command_class.scoped_path, *suffix, "Result"]
55
- end
56
-
57
- remover_class.new(from: transformed_result_type).tap do |r|
58
- r.scoped_path = ["SensitiveValueRemover", *path]
59
- end
60
- end
61
-
62
- result_transformers = [*result_transformers, remover]
63
- end
64
-
65
45
  Class.new(self).tap do |klass|
66
46
  klass.command_class = command_class
67
47
  klass.command_name = command_name
@@ -77,6 +57,8 @@ module Foobara
77
57
  klass.allowed_rule = allowed_rule
78
58
  klass.requires_authentication = requires_authentication
79
59
  klass.authenticator = authenticator
60
+ klass.subclassed_in_namespace = scoped_namespace
61
+ klass.suffix = suffix
80
62
  end
81
63
  end
82
64
 
@@ -85,51 +67,125 @@ module Foobara
85
67
  :organization,
86
68
  to: :command_class
87
69
 
88
- def inputs_type
89
- return @inputs_type if defined?(@inputs_type)
70
+ def result_transformers
71
+ return @result_transformers if @considered_sensitive_value_remover
90
72
 
91
- @inputs_type = if inputs_transformer
92
- if inputs_transformer.is_a?(Value::Processor::Pipeline)
93
- inputs_transformer.processors.each do |transformer|
94
- if transformer.is_a?(TypeDeclarations::TypedTransformer)
95
- from_type = transformer.from_type
96
- if from_type
97
- @inputs_type = from_type
98
- return from_type
99
- end
100
- end
101
- end
102
-
103
- command_class.inputs_type
104
- else
105
- if inputs_transformer.is_a?(TypeDeclarations::TypedTransformer)
106
- inputs_transformer.from_type
107
- end || command_class.inputs_type
108
- end
109
- else
110
- command_class.inputs_type
111
- end
73
+ @considered_sensitive_value_remover = true
74
+
75
+ result_type = command_class.result_type
76
+
77
+ result_transformers.reverse.each do |transformer|
78
+ if transformer.is_a?(TypeDeclarations::TypedTransformer) ||
79
+ (transformer.is_a?(Class) && transformer < TypeDeclarations::TypedTransformer)
80
+ new_type = transformer.to_type
81
+ if new_type
82
+ result_type = new_type
83
+ break
84
+ end
85
+ end
86
+ end
87
+
88
+ if result_type&.has_sensitive_types?
89
+ remover_class = Foobara::TypeDeclarations.sensitive_value_remover_class_for_type(result_type)
90
+
91
+ remover = Namespace.use subclassed_in_namespace do
92
+ path = if result_type.scoped_path_set?
93
+ result_type.scoped_full_path
94
+ else
95
+ [*command_class.scoped_path, *suffix, "Result"]
96
+ end
97
+
98
+ remover_class.new(from: result_type).tap do |r|
99
+ r.scoped_path = ["SensitiveValueRemover", *path]
100
+ end
101
+ end
102
+
103
+ @result_transformers = [*@result_transformers, remover]
104
+ end
105
+
106
+ @result_transformers
107
+ end
108
+
109
+ def inputs_transformers
110
+ return @inputs_transformers if @considered_inputs_sensitive_value_remover
111
+
112
+ @considered_inputs_sensitive_value_remover = true
113
+
114
+ inputs_type = command_class.inputs_type
115
+
116
+ @inputs_transformers = transformers_to_processors(@inputs_transformers, inputs_type, direction: :to)
117
+
118
+ @inputs_transformers.each do |transformer|
119
+ if transformer.is_a?(TypeDeclarations::TypedTransformer)
120
+ new_type = transformer.from_type
121
+ if new_type
122
+ inputs_type = new_type
123
+ break
124
+ end
125
+ end
126
+ end
127
+
128
+ @inputs_transformers
112
129
  end
113
130
 
114
- def result_type_from_transformers(result_type, transformers)
115
- transformers.reverse.each do |transformer|
116
- if transformer.is_a?(Class) && transformer < TypeDeclarations::TypedTransformer
131
+ def inputs_type_for_manifest
132
+ return @inputs_type_for_manifest if defined?(@inputs_type_for_manifest)
133
+
134
+ @inputs_type_for_manifest = if inputs_type&.has_sensitive_types?
135
+ remover_class = Foobara::TypeDeclarations.sensitive_value_remover_class_for_type(
136
+ inputs_type
137
+ )
138
+
139
+ Namespace.use subclassed_in_namespace do
140
+ remover_class.new(to: inputs_type).from_type
141
+ end
142
+ else
143
+ inputs_type
144
+ end
145
+ end
146
+
147
+ def inputs_type_from_transformers
148
+ return @inputs_type_from_transformers if defined?(@inputs_type_from_transformers)
149
+
150
+ @inputs_type_from_transformers = if inputs_transformer
151
+ if inputs_transformer.is_a?(Value::Processor::Pipeline)
152
+ inputs_transformer.processors.each do |transformer|
153
+ if transformer.is_a?(TypeDeclarations::TypedTransformer)
154
+ from_type = transformer.from_type
155
+ if from_type
156
+ @inputs_type_from_transformers = from_type
157
+ return from_type
158
+ end
159
+ end
160
+ end
161
+
162
+ command_class.inputs_type
163
+ else
164
+ if inputs_transformer.is_a?(TypeDeclarations::TypedTransformer)
165
+ inputs_transformer.from_type
166
+ end || command_class.inputs_type
167
+ end
168
+ else
169
+ command_class.inputs_type
170
+ end
171
+ end
172
+
173
+ def result_type_from_transformers
174
+ result_transformers.reverse.each do |transformer|
175
+ if transformer.is_a?(TypeDeclarations::TypedTransformer) ||
176
+ (transformer.is_a?(Class) && transformer < TypeDeclarations::TypedTransformer)
117
177
  new_type = transformer.to_type
118
178
  return new_type if new_type
119
179
  end
120
180
  end
121
181
 
122
- result_type
182
+ command_class.result_type
123
183
  end
124
184
 
125
185
  def result_type
126
- result_type_from_transformers(command_class.result_type, result_transformers)
127
- end
128
-
129
- def result_type_for_manifest
130
- return @result_type_for_manifest if defined?(@result_type_for_manifest)
186
+ return @result_type if defined?(@result_type)
131
187
 
132
- mutated_result_type = result_type
188
+ mutated_result_type = result_type_from_transformers
133
189
 
134
190
  mutators = if response_mutators.size == 1
135
191
  [response_mutator]
@@ -141,25 +197,25 @@ module Foobara
141
197
  mutated_result_type = mutator.result_type_from(mutated_result_type)
142
198
  end
143
199
 
144
- @result_type_for_manifest = mutated_result_type
200
+ @result_type = mutated_result_type
145
201
  end
146
202
 
147
- def inputs_type_for_manifest
148
- return @inputs_type_for_manifest if defined?(@inputs_type_for_manifest)
203
+ def inputs_type
204
+ return @inputs_type if defined?(@inputs_type)
149
205
 
150
- mutated_inputs_type = inputs_type
206
+ mutated_inputs_type = inputs_type_from_transformers
151
207
 
152
208
  mutators = if request_mutators.size == 1
153
209
  [request_mutator]
154
210
  else
155
- request_mutator&.processors&.reverse
211
+ request_mutator&.processors
156
212
  end
157
213
 
158
214
  mutators&.each do |mutator|
159
215
  mutated_inputs_type = mutator.inputs_type_from(mutated_inputs_type)
160
216
  end
161
217
 
162
- @inputs_type_for_manifest = mutated_inputs_type
218
+ @inputs_type = mutated_inputs_type
163
219
  end
164
220
 
165
221
  def error_context_type_map
@@ -211,12 +267,12 @@ module Foobara
211
267
 
212
268
  def inputs_types_depended_on
213
269
  TypeDeclarations.with_manifest_context(remove_sensitive: false) do
214
- inputs_type_for_manifest&.types_depended_on || []
270
+ inputs_type&.types_depended_on || []
215
271
  end
216
272
  end
217
273
 
218
274
  def result_types_depended_on
219
- type_proc = -> { result_type_for_manifest&.types_depended_on || [] }
275
+ type_proc = -> { result_type&.types_depended_on || [] }
220
276
 
221
277
  if TypeDeclarations.manifest_context_set?(:remove_sensitive)
222
278
  type_proc.call
@@ -231,21 +287,21 @@ module Foobara
231
287
  # TODO: memoize this
232
288
  # TODO: this should not delegate to command since transformers are in play
233
289
 
234
- type = inputs_type
290
+ types_proc = proc do
291
+ type = inputs_type_for_manifest
235
292
 
236
- if type
237
- if type.registered?
238
- # TODO: if we ever change from attributes-only inputs type
239
- # then this will be handy
240
- # :nocov:
241
- types |= [type]
242
- # :nocov:
243
- end
293
+ if type
294
+ if type.registered?
295
+ # TODO: if we ever change from attributes-only inputs type
296
+ # then this will be handy
297
+ # :nocov:
298
+ types |= [type]
299
+ # :nocov:
300
+ end
244
301
 
245
- types |= type.types_depended_on
246
- end
302
+ types |= type.types_depended_on
303
+ end
247
304
 
248
- types_proc = proc do
249
305
  type = result_type
250
306
 
251
307
  if type
@@ -319,17 +375,19 @@ module Foobara
319
375
  result_types_depended_on = self.result_types_depended_on.map(&:foobara_manifest_reference)
320
376
  result_types_depended_on = result_types_depended_on.sort
321
377
 
322
- # TODO: Do NOT defer to command_class.foobara_manifest because it might process types that
323
- # might not have an exposed command and might not need one due to transformers/mutators/remove_sensitive flag
324
- command_class.foobara_manifest.merge(
378
+ bit_bucket = Set.new
379
+ manifest = TypeDeclarations.with_manifest_context(to_include: bit_bucket) do
380
+ command_class.foobara_manifest
381
+ end
382
+
383
+ # TODO: handle errors_types_depended_on!
384
+ manifest.merge(
325
385
  Util.remove_blank(
326
386
  inputs_types_depended_on:,
327
387
  result_types_depended_on:,
328
388
  types_depended_on: types,
329
- inputs_type: TypeDeclarations.with_manifest_context(remove_sensitive: false) do
330
- inputs_type_for_manifest&.reference_or_declaration_data
331
- end,
332
- result_type: result_type_for_manifest&.reference_or_declaration_data,
389
+ inputs_type: inputs_type_for_manifest&.reference_or_declaration_data,
390
+ result_type: result_type&.reference_or_declaration_data,
333
391
  possible_errors: possible_errors_manifest,
334
392
  capture_unknown_error:,
335
393
  inputs_transformers:,
@@ -404,13 +462,16 @@ module Foobara
404
462
  def response_mutator
405
463
  return @response_mutator if defined?(@response_mutator)
406
464
 
465
+ # A hack: this will give the SensitiveValueRemover a chance to be injected
466
+ result_transformers
467
+
407
468
  if response_mutators.empty?
408
469
  @response_mutator = nil
409
470
  return
410
471
  end
411
472
 
412
473
  @response_mutator = begin
413
- transformers = transformers_to_processors(response_mutators, result_type, direction: :from)
474
+ transformers = transformers_to_processors(response_mutators, result_type_from_transformers, direction: :from)
414
475
 
415
476
  if transformers.size == 1
416
477
  transformers.first
@@ -423,13 +484,16 @@ module Foobara
423
484
  def request_mutator
424
485
  return @request_mutator if defined?(@request_mutator)
425
486
 
487
+ # HACK: to give SensitiveValueRemover a chance to be injected
488
+ inputs_transformer
489
+
426
490
  if request_mutators.empty?
427
491
  @request_mutator = nil
428
492
  return
429
493
  end
430
494
 
431
495
  @request_mutator = begin
432
- transformers = transformers_to_processors(request_mutators, result_type, direction: :to)
496
+ transformers = transformers_to_processors(request_mutators, inputs_type_from_transformers, direction: :to)
433
497
 
434
498
  if transformers.size == 1
435
499
  transformers.first
@@ -114,6 +114,7 @@ module Foobara
114
114
 
115
115
  Entity.after_initialized_created do |record:, **|
116
116
  transaction = Persistence.current_transaction(record)
117
+
117
118
  unless transaction
118
119
  raise NoCurrentTransactionError,
119
120
  "Cannot initialize #{record} because there's no current transaction"
@@ -131,6 +132,7 @@ module Foobara
131
132
 
132
133
  Entity.after_initialized_thunk do |record:, **|
133
134
  transaction = Persistence.current_transaction(record)
135
+
134
136
  unless transaction
135
137
  # :nocov:
136
138
  raise NoCurrentTransactionError,
@@ -21,9 +21,8 @@ module Foobara
21
21
  attributes = normalize_attributes(attributes)
22
22
  primary_key = primary_key_for_attributes(attributes)
23
23
 
24
- if tracked_records.include_key?(primary_key)
25
- record = tracked_records.find_by_key(primary_key)
26
-
24
+ record = tracked_records.find_by_key(primary_key)
25
+ if record
27
26
  next if record.hard_deleted?
28
27
  next if created?(record)
29
28
 
@@ -34,7 +34,7 @@ module Foobara
34
34
  end
35
35
 
36
36
  def created(record)
37
- tracked_records << record
37
+ tracked(record)
38
38
  mark_created(record)
39
39
  end
40
40
 
@@ -64,7 +64,7 @@ module Foobara
64
64
  end
65
65
 
66
66
  def updated(record)
67
- tracked_records << record
67
+ tracked(record)
68
68
 
69
69
  # TODO: is this check redundant? Maybe have the entity explode directly instead?
70
70
  if hard_deleted?(record)
@@ -65,16 +65,29 @@ module Foobara
65
65
  end
66
66
 
67
67
  def load(entity_or_record_id)
68
- if entity_or_record_id.is_a?(Entity)
69
- if entity_or_record_id.loaded?
70
- # :nocov:
71
- raise "#{entity_or_record_id} is already loaded!"
72
- # :nocov:
73
- end
74
-
75
- entity = tracked_records[entity_or_record_id]
68
+ if entity_or_record_id.nil?
69
+ # :nocov:
70
+ raise "Expected a record or record primary key but received nil"
71
+ # :nocov:
72
+ end
76
73
 
77
- if entity && !entity.equal?(entity_or_record_id)
74
+ if entity_or_record_id.is_a?(Entity)
75
+ entity = if entity_or_record_id.persisted?
76
+ unless entity_or_record_id.primary_key
77
+ # :nocov:
78
+ raise "Did not expect a record to be persisted but have no primary key"
79
+ # :nocov:
80
+ end
81
+
82
+ tracked_records.find_by_key(entity_or_record_id.primary_key)
83
+ else
84
+ # :nocov:
85
+ raise "Cannot load an unpersisted record!"
86
+ # :nocov:
87
+ end
88
+
89
+ if entity &&
90
+ (!entity.equal?(entity_or_record_id) || entity.object_id != entity_or_record_id.object_id)
78
91
  # :nocov:
79
92
  raise "This transaction is already tracking a different entity with the same primary key." \
80
93
  "Try passing in the primary key instead of constructing an unloaded entity to pass in."
@@ -151,7 +164,19 @@ module Foobara
151
164
  next
152
165
  end
153
166
 
154
- entity = tracked_records[entity_or_record_id]
167
+ entity = if entity_or_record_id.persisted?
168
+ unless entity_or_record_id.primary_key
169
+ # :nocov:
170
+ raise "Did not expect a record to be persisted but have no primary key"
171
+ # :nocov:
172
+ end
173
+
174
+ tracked_records.find_by_key(entity_or_record_id.primary_key)
175
+ else
176
+ # :nocov:
177
+ raise "Cannot load an unpersisted record!"
178
+ # :nocov:
179
+ end
155
180
 
156
181
  if entity && !entity.equal?(entity_or_record_id)
157
182
  # :nocov:
@@ -440,7 +465,7 @@ module Foobara
440
465
  end
441
466
 
442
467
  def track_loaded(entity)
443
- tracked_records << entity
468
+ tracked(entity)
444
469
  end
445
470
 
446
471
  def hard_delete_all!
@@ -527,7 +552,7 @@ module Foobara
527
552
 
528
553
  # we need to update finding the tracked object by key and removing/reading it seems to be the simplest
529
554
  # way to accomplish that at the moment
530
- tracked_records << record
555
+ tracked(record)
531
556
 
532
557
  record.is_persisted = record.is_loaded = true
533
558
  record.is_created = false
@@ -547,7 +572,7 @@ module Foobara
547
572
 
548
573
  # we need to update finding the tracked object by key and removing/reading it seems to be the simplest
549
574
  # way to accomplish that at the moment
550
- tracked_records << record
575
+ tracked(record)
551
576
 
552
577
  record.is_persisted = record.is_loaded = true
553
578
  record.is_created = false
@@ -0,0 +1,11 @@
1
+ module Foobara
2
+ class AttributesTransformers < TypeDeclarations::TypedTransformer
3
+ def initialize(...)
4
+ super
5
+
6
+ unless scoped_path_set?
7
+ self.scoped_path = self.class.scoped_path
8
+ end
9
+ end
10
+ end
11
+ end
@@ -5,20 +5,30 @@ module Foobara
5
5
  class RemoveSensitiveValuesTransformer < TypedTransformer
6
6
  def from(...)
7
7
  super.tap do
8
- associations = Foobara::DetachedEntity.construct_deep_associations(from_type)
8
+ create_all_association_types_in_current_namespace(from_type)
9
+ end
10
+ end
11
+
12
+ def to(...)
13
+ super.tap do
14
+ create_all_association_types_in_current_namespace(to_type)
15
+ end
16
+ end
9
17
 
10
- associations&.values&.reverse&.each do |entity_type|
11
- next if entity_type.sensitive?
12
- next unless entity_type.has_sensitive_types?
18
+ def create_all_association_types_in_current_namespace(type)
19
+ associations = Foobara::DetachedEntity.construct_deep_associations(type)
13
20
 
14
- declaration = entity_type.declaration_data
15
- sanitized_type_declaration = TypeDeclarations.remove_sensitive_types(declaration)
21
+ associations&.values&.reverse&.each do |entity_type|
22
+ next if entity_type.sensitive?
23
+ next unless entity_type.has_sensitive_types?
16
24
 
17
- # We want to make sure that any types that change due to having sensitive types
18
- # has a corresponding registered type in the command registry domain if needed
19
- # TODO: this all feels so messy and brittle.
20
- Domain.current.foobara_type_from_declaration(sanitized_type_declaration)
21
- end
25
+ declaration = entity_type.declaration_data
26
+ sanitized_type_declaration = TypeDeclarations.remove_sensitive_types(declaration)
27
+
28
+ # We want to make sure that any types that change due to having sensitive types
29
+ # has a corresponding registered type in the command registry domain if needed
30
+ # TODO: this all feels so messy and brittle.
31
+ Domain.current.foobara_type_from_declaration(sanitized_type_declaration)
22
32
  end
23
33
  end
24
34
 
@@ -26,6 +36,10 @@ module Foobara
26
36
  TypeDeclarations.remove_sensitive_types(from_type.declaration_data)
27
37
  end
28
38
 
39
+ def from_type_declaration
40
+ TypeDeclarations.remove_sensitive_types(to_type.declaration_data)
41
+ end
42
+
29
43
  def transform(_value)
30
44
  # :nocov:
31
45
  raise "subclass responsibility"
@@ -73,7 +73,7 @@ module Foobara
73
73
  self.to to
74
74
  end
75
75
 
76
- # we want to force these to be created now in the current name space if they are declarations
76
+ # we want to force these to be created now in the current namespace if they are declarations
77
77
  from_type
78
78
  to_type
79
79
  end
@@ -324,6 +324,12 @@ module Foobara
324
324
  def foobara_manifest
325
325
  to_include = TypeDeclarations.foobara_manifest_context_to_include
326
326
 
327
+ unless scoped_path_set?
328
+ if self.class.scoped_path_set?
329
+ self.scoped_path = self.class.scoped_path
330
+ end
331
+ end
332
+
327
333
  possible_errors = self.possible_errors.map do |possible_error|
328
334
  [possible_error.key.to_s, possible_error.foobara_manifest]
329
335
  end
@@ -1,52 +1,98 @@
1
+ require "monitor"
2
+
1
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
2
6
  class WeakObjectSet
7
+ class InvalidWtf < StandardError; end
8
+
3
9
  class GarbageCleaner
4
- def initialize(objects, key_to_object_id = nil, object_id_to_key = nil)
5
- @objects = objects
6
- @key_to_object_id = key_to_object_id
7
- @object_id_to_key = object_id_to_key
8
- end
10
+ attr_accessor :weak_object_set, :deactivated, :queue, :cleanup_thread
9
11
 
10
- def cleanup_proc
11
- @cleanup_proc ||= ->(object_id) {
12
- unless @deactivated
13
- @objects.delete(object_id)
12
+ def initialize(weak_object_set, queue)
13
+ self.queue = queue
14
+ self.weak_object_set = weak_object_set
14
15
 
15
- key = @object_id_to_key&.delete(object_id)
16
+ start_cleanup_thread
17
+ end
16
18
 
17
- if key
18
- @key_to_object_id.delete(key)
19
+ def cleanup_proc
20
+ @cleanup_proc ||= begin
21
+ queue = self.queue
22
+
23
+ ->(object_id) do
24
+ unless deactivated?
25
+ begin
26
+ queue.push(object_id)
27
+ rescue ClosedQueueError
28
+ # :nocov:
29
+ deactivate
30
+ # :nocov:
31
+ end
19
32
  end
20
33
  end
21
- }
34
+ end
22
35
  end
23
36
 
24
- def track(object)
25
- if @deactivated
26
- # :nocov:
27
- raise "Cannot track anymore objects since we have been deactivated"
28
- # :nocov:
37
+ def start_cleanup_thread
38
+ self.cleanup_thread = Thread.new do
39
+ loop do
40
+ object_id = queue.pop
41
+ if object_id
42
+ weak_object_set.delete(object_id)
43
+ elsif queue.closed?
44
+ self.queue = nil
45
+ break
46
+ else
47
+ # :nocov:
48
+ raise "Unexpected nil value in the queue"
49
+ # :nocov:
50
+ end
51
+ end
29
52
  end
53
+ end
30
54
 
55
+ def track(object)
31
56
  ObjectSpace.define_finalizer(object, cleanup_proc)
32
57
  end
33
58
 
34
59
  def deactivate
35
- @deactivated = true
60
+ self.deactivated = true
61
+ queue.close
62
+ cleanup_thread.join # just doing this for test suite/simplecov
63
+ end
64
+
65
+ def deactivated?
66
+ deactivated
36
67
  end
37
68
  end
38
69
 
39
70
  include Enumerable
40
71
 
72
+ attr_accessor :monitor, :key_method, :key_to_object_id, :object_id_to_key, :objects
73
+ attr_writer :garbage_cleaner
74
+
41
75
  def initialize(key_method = nil)
42
- @key_method = key_method
76
+ self.key_method = key_method
77
+ self.monitor = Monitor.new
78
+ clear
43
79
  end
44
80
 
45
81
  def [](object_or_object_id)
46
- ref = ref_for(object_or_object_id)
82
+ monitor.synchronize do
83
+ ref = ref_for(object_or_object_id)
47
84
 
48
- if ref&.weakref_alive?
49
- ref.__getobj__
85
+ object = begin
86
+ ref&.__getobj__
87
+ rescue WeakRef::RefError
88
+ # :nocov:
89
+ nil
90
+ # :nocov:
91
+ end
92
+
93
+ if ref&.weakref_alive?
94
+ object
95
+ end
50
96
  end
51
97
  end
52
98
 
@@ -61,121 +107,146 @@ module Foobara
61
107
  end
62
108
 
63
109
  def each
64
- objects.each_value do |ref|
65
- if ref.weakref_alive?
66
- yield ref.__getobj__
110
+ monitor.synchronize do
111
+ objects.each_value do |ref|
112
+ object = begin
113
+ ref.__getobj__
114
+ rescue WeakRef::RefError
115
+ nil
116
+ end
117
+
118
+ if ref.weakref_alive?
119
+ yield object
120
+ end
67
121
  end
68
122
  end
69
123
  end
70
124
 
71
- def objects
72
- @objects ||= {}
73
- end
74
-
75
125
  def size
76
126
  count
77
127
  end
78
128
 
79
129
  def empty?
80
- objects.empty? || objects.values.none?(&:weakref_alive?)
130
+ monitor.synchronize do
131
+ objects.empty? || objects.values.none?(&:weakref_alive?)
132
+ end
81
133
  end
82
134
 
83
- def key_to_object_id
84
- @key_to_object_id ||= {}
85
- end
135
+ def garbage_cleaner
136
+ @garbage_cleaner ||= begin
137
+ queue = Queue.new
86
138
 
87
- def object_id_to_key
88
- @object_id_to_key ||= {}
89
- end
139
+ gc = GarbageCleaner.new(self, queue)
90
140
 
91
- def garbage_cleaner
92
- @garbage_cleaner ||= if @key_method
93
- GarbageCleaner.new(objects, key_to_object_id, object_id_to_key)
94
- else
95
- GarbageCleaner.new(objects)
96
- end
141
+ ObjectSpace.define_finalizer gc do
142
+ # :nocov:
143
+ queue.close
144
+ # :nocov:
145
+ end
146
+
147
+ gc
148
+ end
97
149
  end
98
150
 
99
151
  def <<(object)
100
152
  object_id = object.object_id
101
153
 
102
- if include?(object)
103
- if @key_method
104
- key = object.send(@key_method)
105
- old_key = object_id_to_key[object_id]
154
+ monitor.synchronize do
155
+ existing_object = self[object_id]
156
+
157
+ if existing_object
158
+ if key_method
159
+ key = object.send(key_method)
160
+ old_key = object_id_to_key[object_id]
106
161
 
107
- if key != old_key
108
- key_to_object_id.delete(old_key)
162
+ if key != old_key
163
+ key_to_object_id.delete(old_key)
164
+
165
+ if key
166
+ key_to_object_id[key] = object_id
167
+ object_id_to_key[object_id] = key
168
+ else
169
+ object_id_to_key.delete(object_id)
170
+ end
171
+ end
172
+ end
173
+ else
174
+ garbage_cleaner.track(object)
175
+
176
+ if key_method
177
+ key = object.send(key_method)
109
178
 
110
179
  if key
180
+ existing_record_object_id = key_to_object_id[key]
181
+
182
+ if existing_record_object_id
183
+ # Sometimes this path is hit in the test suite and sometimes not, depending on
184
+ # non-deterministic behavior of the garbage collector
185
+ # :nocov:
186
+ delete(existing_record_object_id)
187
+ # :nocov:
188
+ end
189
+
111
190
  key_to_object_id[key] = object_id
112
191
  object_id_to_key[object_id] = key
113
- else
114
- object_id_to_key.delete(object_id)
115
192
  end
116
193
  end
117
- end
118
- else
119
- garbage_cleaner.track(object)
120
-
121
- objects[object_id] = WeakRef.new(object)
122
194
 
123
- if @key_method
124
- key = object.send(@key_method)
195
+ objects[object_id] = WeakRef.new(object)
125
196
 
126
- if key
127
- key_to_object_id[key] = object_id
128
- object_id_to_key[object_id] = key
129
- end
197
+ object
130
198
  end
131
-
132
- object
133
199
  end
134
200
  end
135
201
 
136
- def include?(object_or_object_id)
137
- ref_for(object_or_object_id)&.weakref_alive?
138
- end
202
+ def delete(object_or_object_id)
203
+ object_id = if object_or_object_id.is_a?(::Integer)
204
+ object_or_object_id
205
+ else
206
+ object_or_object_id.object_id
207
+ end
139
208
 
140
- def delete(object)
141
- if @key_method
142
- key = @object_id_to_key&.delete(object.object_id)
209
+ monitor.synchronize do
210
+ if key_method
211
+ key = object_id_to_key.delete(object_id)
143
212
 
144
- if key
145
- @key_to_object_id.delete(key)
213
+ if key
214
+ key_to_object_id.delete(key)
215
+ end
146
216
  end
147
- end
148
-
149
- objects.delete(object)
150
- end
151
217
 
152
- def include_key?(key)
153
- unless @key_method
154
- # :nocov:
155
- raise "Cannot check by key if there was no key_method given."
156
- # :nocov:
218
+ objects.delete(object_id)
157
219
  end
158
-
159
- objects[key_to_object_id[key]]&.weakref_alive?
160
220
  end
161
221
 
162
222
  def find_by_key(key)
163
- unless @key_method
164
- # :nocov:
165
- raise "Cannot find by key if there was no key_method given."
166
- # :nocov:
167
- end
223
+ monitor.synchronize do
224
+ unless key_method
225
+ # :nocov:
226
+ raise "Cannot find by key if there was no key_method given."
227
+ # :nocov:
228
+ end
168
229
 
169
- object = objects[key_to_object_id[key]]
230
+ object_id = key_to_object_id[key]
170
231
 
171
- if object&.weakref_alive?
172
- object.__getobj__
232
+ if object_id
233
+ self[object_id]
234
+ end
173
235
  end
174
236
  end
175
237
 
176
238
  def clear
177
- @garbage_cleaner&.deactivate
178
- @key_to_object_id = @object_id_to_key = @objects = @garbage_cleaner = nil
239
+ monitor.synchronize do
240
+ garbage_cleaner.deactivate
241
+
242
+ self.garbage_cleaner = nil
243
+ self.objects = {}
244
+
245
+ if key_method
246
+ self.key_to_object_id = {}
247
+ self.object_id_to_key = {}
248
+ end
249
+ end
179
250
  end
180
251
  end
181
252
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foobara
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.119
4
+ version: 0.0.120
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miles Georgi
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-05-08 00:00:00.000000000 Z
10
+ date: 2025-05-11 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: bigdecimal
@@ -396,6 +396,7 @@ files:
396
396
  - projects/state_machine/src/validations.rb
397
397
  - projects/type_declarations/lib/foobara/type_declarations.rb
398
398
  - projects/type_declarations/src/attributes.rb
399
+ - projects/type_declarations/src/attributes_transformers.rb
399
400
  - projects/type_declarations/src/attributes_transformers/only.rb
400
401
  - projects/type_declarations/src/attributes_transformers/reject.rb
401
402
  - projects/type_declarations/src/caster.rb