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 +4 -4
- data/CHANGELOG.md +6 -0
- data/projects/command_connectors/src/command_connector.rb +15 -4
- data/projects/command_connectors/src/command_registry.rb +9 -3
- data/projects/command_connectors/src/transformed_command.rb +151 -87
- data/projects/persistence/src/entity_base/transaction/concerns/entity_callback_handling.rb +2 -0
- data/projects/persistence/src/entity_base/transaction_table/concerns/queries.rb +2 -3
- data/projects/persistence/src/entity_base/transaction_table/concerns/record_tracking.rb +2 -2
- data/projects/persistence/src/entity_base/transaction_table.rb +38 -13
- data/projects/type_declarations/src/attributes_transformers.rb +11 -0
- data/projects/type_declarations/src/remove_sensitive_values_transformer.rb +25 -11
- data/projects/type_declarations/src/typed_transformer.rb +1 -1
- data/projects/value/src/processor.rb +6 -0
- data/projects/weak_object_set/src/weak_object_set.rb +164 -93
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df43aec79b46e3e71fd064c6482bdd5dd6e0af495987d6cea1cdf97311460e91
|
4
|
+
data.tar.gz: 800093b7a126bbca27fa300c4df50b568b329c762ad7a2c68925f4a3d5690090
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
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) ||
|
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
|
-
|
79
|
-
|
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
|
89
|
-
return @
|
70
|
+
def result_transformers
|
71
|
+
return @result_transformers if @considered_sensitive_value_remover
|
90
72
|
|
91
|
-
@
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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
|
115
|
-
|
116
|
-
|
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
|
-
|
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 =
|
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
|
-
@
|
200
|
+
@result_type = mutated_result_type
|
145
201
|
end
|
146
202
|
|
147
|
-
def
|
148
|
-
return @
|
203
|
+
def inputs_type
|
204
|
+
return @inputs_type if defined?(@inputs_type)
|
149
205
|
|
150
|
-
mutated_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
|
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
|
-
@
|
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
|
-
|
270
|
+
inputs_type&.types_depended_on || []
|
215
271
|
end
|
216
272
|
end
|
217
273
|
|
218
274
|
def result_types_depended_on
|
219
|
-
type_proc = -> {
|
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
|
-
|
290
|
+
types_proc = proc do
|
291
|
+
type = inputs_type_for_manifest
|
235
292
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
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
|
-
|
246
|
-
|
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
|
-
|
323
|
-
|
324
|
-
|
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:
|
330
|
-
|
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,
|
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,
|
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
|
-
|
25
|
-
|
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
|
-
|
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
|
-
|
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.
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
575
|
+
tracked(record)
|
551
576
|
|
552
577
|
record.is_persisted = record.is_loaded = true
|
553
578
|
record.is_created = false
|
@@ -5,20 +5,30 @@ module Foobara
|
|
5
5
|
class RemoveSensitiveValuesTransformer < TypedTransformer
|
6
6
|
def from(...)
|
7
7
|
super.tap do
|
8
|
-
|
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
|
-
|
11
|
-
|
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
|
-
|
15
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
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
|
-
|
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
|
11
|
-
|
12
|
-
|
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
|
-
|
16
|
+
start_cleanup_thread
|
17
|
+
end
|
16
18
|
|
17
|
-
|
18
|
-
|
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
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
82
|
+
monitor.synchronize do
|
83
|
+
ref = ref_for(object_or_object_id)
|
47
84
|
|
48
|
-
|
49
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
130
|
+
monitor.synchronize do
|
131
|
+
objects.empty? || objects.values.none?(&:weakref_alive?)
|
132
|
+
end
|
81
133
|
end
|
82
134
|
|
83
|
-
def
|
84
|
-
@
|
85
|
-
|
135
|
+
def garbage_cleaner
|
136
|
+
@garbage_cleaner ||= begin
|
137
|
+
queue = Queue.new
|
86
138
|
|
87
|
-
|
88
|
-
@object_id_to_key ||= {}
|
89
|
-
end
|
139
|
+
gc = GarbageCleaner.new(self, queue)
|
90
140
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
-
|
108
|
-
|
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
|
-
|
124
|
-
key = object.send(@key_method)
|
195
|
+
objects[object_id] = WeakRef.new(object)
|
125
196
|
|
126
|
-
|
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
|
137
|
-
|
138
|
-
|
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
|
-
|
141
|
-
|
142
|
-
|
209
|
+
monitor.synchronize do
|
210
|
+
if key_method
|
211
|
+
key = object_id_to_key.delete(object_id)
|
143
212
|
|
144
|
-
|
145
|
-
|
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
|
-
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
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
|
-
|
230
|
+
object_id = key_to_object_id[key]
|
170
231
|
|
171
|
-
|
172
|
-
|
232
|
+
if object_id
|
233
|
+
self[object_id]
|
234
|
+
end
|
173
235
|
end
|
174
236
|
end
|
175
237
|
|
176
238
|
def clear
|
177
|
-
|
178
|
-
|
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.
|
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-
|
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
|