foobara 0.0.115 → 0.0.116
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 +12 -0
- data/projects/command/src/command_pattern_implementation/concerns/transactions.rb +10 -69
- data/projects/command_connectors/src/authenticator.rb +4 -0
- data/projects/command_connectors/src/authenticator_selector.rb +8 -0
- data/projects/command_connectors/src/command_connector/request.rb +55 -10
- data/projects/command_connectors/src/command_connector/response.rb +4 -0
- data/projects/command_connectors/src/command_connector.rb +92 -97
- data/projects/command_connectors/src/serializers/entities_to_primary_keys_serializer.rb +8 -1
- data/projects/{command → command_connectors}/src/transformed_command.rb +16 -7
- data/projects/entity/src/sensitive_type_removers/entity.rb +15 -0
- data/projects/entity/src/sensitive_value_removers/entity.rb +14 -14
- data/projects/foobara/lib/foobara/all.rb +2 -1
- data/projects/model/src/concerns/types.rb +2 -1
- data/projects/model/src/extensions/builtin_types/model/validators/model_instance_is_valid.rb +2 -2
- data/projects/model/src/extensions/type_declarations/handlers/extend_model_type_declaration/model_class_desugarizer.rb +3 -1
- data/projects/model/src/model.rb +5 -3
- data/projects/monorepo/lib/foobara/monorepo/project.rb +4 -1
- data/projects/nested_transactionable/lib/foobara/nested_transactionable.rb +95 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe51e6ab90aafb99246fd2872f0faa452004f71b0d37a7e68a2fc7e8027feff7
|
4
|
+
data.tar.gz: d9027059c2a75b268950b661e5c3bb3fd1d633f52afbbe6d302c8ba3351908bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3beecfb43803f74a01d9f1d6e36c6b6f10c5abbae6dc3eaf9bc9a62afe25d3413313ed6f65262ba4e4c165e8545bbb506f045cafbd3d7673a37d4b87e9202661
|
7
|
+
data.tar.gz: 34554e412fc605f453392d2432f59311abe65498ec0db28e1c617c61b4c7fb1ac4e1bd7140f1ee8064482355a6a90e800cefea7b2ea8f35e23ad00eed59cf674
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
# [0.0.116] - 2025-05-03
|
2
|
+
|
3
|
+
- Add automatic transaction support to requests, cover in/out mutators/transformers
|
4
|
+
- This makes it so that authenticators and allowed rules can more cleanly be
|
5
|
+
expressed when they have the same entity bases needed by the transformed command
|
6
|
+
- Move #authenticated_user from TransformedCommand to Request
|
7
|
+
- Add a Request#authenticated_credential
|
8
|
+
- Provide a way to skip validations for models
|
9
|
+
- A type without sensitive types derived from an entity type will now be registered as a
|
10
|
+
detached entity
|
11
|
+
- Fix model type re-registering bug
|
12
|
+
|
1
13
|
# [0.0.115] - 2025-05-01
|
2
14
|
|
3
15
|
- Make sure Authenticator#authenticate hits #applicable?
|
@@ -3,81 +3,22 @@ module Foobara
|
|
3
3
|
module Concerns
|
4
4
|
module Transactions
|
5
5
|
include Concern
|
6
|
-
|
7
|
-
def transactions
|
8
|
-
@transactions ||= []
|
9
|
-
end
|
10
|
-
|
11
|
-
def opened_transactions
|
12
|
-
@opened_transactions ||= []
|
13
|
-
end
|
14
|
-
|
15
|
-
def auto_detect_current_transactions
|
16
|
-
bases = relevant_entity_classes.map(&:entity_base).uniq
|
17
|
-
|
18
|
-
bases.each do |base|
|
19
|
-
tx = base.current_transaction
|
20
|
-
transactions << tx if tx
|
21
|
-
end
|
22
|
-
end
|
6
|
+
include NestedTransactionable
|
23
7
|
|
24
8
|
def relevant_entity_classes
|
25
|
-
@relevant_entity_classes
|
26
|
-
entity_classes = if inputs_type
|
27
|
-
Entity.construct_associations(
|
28
|
-
inputs_type
|
29
|
-
).values.uniq.map(&:target_class)
|
30
|
-
else
|
31
|
-
[]
|
32
|
-
end
|
33
|
-
|
34
|
-
if result_type
|
35
|
-
entity_classes += Entity.construct_associations(
|
36
|
-
result_type
|
37
|
-
).values.uniq.map(&:target_class)
|
38
|
-
|
39
|
-
if result_type.extends?(BuiltinTypes[:entity])
|
40
|
-
entity_classes << result_type.target_class
|
41
|
-
end
|
42
|
-
end
|
9
|
+
return @relevant_entity_classes if defined?(@relevant_entity_classes)
|
43
10
|
|
44
|
-
|
45
|
-
|
46
|
-
|
11
|
+
entity_classes = if inputs_type
|
12
|
+
relevant_entity_classes_for_type(inputs_type)
|
13
|
+
else
|
14
|
+
[]
|
15
|
+
end
|
47
16
|
|
48
|
-
|
17
|
+
if result_type
|
18
|
+
entity_classes += relevant_entity_classes_for_type(result_type)
|
49
19
|
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def open_transaction
|
53
|
-
auto_detect_current_transactions
|
54
|
-
|
55
|
-
bases_not_needing_transaction = transactions.map(&:entity_base)
|
56
|
-
|
57
|
-
bases_needing_transaction = relevant_entity_classes.map(&:entity_base).uniq - bases_not_needing_transaction
|
58
|
-
|
59
|
-
bases_needing_transaction.each do |entity_base|
|
60
|
-
transaction = entity_base.transaction
|
61
|
-
transaction.open!
|
62
|
-
opened_transactions << transaction
|
63
|
-
transactions << transaction
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def rollback_transaction
|
68
|
-
opened_transactions.reverse.each do |transaction|
|
69
|
-
if transaction.currently_open?
|
70
|
-
# Hard to test this because halting and other exceptions rollback the transactions via
|
71
|
-
# block form but to be safe keeping this
|
72
|
-
# :nocov:
|
73
|
-
transaction.rollback!
|
74
|
-
# :nocov:
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
20
|
|
79
|
-
|
80
|
-
opened_transactions.reverse.each(&:commit!)
|
21
|
+
@relevant_entity_classes = [*entity_classes, *self.class.depends_on_entities].uniq
|
81
22
|
end
|
82
23
|
end
|
83
24
|
end
|
@@ -24,6 +24,14 @@ module Foobara
|
|
24
24
|
def authenticate(request)
|
25
25
|
selector.process_value!(request)
|
26
26
|
end
|
27
|
+
|
28
|
+
def relevant_entity_classes(request)
|
29
|
+
outcome = selector.processor_for(request)
|
30
|
+
|
31
|
+
if outcome.success?
|
32
|
+
outcome.result&.relevant_entity_classes(request)
|
33
|
+
end
|
34
|
+
end
|
27
35
|
end
|
28
36
|
end
|
29
37
|
end
|
@@ -2,9 +2,11 @@ module Foobara
|
|
2
2
|
class CommandConnector
|
3
3
|
class Request
|
4
4
|
include TruncatedInspect
|
5
|
+
include NestedTransactionable
|
5
6
|
|
6
7
|
# TODO: this feels like a smell of some sort...
|
7
8
|
attr_accessor :command,
|
9
|
+
:command_class,
|
8
10
|
:error,
|
9
11
|
:command_connector,
|
10
12
|
# Why aren't there serializers on the response?
|
@@ -12,9 +14,9 @@ module Foobara
|
|
12
14
|
:inputs,
|
13
15
|
:full_command_name,
|
14
16
|
:action,
|
15
|
-
:response
|
16
|
-
|
17
|
-
|
17
|
+
:response,
|
18
|
+
:authenticated_user,
|
19
|
+
:authenticated_credential
|
18
20
|
|
19
21
|
def initialize(**opts)
|
20
22
|
valid_keys = %i[inputs full_command_name action serializers]
|
@@ -33,8 +35,8 @@ module Foobara
|
|
33
35
|
self.serializers = Util.array(opts[:serializers]) if opts.key?(:serializers)
|
34
36
|
end
|
35
37
|
|
36
|
-
def
|
37
|
-
|
38
|
+
def mutate_request
|
39
|
+
return if error
|
38
40
|
|
39
41
|
# TODO: we really need to revisit these interfaces. Something is wrong.
|
40
42
|
if command_class.respond_to?(:mutate_request)
|
@@ -42,6 +44,25 @@ module Foobara
|
|
42
44
|
end
|
43
45
|
end
|
44
46
|
|
47
|
+
def authenticate
|
48
|
+
return if error
|
49
|
+
return unless command_class.respond_to?(:requires_authentication) && command_class.requires_authentication
|
50
|
+
|
51
|
+
authenticated_user, authenticated_credential = authenticator.authenticate(self)
|
52
|
+
|
53
|
+
# TODO: why are these on the command instead of the request??
|
54
|
+
self.authenticated_user = authenticated_user
|
55
|
+
self.authenticated_credential = authenticated_credential
|
56
|
+
|
57
|
+
unless authenticated_user
|
58
|
+
self.error = CommandConnector::UnauthenticatedError.new
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def authenticator
|
63
|
+
command_class.authenticator
|
64
|
+
end
|
65
|
+
|
45
66
|
def serializer
|
46
67
|
return @serializer if defined?(@serializer)
|
47
68
|
|
@@ -77,12 +98,10 @@ module Foobara
|
|
77
98
|
end
|
78
99
|
|
79
100
|
def outcome
|
80
|
-
|
81
|
-
|
82
|
-
if outcome
|
83
|
-
outcome
|
84
|
-
elsif error
|
101
|
+
if error
|
85
102
|
Outcome.error(error)
|
103
|
+
else
|
104
|
+
command&.outcome
|
86
105
|
end
|
87
106
|
end
|
88
107
|
|
@@ -94,8 +113,34 @@ module Foobara
|
|
94
113
|
outcome.error_collection
|
95
114
|
end
|
96
115
|
|
116
|
+
def relevant_entity_classes
|
117
|
+
if command_class.is_a?(::Class) && command_class < TransformedCommand
|
118
|
+
entity_classes = authenticator&.relevant_entity_classes(self)
|
119
|
+
[*entity_classes, *relevant_entity_classes_from_inputs_transformer]
|
120
|
+
end || []
|
121
|
+
end
|
122
|
+
|
97
123
|
private
|
98
124
|
|
125
|
+
def relevant_entity_classes_from_inputs_transformer(
|
126
|
+
object = [*command_class.inputs_transformer, *command_class.result_transformer]
|
127
|
+
)
|
128
|
+
case object
|
129
|
+
when TypeDeclarations::TypedTransformer
|
130
|
+
relevant_entity_classes_from_inputs_transformer([*object.from_type, *object.to_type])
|
131
|
+
when Types::Type
|
132
|
+
relevant_entity_classes_for_type(object)
|
133
|
+
when ::Array
|
134
|
+
object.map do |o|
|
135
|
+
relevant_entity_classes_from_inputs_transformer(o)
|
136
|
+
end.flatten
|
137
|
+
when Value::Processor::Pipeline
|
138
|
+
relevant_entity_classes_from_inputs_transformer(object.processors)
|
139
|
+
else
|
140
|
+
[]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
99
144
|
def objects_to_serializers(objects)
|
100
145
|
objects.map do |object|
|
101
146
|
case object
|
@@ -154,15 +154,11 @@ module Foobara
|
|
154
154
|
command_registry.foobara_lookup_command(name)
|
155
155
|
end
|
156
156
|
|
157
|
-
|
158
|
-
# in order to make this easier to extend and manage.
|
159
|
-
def request_to_command(request)
|
157
|
+
def request_to_command_class(request)
|
160
158
|
action = request.action
|
161
|
-
inputs = nil
|
162
159
|
full_command_name = request.full_command_name
|
163
160
|
|
164
|
-
|
165
|
-
when "run"
|
161
|
+
if action == "run"
|
166
162
|
transformed_command_class = transformed_command_from_name(full_command_name)
|
167
163
|
|
168
164
|
unless transformed_command_class
|
@@ -171,9 +167,36 @@ module Foobara
|
|
171
167
|
# :nocov:
|
172
168
|
end
|
173
169
|
|
174
|
-
|
170
|
+
transformed_command_class
|
171
|
+
else
|
172
|
+
action = case action
|
173
|
+
when "describe_type", "manifest", "describe_command"
|
174
|
+
"describe"
|
175
|
+
when "describe", "ping", "query_git_commit_info", "help"
|
176
|
+
action
|
177
|
+
when "list"
|
178
|
+
"list_commands"
|
179
|
+
else
|
180
|
+
# :nocov:
|
181
|
+
raise InvalidContextError.new(message: "Not sure what to do with #{action}")
|
182
|
+
# :nocov:
|
183
|
+
end
|
184
|
+
|
185
|
+
command_name = Util.classify(action)
|
186
|
+
command_class = find_builtin_command_class(command_name)
|
187
|
+
full_command_name = command_class.full_command_name
|
188
|
+
|
189
|
+
transformed_command_from_name(full_command_name) || transform_command_class(command_class)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def request_to_command_inputs(request)
|
194
|
+
action = request.action
|
195
|
+
full_command_name = request.full_command_name
|
175
196
|
|
176
|
-
|
197
|
+
case action
|
198
|
+
when "run"
|
199
|
+
request.inputs
|
177
200
|
when "describe"
|
178
201
|
manifestable = transformed_command_from_name(full_command_name) || type_from_name(full_command_name)
|
179
202
|
|
@@ -185,13 +208,7 @@ module Foobara
|
|
185
208
|
# :nocov:
|
186
209
|
end
|
187
210
|
|
188
|
-
|
189
|
-
full_command_name = command_class.full_command_name
|
190
|
-
|
191
|
-
inputs = request.inputs.merge(manifestable:, request:)
|
192
|
-
|
193
|
-
transformed_command_class = transformed_command_from_name(full_command_name) ||
|
194
|
-
transform_command_class(command_class)
|
211
|
+
request.inputs.merge(manifestable:, request:)
|
195
212
|
when "describe_command"
|
196
213
|
transformed_command_class = transformed_command_from_name(full_command_name)
|
197
214
|
|
@@ -201,12 +218,7 @@ module Foobara
|
|
201
218
|
# :nocov:
|
202
219
|
end
|
203
220
|
|
204
|
-
|
205
|
-
full_command_name = command_class.full_command_name
|
206
|
-
|
207
|
-
inputs = request.inputs.merge(manifestable: transformed_command_class, request:)
|
208
|
-
transformed_command_class = transformed_command_from_name(full_command_name) ||
|
209
|
-
transform_command_class(command_class)
|
221
|
+
request.inputs.merge(manifestable: transformed_command_class, request:)
|
210
222
|
when "describe_type"
|
211
223
|
type = type_from_name(full_command_name)
|
212
224
|
|
@@ -216,59 +228,30 @@ module Foobara
|
|
216
228
|
# :nocov:
|
217
229
|
end
|
218
230
|
|
219
|
-
|
220
|
-
full_command_name = command_class.full_command_name
|
221
|
-
|
222
|
-
inputs = request.inputs.merge(manifestable: type, request:)
|
223
|
-
transformed_command_class = transformed_command_from_name(full_command_name) ||
|
224
|
-
transform_command_class(command_class)
|
231
|
+
request.inputs.merge(manifestable: type, request:)
|
225
232
|
when "manifest"
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
inputs = request.inputs.merge(manifestable: self, request:)
|
230
|
-
transformed_command_class = transformed_command_from_name(full_command_name) ||
|
231
|
-
transform_command_class(command_class)
|
232
|
-
when "ping"
|
233
|
-
command_class = find_builtin_command_class("Ping")
|
234
|
-
full_command_name = command_class.full_command_name
|
235
|
-
|
236
|
-
transformed_command_class = transformed_command_from_name(full_command_name) ||
|
237
|
-
transform_command_class(command_class)
|
238
|
-
when "query_git_commit_info"
|
239
|
-
# TODO: this feels out of control... should just accomplish this through run I think instead. Same with ping.
|
240
|
-
command_class = find_builtin_command_class("QueryGitCommitInfo")
|
241
|
-
full_command_name = command_class.full_command_name
|
242
|
-
|
243
|
-
transformed_command_class = transformed_command_from_name(full_command_name) ||
|
244
|
-
transform_command_class(command_class)
|
233
|
+
request.inputs.merge(manifestable: self, request:)
|
234
|
+
when "ping", "query_git_commit_info"
|
235
|
+
nil
|
245
236
|
when "help"
|
246
|
-
|
247
|
-
full_command_name = command_class.full_command_name
|
248
|
-
|
249
|
-
inputs = { request: }
|
250
|
-
transformed_command_class = transformed_command_from_name(full_command_name) ||
|
251
|
-
transform_command_class(command_class)
|
237
|
+
{ request: }
|
252
238
|
when "list"
|
253
|
-
|
254
|
-
|
255
|
-
full_command_name = command_class.full_command_name
|
256
|
-
|
257
|
-
request.command_class = command_class
|
258
|
-
inputs = request.inputs.merge(request:)
|
259
|
-
|
260
|
-
transformed_command_class = transformed_command_from_name(full_command_name) ||
|
261
|
-
transform_command_class(command_class)
|
239
|
+
request.inputs.merge(request:)
|
262
240
|
else
|
263
241
|
# :nocov:
|
264
242
|
raise InvalidContextError.new(message: "Not sure what to do with #{action}")
|
265
243
|
# :nocov:
|
266
244
|
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def request_to_command_instance(request)
|
248
|
+
command_class = request.command_class
|
249
|
+
inputs = request.inputs
|
267
250
|
|
268
251
|
if inputs && !inputs.empty?
|
269
|
-
|
252
|
+
command_class.new(inputs)
|
270
253
|
else
|
271
|
-
|
254
|
+
command_class.new
|
272
255
|
end
|
273
256
|
end
|
274
257
|
|
@@ -305,6 +288,7 @@ module Foobara
|
|
305
288
|
command = response.command
|
306
289
|
|
307
290
|
if command.respond_to?(:serialize_result)
|
291
|
+
# TODO: Get serialization off of the command instance!!
|
308
292
|
response.body = command.serialize_result(response.body)
|
309
293
|
end
|
310
294
|
end
|
@@ -383,18 +367,40 @@ module Foobara
|
|
383
367
|
end
|
384
368
|
|
385
369
|
def run_request(request)
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
370
|
+
command_class = determine_command_class(request)
|
371
|
+
request.command_class = command_class
|
372
|
+
|
373
|
+
return build_response(request) unless command_class
|
374
|
+
|
375
|
+
begin
|
376
|
+
request.open_transaction
|
377
|
+
request.use_transaction do
|
378
|
+
request.authenticate
|
379
|
+
request.mutate_request
|
380
|
+
|
381
|
+
inputs = request_to_command_inputs(request)
|
382
|
+
request.inputs = inputs
|
383
|
+
command = build_command_instance(request)
|
384
|
+
request.command = command
|
385
|
+
|
386
|
+
unless request.error
|
387
|
+
if command
|
388
|
+
run_command(request)
|
389
|
+
# :nocov:
|
390
|
+
else
|
391
|
+
raise "No command returned from #request_to_command"
|
392
|
+
# :nocov:
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
ensure
|
397
|
+
request.use_transaction do
|
398
|
+
if (request.response || request).outcome&.success?
|
399
|
+
request.commit_transaction_if_open
|
400
|
+
else
|
401
|
+
request.rollback_transaction
|
402
|
+
end
|
403
|
+
end
|
398
404
|
end
|
399
405
|
|
400
406
|
build_response(request)
|
@@ -414,33 +420,22 @@ module Foobara
|
|
414
420
|
end
|
415
421
|
end
|
416
422
|
|
417
|
-
def
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
# TODO: why are these on the command instead of the request??
|
425
|
-
request_command.authenticated_user = authenticated_user
|
426
|
-
request_command.authenticated_credential = authenticated_credential
|
427
|
-
|
428
|
-
unless authenticated_user
|
429
|
-
request_command.outcome = Outcome.error(CommandConnector::UnauthenticatedError.new)
|
430
|
-
|
431
|
-
command.state_machine.error!
|
432
|
-
command.halt!
|
433
|
-
end
|
423
|
+
def build_command_instance(request)
|
424
|
+
command = request_to_command_instance(request)
|
425
|
+
request.command = command
|
426
|
+
if command.is_a?(TransformedCommand)
|
427
|
+
# This allows the command to access the authenticated_user
|
428
|
+
command.request = request
|
434
429
|
end
|
430
|
+
|
431
|
+
command
|
435
432
|
end
|
436
433
|
|
437
|
-
def
|
434
|
+
def determine_command_class(request)
|
438
435
|
unless request.error
|
439
|
-
|
440
|
-
request.
|
436
|
+
command_class = request_to_command_class(request)
|
437
|
+
request.command_class = command_class
|
441
438
|
end
|
442
|
-
|
443
|
-
command
|
444
439
|
end
|
445
440
|
|
446
441
|
def build_response(request)
|
@@ -11,7 +11,7 @@ module Foobara
|
|
11
11
|
# Is there maybe prior art for this in the associations stuff?
|
12
12
|
object.primary_key
|
13
13
|
when DetachedEntity
|
14
|
-
if
|
14
|
+
if detached_to_primary_key?
|
15
15
|
object.primary_key
|
16
16
|
else
|
17
17
|
object.attributes
|
@@ -28,6 +28,13 @@ module Foobara
|
|
28
28
|
object
|
29
29
|
end
|
30
30
|
end
|
31
|
+
|
32
|
+
def detached_to_primary_key?
|
33
|
+
return true unless declaration_data.is_a?(::Hash)
|
34
|
+
return true unless declaration_data.key?(:detached_to_primary_key)
|
35
|
+
|
36
|
+
declaration_data[:detached_to_primary_key]
|
37
|
+
end
|
31
38
|
end
|
32
39
|
end
|
33
40
|
end
|
@@ -95,7 +95,9 @@ module Foobara
|
|
95
95
|
|
96
96
|
command_class.inputs_type
|
97
97
|
else
|
98
|
-
inputs_transformer.
|
98
|
+
if inputs_transformer.is_a?(TypeDeclarations::TypedTransformer)
|
99
|
+
inputs_transformer.from_type
|
100
|
+
end || command_class.inputs_type
|
99
101
|
end
|
100
102
|
else
|
101
103
|
command_class.inputs_type
|
@@ -479,8 +481,7 @@ module Foobara
|
|
479
481
|
end
|
480
482
|
end
|
481
483
|
|
482
|
-
attr_accessor :command, :untransformed_inputs, :transformed_inputs, :outcome, :
|
483
|
-
:authenticated_credential
|
484
|
+
attr_accessor :command, :untransformed_inputs, :transformed_inputs, :outcome, :request
|
484
485
|
|
485
486
|
def initialize(untransformed_inputs = {})
|
486
487
|
self.untransformed_inputs = untransformed_inputs || {}
|
@@ -499,7 +500,6 @@ module Foobara
|
|
499
500
|
:errors_transformers,
|
500
501
|
:pre_commit_transformers,
|
501
502
|
:serializers,
|
502
|
-
:requires_authentication,
|
503
503
|
:allowed_rule,
|
504
504
|
:authenticator,
|
505
505
|
to: :class
|
@@ -508,14 +508,19 @@ module Foobara
|
|
508
508
|
apply_allowed_rule
|
509
509
|
apply_pre_commit_transformers
|
510
510
|
run_command
|
511
|
-
#
|
511
|
+
# this gives us primary keys
|
512
|
+
flush_transactions
|
512
513
|
transform_outcome
|
513
514
|
|
514
515
|
outcome
|
515
516
|
end
|
516
517
|
|
517
|
-
def
|
518
|
-
|
518
|
+
def authenticated_user
|
519
|
+
request.authenticated_user
|
520
|
+
end
|
521
|
+
|
522
|
+
def authenticated_credential
|
523
|
+
request.authenticated_credential
|
519
524
|
end
|
520
525
|
|
521
526
|
def transform_inputs
|
@@ -669,6 +674,10 @@ module Foobara
|
|
669
674
|
outcome.errors
|
670
675
|
end
|
671
676
|
|
677
|
+
def flush_transactions
|
678
|
+
request.opened_transactions&.reverse&.each(&:flush!)
|
679
|
+
end
|
680
|
+
|
672
681
|
def transform_outcome
|
673
682
|
if outcome.success?
|
674
683
|
# can we do this while still in the transaction of the command???
|
@@ -2,6 +2,21 @@ module Foobara
|
|
2
2
|
class Entity < DetachedEntity
|
3
3
|
module SensitiveTypeRemovers
|
4
4
|
class Entity < DetachedEntity::SensitiveTypeRemovers::DetachedEntity
|
5
|
+
def transform(strict_type_declaration)
|
6
|
+
new_type_declaration = super
|
7
|
+
|
8
|
+
if strict_type_declaration != new_type_declaration
|
9
|
+
if new_type_declaration[:type] == :entity
|
10
|
+
new_type_declaration[:type] = :detached_entity
|
11
|
+
|
12
|
+
if new_type_declaration[:model_base_class] == "Foobara::Entity"
|
13
|
+
new_type_declaration[:model_base_class] = "Foobara::DetachedEntity"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
new_type_declaration
|
19
|
+
end
|
5
20
|
end
|
6
21
|
end
|
7
22
|
end
|
@@ -4,22 +4,15 @@ module Foobara
|
|
4
4
|
class Entity < DetachedEntity::SensitiveValueRemovers::DetachedEntity
|
5
5
|
def transform(record)
|
6
6
|
if record.loaded? || record.created?
|
7
|
-
|
8
|
-
|
9
|
-
sanitized_record.is_loaded = record.loaded?
|
10
|
-
sanitized_record.is_persisted = record.persisted?
|
11
|
-
|
12
|
-
sanitized_record
|
7
|
+
super
|
13
8
|
elsif record.persisted?
|
14
9
|
# We will assume that we do not need to clean up the primary key itself as
|
15
10
|
# we will assume we don't allow sensitive primary keys for now.
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
sanitized_record
|
11
|
+
# We use .new because the target_class should be a detached entity
|
12
|
+
to_type.target_class.new(
|
13
|
+
{ record.class.primary_key_attribute => record.primary_key },
|
14
|
+
{ mutable: false, skip_validations: true }
|
15
|
+
)
|
23
16
|
else
|
24
17
|
# :nocov:
|
25
18
|
raise "Not sure what to do with a record that isn't loaded, created, or persisted"
|
@@ -28,7 +21,14 @@ module Foobara
|
|
28
21
|
end
|
29
22
|
|
30
23
|
def build_method
|
31
|
-
:
|
24
|
+
if to_type.extends?(:entity)
|
25
|
+
# TODO: test this code path
|
26
|
+
# :nocov:
|
27
|
+
:build
|
28
|
+
# :nocov:
|
29
|
+
else
|
30
|
+
:new
|
31
|
+
end
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
@@ -30,9 +30,10 @@ module Foobara
|
|
30
30
|
"detached_entity",
|
31
31
|
"entity",
|
32
32
|
"model_attribute_helpers",
|
33
|
+
"nested_transactionable",
|
33
34
|
"command",
|
34
35
|
"domain_mapper",
|
35
|
-
"persistence",
|
36
|
+
"persistence", # Feels like this would be loaded before command?
|
36
37
|
"in_memory_crud_driver_minimal",
|
37
38
|
"in_memory_crud_driver",
|
38
39
|
"manifest"
|
@@ -64,8 +64,9 @@ module Foobara
|
|
64
64
|
|
65
65
|
if model_type
|
66
66
|
unless Foobara::TypeDeclarations.declarations_equal?(declaration, model_type.declaration_data)
|
67
|
+
type_domain = domain
|
67
68
|
self.model_type = nil
|
68
|
-
|
69
|
+
type_domain.foobara_type_from_declaration(declaration)
|
69
70
|
end
|
70
71
|
else
|
71
72
|
domain.foobara_type_from_declaration(declaration)
|
@@ -17,6 +17,8 @@ module Foobara
|
|
17
17
|
|
18
18
|
# TODO: consider splitting this up into multiple desugarizers
|
19
19
|
def desugarize(strictish_type_declaration)
|
20
|
+
strictish_type_declaration = strictish_type_declaration.dup
|
21
|
+
|
20
22
|
if strictish_type_declaration.key?(:model_module)
|
21
23
|
model_module = strictish_type_declaration[:model_module]
|
22
24
|
|
@@ -47,7 +49,7 @@ module Foobara
|
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
50
|
-
strictish_type_declaration[:model_base_class]
|
52
|
+
strictish_type_declaration[:model_base_class] ||= model_class.superclass.name
|
51
53
|
|
52
54
|
model_class.name
|
53
55
|
elsif klass.is_a?(::String)
|
data/projects/model/src/model.rb
CHANGED
@@ -169,10 +169,10 @@ module Foobara
|
|
169
169
|
|
170
170
|
abstract
|
171
171
|
|
172
|
-
attr_accessor :mutable
|
172
|
+
attr_accessor :mutable, :skip_validations
|
173
173
|
|
174
174
|
def initialize(attributes = nil, options = {})
|
175
|
-
allowed_options = %i[validate mutable ignore_unexpected_attributes]
|
175
|
+
allowed_options = %i[validate mutable ignore_unexpected_attributes skip_validations]
|
176
176
|
invalid_options = options.keys - allowed_options
|
177
177
|
|
178
178
|
unless invalid_options.empty?
|
@@ -181,6 +181,8 @@ module Foobara
|
|
181
181
|
# :nocov:
|
182
182
|
end
|
183
183
|
|
184
|
+
self.skip_validations = options[:skip_validations]
|
185
|
+
|
184
186
|
if options[:ignore_unexpected_attributes]
|
185
187
|
Thread.with_inheritable_thread_local_var(:foobara_ignore_unexpected_attributes, true) do
|
186
188
|
initialize(attributes, options.except(:ignore_unexpected_attributes))
|
@@ -226,7 +228,7 @@ module Foobara
|
|
226
228
|
mutable
|
227
229
|
end
|
228
230
|
|
229
|
-
validate! if validate
|
231
|
+
validate! if validate # TODO: test this code path
|
230
232
|
end
|
231
233
|
|
232
234
|
foobara_delegate :model_name, :valid_attribute_name?, :validate_attribute_name!, to: :class
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Foobara
|
2
|
+
module NestedTransactionable
|
3
|
+
include Concern
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def relevant_entity_classes_for_type(type)
|
7
|
+
entity_classes = []
|
8
|
+
entity_classes += Entity.construct_associations(type).values.map(&:target_class)
|
9
|
+
|
10
|
+
if type.extends?(BuiltinTypes[:entity])
|
11
|
+
entity_classes << type.target_class
|
12
|
+
end
|
13
|
+
|
14
|
+
entity_classes.uniq.each do |entity_class|
|
15
|
+
entity_classes += entity_class.deep_associations.values.map(&:target_class)
|
16
|
+
end
|
17
|
+
|
18
|
+
entity_classes.uniq
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def relevant_entity_classes_for_type(type)
|
23
|
+
NestedTransactionable.relevant_entity_classes_for_type(type)
|
24
|
+
end
|
25
|
+
|
26
|
+
def relevant_entity_classes
|
27
|
+
# :nocov:
|
28
|
+
raise "subclass responsibility"
|
29
|
+
# :nocov:
|
30
|
+
end
|
31
|
+
|
32
|
+
def transactions
|
33
|
+
@transactions ||= []
|
34
|
+
end
|
35
|
+
|
36
|
+
def opened_transactions
|
37
|
+
@opened_transactions ||= []
|
38
|
+
end
|
39
|
+
|
40
|
+
def auto_detect_current_transactions
|
41
|
+
classes = relevant_entity_classes
|
42
|
+
return if classes.nil? || classes.empty?
|
43
|
+
|
44
|
+
bases = classes.map(&:entity_base).uniq
|
45
|
+
|
46
|
+
bases.each do |base|
|
47
|
+
tx = base.current_transaction
|
48
|
+
transactions << tx if tx
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def open_transaction
|
53
|
+
auto_detect_current_transactions
|
54
|
+
|
55
|
+
bases_not_needing_transaction = transactions.map(&:entity_base)
|
56
|
+
|
57
|
+
bases_needing_transaction = relevant_entity_classes.map(&:entity_base).uniq - bases_not_needing_transaction
|
58
|
+
|
59
|
+
bases_needing_transaction.each do |entity_base|
|
60
|
+
transaction = entity_base.transaction
|
61
|
+
transaction.open!
|
62
|
+
opened_transactions << transaction
|
63
|
+
transactions << transaction
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def rollback_transaction
|
68
|
+
opened_transactions.reverse.each do |transaction|
|
69
|
+
if transaction.currently_open?
|
70
|
+
# Hard to test this because halting and other exceptions rollback the transactions via
|
71
|
+
# block form but to be safe keeping this
|
72
|
+
# :nocov:
|
73
|
+
transaction.rollback!
|
74
|
+
# :nocov:
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def commit_transaction
|
80
|
+
opened_transactions.reverse.each(&:commit!)
|
81
|
+
end
|
82
|
+
|
83
|
+
def commit_transaction_if_open
|
84
|
+
opened_transactions.reverse.each do |tx|
|
85
|
+
if tx.currently_open?
|
86
|
+
tx.commit!
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def use_transaction(&)
|
92
|
+
Persistence::EntityBase.using_transactions(transactions, &)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
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.116
|
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-03 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: bigdecimal
|
@@ -179,7 +179,6 @@ files:
|
|
179
179
|
- projects/command/src/command_pattern_implementation/concerns/subcommands.rb
|
180
180
|
- projects/command/src/command_pattern_implementation/concerns/transactions.rb
|
181
181
|
- projects/command/src/state_machine.rb
|
182
|
-
- projects/command/src/transformed_command.rb
|
183
182
|
- projects/command_connectors/lib/foobara/command_connectors.rb
|
184
183
|
- projects/command_connectors/src/authenticator.rb
|
185
184
|
- projects/command_connectors/src/authenticator_selector.rb
|
@@ -216,6 +215,7 @@ files:
|
|
216
215
|
- projects/command_connectors/src/serializers/record_store_serializer.rb
|
217
216
|
- projects/command_connectors/src/serializers/success_serializer.rb
|
218
217
|
- projects/command_connectors/src/serializers/yaml_serializer.rb
|
218
|
+
- projects/command_connectors/src/transformed_command.rb
|
219
219
|
- projects/command_connectors/src/transformers/auth_errors_transformer.rb
|
220
220
|
- projects/command_connectors/src/transformers/load_aggregates_pre_commit_transformer.rb
|
221
221
|
- projects/command_connectors/src/transformers/load_delegated_attributes_entities_pre_commit_transformer.rb
|
@@ -369,6 +369,7 @@ files:
|
|
369
369
|
- projects/namespace/src/prefixless_registry.rb
|
370
370
|
- projects/namespace/src/scoped.rb
|
371
371
|
- projects/namespace/src/unambiguous_registry.rb
|
372
|
+
- projects/nested_transactionable/lib/foobara/nested_transactionable.rb
|
372
373
|
- projects/persistence/lib/foobara/persistence.rb
|
373
374
|
- projects/persistence/src/entity_attributes_crud_driver.rb
|
374
375
|
- projects/persistence/src/entity_base.rb
|
@@ -492,6 +493,7 @@ require_paths:
|
|
492
493
|
- "./projects/model_attribute_helpers/lib"
|
493
494
|
- "./projects/monorepo/lib"
|
494
495
|
- "./projects/namespace/lib"
|
496
|
+
- "./projects/nested_transactionable/lib"
|
495
497
|
- "./projects/persistence/lib"
|
496
498
|
- "./projects/state_machine/lib"
|
497
499
|
- "./projects/type_declarations/lib"
|