foobara 0.0.130 → 0.0.132
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 +15 -0
- data/projects/command_connectors/src/serializers/entities_to_primary_keys_serializer.rb +2 -2
- data/projects/command_connectors/src/transformed_command.rb +10 -11
- data/projects/command_connectors/src/transformers/entity_to_primary_key_inputs_transformer.rb +88 -0
- data/projects/detached_entity/src/concerns/associations.rb +20 -10
- data/projects/in_memory_crud_driver_minimal/src/in_memory_minimal.rb +1 -1
- data/projects/model/src/extensions/builtin_types/model/validators/model_instance_is_valid.rb +1 -1
- data/projects/persistence/src/entity_attributes_crud_driver.rb +60 -27
- data/projects/persistence/src/entity_base/transaction/concerns/state_transitions.rb +47 -5
- data/projects/persistence/src/entity_base/transaction/state_machine.rb +4 -1
- data/projects/persistence/src/entity_base/transaction.rb +5 -1
- data/projects/persistence/src/entity_base/transaction_table/concerns/record_tracking.rb +16 -1
- data/projects/persistence/src/entity_base/transaction_table.rb +8 -0
- data/projects/persistence/src/entity_base.rb +52 -7
- data/projects/type_declarations/src/typed_transformer.rb +4 -0
- data/projects/types/src/type.rb +21 -0
- data/projects/weak_object_set/src/weak_object_set.rb +48 -14
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d4c8a877056c159be9d156cde81dececc246a1dda21e99c39d567603c4b99aca
|
4
|
+
data.tar.gz: 71f5c66811f399c7311b6a13fcd9562b7b11f1e6b3eb34e6cd0298cb6e2e548a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 97210eac20ace35283b2fdffe8ac36d924c3ac19336a34b914357a7c0cc7ab0a3097a867d78032898cbf8dc21ef57c6c605100919f96cfbbad15a3955cde4d2f
|
7
|
+
data.tar.gz: fb717c26f27f5fb6be91d77ddce128de252198523d4cc535d315fbe1029981951242a845b37460cee4cc3b6b0862a6220463750499e400d4a8d8426c62e776d4
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
# [0.0.132] - 2025-06-19
|
2
|
+
|
3
|
+
- Add/improve the description for primary key types in EntityToPrimaryKeyInputsTransformer
|
4
|
+
- Support model and tuple types in .has_associations?
|
5
|
+
- Move EntityToPrimaryKeyInputsTransformer over from foobara-agent gem
|
6
|
+
- Make sure EntitiesToPrimaryKeysSerializer recurses into models/detached entities
|
7
|
+
- Fix bugs impacting use of multiple inputs transformers at a time and other issues with typed transformers
|
8
|
+
|
9
|
+
# [0.0.131] - 2025-06-16
|
10
|
+
|
11
|
+
- Extract InMemoryMinimal crud driver specs to foobara-crud-driver-spec-helpers gem
|
12
|
+
- Better support for nested transactions
|
13
|
+
- Fix buggy actions carried out when committing/rolling back transactions
|
14
|
+
- Fix thread leaks in WeakObjectSet and test suite
|
15
|
+
|
1
16
|
# [0.0.130] - 2025-06-06
|
2
17
|
|
3
18
|
- Support using a proc as an attributes default for lazy evaluation of default values
|
@@ -14,10 +14,10 @@ module Foobara
|
|
14
14
|
if detached_to_primary_key?
|
15
15
|
object.primary_key
|
16
16
|
else
|
17
|
-
object.attributes_with_delegates
|
17
|
+
serialize(object.attributes_with_delegates)
|
18
18
|
end
|
19
19
|
when Model
|
20
|
-
object.attributes_with_delegates
|
20
|
+
serialize(object.attributes_with_delegates)
|
21
21
|
when ::Array
|
22
22
|
object.map { |element| serialize(element) }
|
23
23
|
when ::Hash
|
@@ -114,7 +114,9 @@ module Foobara
|
|
114
114
|
inputs_type = command_class.inputs_type
|
115
115
|
|
116
116
|
@inputs_transformers = transformers_to_processors(@inputs_transformers, inputs_type, direction: :to)
|
117
|
+
@inputs_transformers = @inputs_transformers.reverse
|
117
118
|
|
119
|
+
# TODO: this block looks pointless...
|
118
120
|
@inputs_transformers.each do |transformer|
|
119
121
|
if transformer.is_a?(TypeDeclarations::TypedTransformer)
|
120
122
|
new_type = transformer.from_type
|
@@ -442,21 +444,18 @@ module Foobara
|
|
442
444
|
def inputs_transformer
|
443
445
|
return @inputs_transformer if defined?(@inputs_transformer)
|
444
446
|
|
445
|
-
|
447
|
+
transformers = inputs_transformers
|
448
|
+
|
449
|
+
if transformers.empty?
|
446
450
|
@inputs_transformer = nil
|
447
451
|
return
|
448
452
|
end
|
449
453
|
|
450
|
-
@inputs_transformer =
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
transformers.first
|
456
|
-
else
|
457
|
-
Value::Processor::Pipeline.new(processors: transformers)
|
458
|
-
end
|
459
|
-
end
|
454
|
+
@inputs_transformer = if transformers.size == 1
|
455
|
+
transformers.first
|
456
|
+
else
|
457
|
+
Value::Processor::Pipeline.new(processors: transformers)
|
458
|
+
end
|
460
459
|
end
|
461
460
|
|
462
461
|
def response_mutator
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Foobara
|
2
|
+
module CommandConnectors
|
3
|
+
module Transformers
|
4
|
+
class EntityToPrimaryKeyInputsTransformer < TypeDeclarations::TypedTransformer
|
5
|
+
def from_type_declaration
|
6
|
+
return nil unless to_type
|
7
|
+
|
8
|
+
if contains_associations_or_is_entity?(to_type)
|
9
|
+
if to_type.extends?(Foobara::BuiltinTypes[:attributes])
|
10
|
+
to_fix = {}
|
11
|
+
|
12
|
+
to_type.element_types.each_pair do |attribute_name, attribute_type|
|
13
|
+
if contains_associations_or_is_entity?(attribute_type)
|
14
|
+
to_fix[attribute_name] = attribute_type
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
element_type_declarations = to_type.declaration_data[:element_type_declarations].dup
|
19
|
+
|
20
|
+
to_fix.each_pair do |attribute_name, attribute_type|
|
21
|
+
transformer = EntityToPrimaryKeyInputsTransformer.new(to: attribute_type)
|
22
|
+
element_type_declarations[attribute_name] = transformer.from_type_declaration
|
23
|
+
end
|
24
|
+
|
25
|
+
to_type.declaration_data.merge(element_type_declarations:)
|
26
|
+
elsif to_type.extends?(Foobara::BuiltinTypes[:tuple])
|
27
|
+
indexes_to_fix = []
|
28
|
+
|
29
|
+
to_type.element_types.each.with_index do |element_type, index|
|
30
|
+
if contains_associations_or_is_entity?(element_type)
|
31
|
+
indexes_to_fix << index
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
element_type_declarations = to_type.declaration_data[:element_type_declarations].dup
|
36
|
+
|
37
|
+
indexes_to_fix.each do |index|
|
38
|
+
transformer = EntityToPrimaryKeyInputsTransformer.new(to: to_type.element_types[index])
|
39
|
+
element_type_declarations[index] = transformer.from_type_declaration
|
40
|
+
end
|
41
|
+
|
42
|
+
to_type.declaration_data.merge(element_type_declarations:)
|
43
|
+
elsif to_type.extends?(Foobara::BuiltinTypes[:array])
|
44
|
+
transformer = EntityToPrimaryKeyInputsTransformer.new(to: to_type.element_type)
|
45
|
+
element_type_declaration = transformer.from_type_declaration
|
46
|
+
|
47
|
+
to_type.declaration_data.merge(element_type_declaration:)
|
48
|
+
elsif to_type.extends?(Foobara::BuiltinTypes[:detached_entity])
|
49
|
+
declaration = to_type.target_class.primary_key_type.reference_or_declaration_data
|
50
|
+
|
51
|
+
description = "#{to_type.target_class.model_name} #{to_type.target_class.primary_key_attribute}"
|
52
|
+
|
53
|
+
unless to_type.extends_directly?(Foobara::BuiltinTypes[:detached_entity]) ||
|
54
|
+
to_type.extends_directly?(Foobara::BuiltinTypes[:entity])
|
55
|
+
description = [
|
56
|
+
description,
|
57
|
+
to_type.description
|
58
|
+
].join(" : ")
|
59
|
+
end
|
60
|
+
declaration[:description] = description
|
61
|
+
|
62
|
+
declaration
|
63
|
+
elsif to_type.extends?(Foobara::BuiltinTypes[:model])
|
64
|
+
attributes_type = to_type.target_class.attributes_type
|
65
|
+
EntityToPrimaryKeyInputsTransformer.new(to: attributes_type).from_type_declaration
|
66
|
+
else
|
67
|
+
# :nocov:
|
68
|
+
raise "Not sure how to handle #{to_type}"
|
69
|
+
# :nocov:
|
70
|
+
end
|
71
|
+
else
|
72
|
+
to_type
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def transform(inputs)
|
77
|
+
inputs
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def contains_associations_or_is_entity?(type)
|
83
|
+
DetachedEntity.contains_associations?(type) || type.extends?(Foobara::BuiltinTypes[:detached_entity])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -221,19 +221,29 @@ module Foobara
|
|
221
221
|
|
222
222
|
if type.extends?(BuiltinTypes[:detached_entity])
|
223
223
|
if initial
|
224
|
-
|
225
|
-
|
226
|
-
if remove_sensitive
|
227
|
-
# TODO: test this code path
|
228
|
-
# :nocov:
|
229
|
-
element_types = element_types&.reject(&:sensitive?)
|
230
|
-
# :nocov:
|
231
|
-
end
|
232
|
-
|
233
|
-
contains_associations?(element_types, false)
|
224
|
+
contains_associations?(type.element_types, false)
|
234
225
|
else
|
235
226
|
true
|
236
227
|
end
|
228
|
+
elsif type.extends?(BuiltinTypes[:model])
|
229
|
+
element_types = type.element_types
|
230
|
+
|
231
|
+
if remove_sensitive
|
232
|
+
# TODO: test this code path
|
233
|
+
# :nocov:
|
234
|
+
element_types = element_types&.reject(&:sensitive?)
|
235
|
+
# :nocov:
|
236
|
+
end
|
237
|
+
|
238
|
+
contains_associations?(element_types, false)
|
239
|
+
elsif type.extends?(BuiltinTypes[:tuple])
|
240
|
+
element_types = type.element_types
|
241
|
+
|
242
|
+
element_types&.any? do |element_type|
|
243
|
+
if !remove_sensitive || !element_type.sensitive?
|
244
|
+
contains_associations?(element_type, false)
|
245
|
+
end
|
246
|
+
end
|
237
247
|
elsif type.extends?(BuiltinTypes[:array])
|
238
248
|
# TODO: what to do about an associative array type?? Unclear how to make a key from that...
|
239
249
|
# TODO: raise if associative array contains a non-persisted record to handle this edge case for now.
|
@@ -20,7 +20,7 @@ module Foobara
|
|
20
20
|
# TODO: all multiple record methods should return enumerators and code further up should only use
|
21
21
|
# the lazy enumerator interface... to encourage that/catch bugs we will return lazy enumerators in these
|
22
22
|
# built-in crud drivers
|
23
|
-
def all
|
23
|
+
def all(page_size: nil)
|
24
24
|
records.each_value.lazy
|
25
25
|
end
|
26
26
|
|
@@ -4,6 +4,12 @@ module Foobara
|
|
4
4
|
class EntityAttributesCrudDriver
|
5
5
|
attr_accessor :raw_connection, :tables
|
6
6
|
|
7
|
+
class << self
|
8
|
+
def has_real_transactions?
|
9
|
+
false
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
7
13
|
def initialize(connection_or_credentials = nil)
|
8
14
|
self.raw_connection = open_connection(connection_or_credentials)
|
9
15
|
self.tables = {}
|
@@ -31,7 +37,7 @@ module Foobara
|
|
31
37
|
def rollback_transaction(_raw_tx)
|
32
38
|
end
|
33
39
|
|
34
|
-
def
|
40
|
+
def commit_transaction(_raw_tx)
|
35
41
|
end
|
36
42
|
|
37
43
|
def table_for(entity_class)
|
@@ -91,7 +97,7 @@ module Foobara
|
|
91
97
|
# :nocov:
|
92
98
|
end
|
93
99
|
|
94
|
-
def all
|
100
|
+
def all(page_size: nil)
|
95
101
|
# :nocov:
|
96
102
|
raise "subclass responsibility"
|
97
103
|
# :nocov:
|
@@ -161,39 +167,22 @@ module Foobara
|
|
161
167
|
|
162
168
|
def matches_attributes_filter?(attributes, attributes_filter)
|
163
169
|
attributes_filter.all? do |attribute_name_or_path, value|
|
164
|
-
|
170
|
+
# get the model-free type?
|
171
|
+
attribute_type = entity_class.attributes_type.type_at_path(attribute_name_or_path)
|
172
|
+
|
173
|
+
value = restore_attributes(value, attribute_type)
|
165
174
|
|
166
175
|
if attribute_name_or_path.is_a?(::Array)
|
167
176
|
values = DataPath.values_at(attribute_name_or_path, attributes)
|
168
177
|
|
169
178
|
values.any? do |attribute_value|
|
170
|
-
|
179
|
+
restore_attributes(attribute_value, attribute_type) == value
|
171
180
|
end
|
172
181
|
else
|
173
|
-
attribute_value = attributes
|
174
|
-
|
175
|
-
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
def normalize_attribute_filter_value(value)
|
180
|
-
case value
|
181
|
-
when ::Array
|
182
|
-
value.map { |v| normalize_attribute_filter_value(v) }
|
183
|
-
when ::Hash
|
184
|
-
value.to_h do |k, v|
|
185
|
-
[normalize_attribute_filter_value(k), normalize_attribute_filter_value(v)]
|
186
|
-
end
|
187
|
-
when DetachedEntity
|
188
|
-
if value.persisted?
|
189
|
-
normalize_attribute_filter_value(value.primary_key)
|
190
|
-
else
|
191
|
-
value
|
182
|
+
attribute_value = DataPath.value_at(attribute_name_or_path, attributes)
|
183
|
+
attribute_value = restore_attributes(attribute_value, attribute_type)
|
184
|
+
attribute_value == value
|
192
185
|
end
|
193
|
-
when Model
|
194
|
-
normalize_attribute_filter_value(value.attributes)
|
195
|
-
else
|
196
|
-
value
|
197
186
|
end
|
198
187
|
end
|
199
188
|
|
@@ -262,6 +251,50 @@ module Foobara
|
|
262
251
|
def primary_key_attribute
|
263
252
|
entity_class.primary_key_attribute
|
264
253
|
end
|
254
|
+
|
255
|
+
def restore_attributes(object, type = entity_class.attributes_type)
|
256
|
+
if type.extends?(BuiltinTypes[:attributes])
|
257
|
+
object.to_h do |attribute_name, attribute_value|
|
258
|
+
attribute_type = type.type_at_path(attribute_name)
|
259
|
+
[attribute_name.to_sym, restore_attributes(attribute_value, attribute_type)]
|
260
|
+
end
|
261
|
+
elsif type.extends?(BuiltinTypes[:tuple])
|
262
|
+
# TODO: test this code path
|
263
|
+
# :nocov:
|
264
|
+
object.map.with_index do |value, index|
|
265
|
+
element_type = type.element_types[index]
|
266
|
+
restore_attributes(value, element_type)
|
267
|
+
end
|
268
|
+
# :nocov:
|
269
|
+
elsif type.extends?(BuiltinTypes[:array])
|
270
|
+
element_type = type.element_type
|
271
|
+
object.map { |value| restore_attributes(value, element_type) }
|
272
|
+
elsif type.extends?(BuiltinTypes[:entity])
|
273
|
+
if object.is_a?(Model)
|
274
|
+
if object.persisted?
|
275
|
+
object = object.primary_key
|
276
|
+
restore_attributes(object, type.target_class.primary_key_type)
|
277
|
+
else
|
278
|
+
object
|
279
|
+
end
|
280
|
+
else
|
281
|
+
restore_attributes(object, type.target_class.primary_key_type)
|
282
|
+
end
|
283
|
+
elsif type.extends?(BuiltinTypes[:model])
|
284
|
+
if object.is_a?(Model)
|
285
|
+
object = object.attributes
|
286
|
+
end
|
287
|
+
restore_attributes(object, type.element_types)
|
288
|
+
else
|
289
|
+
outcome = type.process_value(object)
|
290
|
+
|
291
|
+
if outcome.success?
|
292
|
+
outcome.result
|
293
|
+
else
|
294
|
+
object
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
265
298
|
end
|
266
299
|
end
|
267
300
|
end
|
@@ -15,12 +15,20 @@ module Foobara
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
+
def open_nested!(outer_tx)
|
19
|
+
state_machine.open_nested! do
|
20
|
+
self.is_nested = true
|
21
|
+
self.raw_tx = outer_tx.raw_tx
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
18
25
|
def flush!
|
19
26
|
state_machine.flush! do
|
20
27
|
each_table(&:validate!)
|
21
28
|
each_table(&:flush_created!)
|
22
29
|
each_table(&:flush_updated_and_hard_deleted!)
|
23
30
|
end
|
31
|
+
entity_attributes_crud_driver.flush_transaction(raw_tx)
|
24
32
|
rescue => e
|
25
33
|
# :nocov:
|
26
34
|
rollback!(e)
|
@@ -34,6 +42,7 @@ module Foobara
|
|
34
42
|
state_machine.revert! do
|
35
43
|
each_table(&:revert!)
|
36
44
|
end
|
45
|
+
entity_attributes_crud_driver.revert_transaction(raw_tx)
|
37
46
|
rescue => e
|
38
47
|
# :nocov:
|
39
48
|
rollback!(e)
|
@@ -42,11 +51,25 @@ module Foobara
|
|
42
51
|
end
|
43
52
|
|
44
53
|
def commit!
|
54
|
+
return commit_nested! if nested?
|
55
|
+
|
45
56
|
state_machine.commit! do
|
46
|
-
each_table(&:
|
47
|
-
|
48
|
-
each_table(&:
|
49
|
-
|
57
|
+
each_table(&:commit!)
|
58
|
+
entity_attributes_crud_driver.commit_transaction(raw_tx)
|
59
|
+
each_table(&:transaction_closed)
|
60
|
+
end
|
61
|
+
rescue => e
|
62
|
+
# :nocov:
|
63
|
+
rollback!(e)
|
64
|
+
raise
|
65
|
+
# :nocov:
|
66
|
+
end
|
67
|
+
|
68
|
+
def commit_nested!
|
69
|
+
state_machine.commit_nested! do
|
70
|
+
each_table(&:commit!)
|
71
|
+
entity_attributes_crud_driver.flush_transaction(raw_tx)
|
72
|
+
each_table(&:transaction_closed)
|
50
73
|
end
|
51
74
|
rescue => e
|
52
75
|
# :nocov:
|
@@ -61,13 +84,32 @@ module Foobara
|
|
61
84
|
end
|
62
85
|
|
63
86
|
def rollback!(because_of = nil)
|
87
|
+
return rollback_nested!(because_of) if nested?
|
88
|
+
|
64
89
|
state_machine.rollback! do
|
65
90
|
# TODO: raise error if already flushed and if crud_driver doesn't support true transactions
|
66
91
|
entity_attributes_crud_driver.rollback_transaction(raw_tx)
|
67
92
|
each_table(&:rollback!)
|
68
|
-
entity_attributes_crud_driver.close_transaction(raw_tx)
|
69
93
|
end
|
70
94
|
|
95
|
+
each_table(&:transaction_closed)
|
96
|
+
|
97
|
+
if !because_of && (self == entity_base.current_transaction)
|
98
|
+
raise RolledBack, "intentionally rolled back"
|
99
|
+
end
|
100
|
+
rescue
|
101
|
+
state_machine.error! if state_machine.currently_open?
|
102
|
+
raise
|
103
|
+
end
|
104
|
+
|
105
|
+
def rollback_nested!(because_of = nil)
|
106
|
+
state_machine.rollback_nested! do
|
107
|
+
entity_attributes_crud_driver.revert_transaction(raw_tx)
|
108
|
+
each_table(&:revert!)
|
109
|
+
end
|
110
|
+
|
111
|
+
each_table(&:transaction_closed)
|
112
|
+
|
71
113
|
if !because_of && (self == entity_base.current_transaction)
|
72
114
|
raise RolledBack, "intentionally rolled back"
|
73
115
|
end
|
@@ -7,6 +7,7 @@ module Foobara
|
|
7
7
|
set_transition_map({
|
8
8
|
unopened: {
|
9
9
|
open: :open,
|
10
|
+
open_nested: :open,
|
10
11
|
close: :closed
|
11
12
|
},
|
12
13
|
open: {
|
@@ -17,7 +18,9 @@ module Foobara
|
|
17
18
|
# TODO: should we have intermediate states to quickly get out of the open state?
|
18
19
|
rollback: :closed,
|
19
20
|
commit: :closed,
|
20
|
-
error: :closed
|
21
|
+
error: :closed,
|
22
|
+
commit_nested: :closed,
|
23
|
+
rollback_nested: :closed
|
21
24
|
}
|
22
25
|
})
|
23
26
|
end
|
@@ -6,7 +6,7 @@ module Foobara
|
|
6
6
|
include Concerns::EntityCallbackHandling
|
7
7
|
include Concerns::TransactionTracking
|
8
8
|
|
9
|
-
attr_accessor :state_machine, :entity_base, :raw_tx, :tables
|
9
|
+
attr_accessor :state_machine, :entity_base, :raw_tx, :tables, :is_nested
|
10
10
|
|
11
11
|
def initialize(entity_base)
|
12
12
|
self.entity_base = entity_base
|
@@ -162,6 +162,10 @@ module Foobara
|
|
162
162
|
def perform(&)
|
163
163
|
entity_base.using_transaction(self, &)
|
164
164
|
end
|
165
|
+
|
166
|
+
def nested?
|
167
|
+
is_nested
|
168
|
+
end
|
165
169
|
end
|
166
170
|
end
|
167
171
|
end
|
@@ -83,10 +83,25 @@ module Foobara
|
|
83
83
|
end
|
84
84
|
|
85
85
|
def rolled_back
|
86
|
+
closed
|
87
|
+
end
|
88
|
+
|
89
|
+
def committed
|
90
|
+
closed
|
91
|
+
end
|
92
|
+
|
93
|
+
def closed
|
86
94
|
marked_hard_deleted.clear
|
87
95
|
marked_updated.clear
|
88
96
|
marked_created.clear
|
89
|
-
|
97
|
+
marked_loading.clear
|
98
|
+
end
|
99
|
+
|
100
|
+
# We need to clear this one separately. That's because otherwise a different table
|
101
|
+
# might flush and create a thunk if it has an association to this table but we've stopped
|
102
|
+
# tracking the record.
|
103
|
+
def transaction_closed
|
104
|
+
tracked_records.close
|
90
105
|
end
|
91
106
|
|
92
107
|
def reverted
|
@@ -632,6 +632,14 @@ module Foobara
|
|
632
632
|
rolled_back
|
633
633
|
end
|
634
634
|
|
635
|
+
def commit!
|
636
|
+
validate!
|
637
|
+
flush_created!
|
638
|
+
flush_updated_and_hard_deleted!
|
639
|
+
|
640
|
+
committed
|
641
|
+
end
|
642
|
+
|
635
643
|
def revert!
|
636
644
|
# TODO: could pause record tracking while doing this as a performance boost
|
637
645
|
marked_updated.each(&:restore_without_callbacks!)
|
@@ -52,7 +52,31 @@ module Foobara
|
|
52
52
|
Thread.inheritable_thread_local_var_set(transaction_key, transaction)
|
53
53
|
end
|
54
54
|
|
55
|
-
|
55
|
+
# What types of transaction scenarios are there?
|
56
|
+
# 1. If a transaction is already open, use it as a "nested transaction", otherwise, open a new one.
|
57
|
+
# A nested transaction means that "rollback" is the same as "revert" and "commit" is the same as "flush".
|
58
|
+
# For a true
|
59
|
+
# 2. If a transaction is already open, raise an error. otherwise, open a new one.
|
60
|
+
# 3. Open a new, independent transaction, no matter what.
|
61
|
+
# 4. If a transaction is already open, use it, otherwise, open a new one.
|
62
|
+
# 5. We are outside of a transaction but have a handle on one. We want to set it as the current transaction.
|
63
|
+
# and do some work in that transaction.
|
64
|
+
# Which use cases do we probably need at the moment?
|
65
|
+
# 1. If we are running a command calling other commands, we will open transactions when needed but
|
66
|
+
# inherit any already-open transactions. Commands don't commit or flush to the already-open transactions
|
67
|
+
# that they inherit. So this feels like a "use existing" situation or a situation where we don't even
|
68
|
+
# bother calling open_transaction at all. This is the most important use-case. It can be helpful to raise
|
69
|
+
# in this situation because it is not expected that there's an existing transaction yet we're opening another.
|
70
|
+
# 2. We might have a situation where we are in one transaction but definitely want to open a new one and
|
71
|
+
# commit it ourselves and have its results committed and visible independent of the current transaction.
|
72
|
+
# So this feels like a "open new" situation where we don't want to raise an error if a transaction is
|
73
|
+
# already open.
|
74
|
+
VALID_MODES = [
|
75
|
+
:use_existing,
|
76
|
+
:open_nested,
|
77
|
+
:open_new,
|
78
|
+
nil
|
79
|
+
].freeze
|
56
80
|
|
57
81
|
def using_transaction(existing_transaction, &)
|
58
82
|
transaction(existing_transaction:, &)
|
@@ -67,18 +91,28 @@ module Foobara
|
|
67
91
|
|
68
92
|
old_transaction = current_transaction
|
69
93
|
|
70
|
-
if old_transaction
|
94
|
+
if old_transaction && !old_transaction.currently_open?
|
71
95
|
old_transaction = nil
|
72
96
|
end
|
73
97
|
|
74
|
-
|
98
|
+
open_nested = false
|
99
|
+
|
100
|
+
if old_transaction
|
75
101
|
if mode == :use_existing || existing_transaction == old_transaction
|
76
102
|
if block_given?
|
77
103
|
return yield old_transaction
|
78
104
|
else
|
79
105
|
return old_transaction
|
80
106
|
end
|
81
|
-
elsif mode
|
107
|
+
elsif mode == :open_nested
|
108
|
+
open_nested = true
|
109
|
+
elsif mode == :open_new
|
110
|
+
if existing_transaction
|
111
|
+
# :nocov:
|
112
|
+
raise ArgumentError, "Cannot use mode :open_new with existing_transaction:"
|
113
|
+
# :nocov:
|
114
|
+
end
|
115
|
+
else
|
82
116
|
# :nocov:
|
83
117
|
raise "Transaction already open. " \
|
84
118
|
"Use mode :use_existing if you want to make use of the existing transaction. " \
|
@@ -96,16 +130,27 @@ module Foobara
|
|
96
130
|
tx = existing_transaction
|
97
131
|
else
|
98
132
|
tx = Transaction.new(self)
|
99
|
-
|
133
|
+
|
134
|
+
if open_nested
|
135
|
+
tx.open_nested!(old_transaction)
|
136
|
+
else
|
137
|
+
tx.open!
|
138
|
+
end
|
100
139
|
end
|
101
140
|
|
102
141
|
set_current_transaction(tx)
|
142
|
+
|
103
143
|
result = yield tx
|
104
|
-
|
144
|
+
|
145
|
+
if tx.currently_open? && !existing_transaction
|
146
|
+
tx.commit!
|
147
|
+
end
|
105
148
|
result
|
106
149
|
rescue Foobara::Persistence::EntityBase::Transaction::RolledBack # rubocop:disable Lint/SuppressedException
|
107
150
|
rescue => e
|
108
|
-
|
151
|
+
if tx.currently_open?
|
152
|
+
tx.rollback!(e)
|
153
|
+
end
|
109
154
|
raise
|
110
155
|
ensure
|
111
156
|
set_current_transaction(old_transaction)
|
data/projects/types/src/type.rb
CHANGED
@@ -232,6 +232,27 @@ module Foobara
|
|
232
232
|
end
|
233
233
|
end
|
234
234
|
|
235
|
+
def extends_directly?(type)
|
236
|
+
case type
|
237
|
+
when Type
|
238
|
+
base_type == type
|
239
|
+
when Symbol, String
|
240
|
+
concrete_type = created_in_namespace.foobara_lookup_type(type)
|
241
|
+
|
242
|
+
if concrete_type.nil?
|
243
|
+
# :nocov:
|
244
|
+
raise "No type found for #{type}"
|
245
|
+
# :nocov:
|
246
|
+
end
|
247
|
+
|
248
|
+
extends_directly?(concrete_type)
|
249
|
+
else
|
250
|
+
# :nocov:
|
251
|
+
raise ArgumentError, "Expected a Type or a Symbol/String, but got #{type.inspect}"
|
252
|
+
# :nocov:
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
235
256
|
def extends_type?(type)
|
236
257
|
return true if self == type
|
237
258
|
|
@@ -4,8 +4,6 @@ module Foobara
|
|
4
4
|
# TODO: a possible optimization: have a certain number of records before the Weakref approach kicks in
|
5
5
|
# that way we don't just immediately clear out useful information without any actual memory burden
|
6
6
|
class WeakObjectSet
|
7
|
-
class InvalidWtf < StandardError; end
|
8
|
-
|
9
7
|
class GarbageCleaner
|
10
8
|
attr_accessor :weak_object_set, :deactivated, :queue, :cleanup_thread
|
11
9
|
|
@@ -17,6 +15,12 @@ module Foobara
|
|
17
15
|
end
|
18
16
|
|
19
17
|
def cleanup_proc
|
18
|
+
if deactivated?
|
19
|
+
# :nocov:
|
20
|
+
raise "GarbageCleaner has been deactivated"
|
21
|
+
# :nocov:
|
22
|
+
end
|
23
|
+
|
20
24
|
@cleanup_proc ||= begin
|
21
25
|
queue = self.queue
|
22
26
|
|
@@ -35,6 +39,8 @@ module Foobara
|
|
35
39
|
end
|
36
40
|
|
37
41
|
def start_cleanup_thread
|
42
|
+
queue = self.queue
|
43
|
+
|
38
44
|
self.cleanup_thread = Thread.new do
|
39
45
|
loop do
|
40
46
|
object_id = queue.pop
|
@@ -57,9 +63,16 @@ module Foobara
|
|
57
63
|
end
|
58
64
|
|
59
65
|
def deactivate
|
66
|
+
raise if deactivated?
|
67
|
+
|
60
68
|
self.deactivated = true
|
61
69
|
queue.close
|
70
|
+
# TODO: don't bother to join here outside of test suite
|
62
71
|
cleanup_thread.join # just doing this for test suite/simplecov
|
72
|
+
@cleanup_proc = nil
|
73
|
+
@queue = nil
|
74
|
+
@weak_object_set = nil
|
75
|
+
@cleanup_thread = nil
|
63
76
|
end
|
64
77
|
|
65
78
|
def deactivated?
|
@@ -69,7 +82,7 @@ module Foobara
|
|
69
82
|
|
70
83
|
include Enumerable
|
71
84
|
|
72
|
-
attr_accessor :monitor, :key_method, :key_to_object_id, :object_id_to_key, :objects
|
85
|
+
attr_accessor :monitor, :key_method, :key_to_object_id, :object_id_to_key, :objects, :closed
|
73
86
|
attr_writer :garbage_cleaner
|
74
87
|
|
75
88
|
def initialize(key_method = nil)
|
@@ -136,19 +149,17 @@ module Foobara
|
|
136
149
|
@garbage_cleaner ||= begin
|
137
150
|
queue = Queue.new
|
138
151
|
|
139
|
-
|
140
|
-
|
141
|
-
ObjectSpace.define_finalizer gc do
|
142
|
-
# :nocov:
|
143
|
-
queue.close
|
144
|
-
# :nocov:
|
145
|
-
end
|
146
|
-
|
147
|
-
gc
|
152
|
+
GarbageCleaner.new(self, queue)
|
148
153
|
end
|
149
154
|
end
|
150
155
|
|
151
156
|
def <<(object)
|
157
|
+
if closed?
|
158
|
+
# :nocov:
|
159
|
+
raise "Cannot add objects to a closed WeakObjectSet"
|
160
|
+
# :nocov:
|
161
|
+
end
|
162
|
+
|
152
163
|
object_id = object.object_id
|
153
164
|
|
154
165
|
monitor.synchronize do
|
@@ -235,11 +246,30 @@ module Foobara
|
|
235
246
|
end
|
236
247
|
end
|
237
248
|
|
249
|
+
def close
|
250
|
+
raise if closed?
|
251
|
+
|
252
|
+
self.closed = true
|
253
|
+
stop_garbage_cleaner
|
254
|
+
end
|
255
|
+
|
256
|
+
def stop_garbage_cleaner
|
257
|
+
gc = nil
|
258
|
+
|
259
|
+
monitor.synchronize do
|
260
|
+
if @garbage_cleaner
|
261
|
+
gc = garbage_cleaner
|
262
|
+
self.garbage_cleaner = nil
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
gc&.deactivate
|
267
|
+
end
|
268
|
+
|
238
269
|
def clear
|
239
270
|
monitor.synchronize do
|
240
|
-
|
271
|
+
stop_garbage_cleaner
|
241
272
|
|
242
|
-
self.garbage_cleaner = nil
|
243
273
|
self.objects = {}
|
244
274
|
|
245
275
|
if key_method
|
@@ -248,5 +278,9 @@ module Foobara
|
|
248
278
|
end
|
249
279
|
end
|
250
280
|
end
|
281
|
+
|
282
|
+
def closed?
|
283
|
+
closed
|
284
|
+
end
|
251
285
|
end
|
252
286
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foobara
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.132
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Miles Georgi
|
@@ -229,6 +229,7 @@ files:
|
|
229
229
|
- projects/command_connectors/src/serializers/yaml_serializer.rb
|
230
230
|
- projects/command_connectors/src/transformed_command.rb
|
231
231
|
- projects/command_connectors/src/transformers/auth_errors_transformer.rb
|
232
|
+
- projects/command_connectors/src/transformers/entity_to_primary_key_inputs_transformer.rb
|
232
233
|
- projects/command_connectors/src/transformers/load_aggregates_pre_commit_transformer.rb
|
233
234
|
- projects/command_connectors/src/transformers/load_delegated_attributes_entities_pre_commit_transformer.rb
|
234
235
|
- projects/common/lib/foobara/common.rb
|
@@ -486,8 +487,8 @@ licenses:
|
|
486
487
|
- MPL-2.0
|
487
488
|
metadata:
|
488
489
|
homepage_uri: https://foobara.com
|
489
|
-
source_code_uri: https://
|
490
|
-
changelog_uri: https://
|
490
|
+
source_code_uri: https://github.com/foobara/foobara
|
491
|
+
changelog_uri: https://github.com/foobara/foobara/blob/main/CHANGELOG.md
|
491
492
|
rubygems_mfa_required: 'true'
|
492
493
|
rdoc_options: []
|
493
494
|
require_paths:
|
@@ -529,7 +530,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
529
530
|
- !ruby/object:Gem::Version
|
530
531
|
version: '0'
|
531
532
|
requirements: []
|
532
|
-
rubygems_version: 3.6.
|
533
|
+
rubygems_version: 3.6.9
|
533
534
|
specification_version: 4
|
534
535
|
summary: A command-centric and discoverable software framework with a focus on domain
|
535
536
|
concepts and abstracting away integration code
|