foobara 0.1.16 → 0.2.1
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/.ruby-version +1 -1
- data/CHANGELOG.md +10 -0
- data/projects/entity/src/concerns/persistence.rb +1 -1
- data/projects/entity/src/extensions/domain/domain_module_extension.rb +15 -0
- data/projects/nested_transactionable/lib/foobara/nested_transactionable.rb +78 -27
- data/projects/persistence/src/entity_attributes_crud_driver.rb +30 -4
- data/projects/persistence/src/entity_base/table.rb +1 -0
- data/projects/persistence/src/entity_base/transaction.rb +0 -1
- data/projects/persistence/src/entity_base.rb +5 -1
- data/projects/persistence/src/persistence.rb +139 -24
- data/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d6975f14259f6fa733c19dc1877d1868ef90b2f72a6408ba49cf1d0706ca69fe
|
4
|
+
data.tar.gz: ab1c0f032c8f653c2b6f5d2b24aa1eded6a5cec1ec6994ee905e90d87edcb157
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ff85faf59d67f5d1cfeab25f3520633df1747025aa760428af91c0b6e100893ff722f9a5dcac86246009842a0b2a0930ae18be26baabedaba42b46aa94f36472
|
7
|
+
data.tar.gz: 13ca3101da2f61f2d181a10a751e5e299722195cfdf1057a4216cf4bbf592728a2ec0cbbbfdc315827e74b6d579ba6e57fc5d31bdd7ab84323c48cffd6ba238b
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.4.
|
1
|
+
3.4.7
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
# [0.2.1] - 2025-10-21
|
2
|
+
|
3
|
+
- Rename EntityBase#initialize prefix: to table_prefix: to avoid collision with redis-crud-driver
|
4
|
+
|
5
|
+
# [0.2.0] - 2025-10-21
|
6
|
+
|
7
|
+
- Support setting an entity base for a domain
|
8
|
+
- Add TransactionGroup
|
9
|
+
- Properly sort and nest bases/transactions/entities to fix some multi-base transaction bugs
|
10
|
+
|
1
11
|
# [0.1.16] - 2025-10-15
|
2
12
|
|
3
13
|
- Improve manifest processing performance by memoizing ManifestBase#hash
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Foobara
|
2
|
+
module Domain
|
3
|
+
module DomainModuleExtension
|
4
|
+
module ClassMethods
|
5
|
+
attr_reader :foobara_default_entity_base
|
6
|
+
|
7
|
+
def foobara_set_entity_base(*, name: nil, table_prefix: nil)
|
8
|
+
name ||= Util.underscore(scoped_full_name).gsub("::", "_")
|
9
|
+
base = Persistence.register_base(*, name:, table_prefix:)
|
10
|
+
@foobara_default_entity_base = base
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -19,30 +19,13 @@ module Foobara
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def with_needed_transactions_for_type(type, &)
|
22
|
-
|
22
|
+
entity_classes = relevant_entity_classes_for_type(type)
|
23
23
|
|
24
|
-
if
|
24
|
+
if entity_classes.empty?
|
25
25
|
return yield
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
tx_class.include NestedTransactionable
|
30
|
-
|
31
|
-
tx_class.define_method(:relevant_entity_classes) do
|
32
|
-
relevant_entity_classes
|
33
|
-
end
|
34
|
-
|
35
|
-
tx_instance = tx_class.new
|
36
|
-
|
37
|
-
begin
|
38
|
-
tx_instance.open_transaction
|
39
|
-
result = Persistence::EntityBase.using_transactions(tx_instance.transactions, &)
|
40
|
-
tx_instance.commit_transaction
|
41
|
-
result
|
42
|
-
rescue
|
43
|
-
tx_instance.rollback_transaction
|
44
|
-
raise
|
45
|
-
end
|
28
|
+
TransactionGroup.run(entity_classes:, &)
|
46
29
|
end
|
47
30
|
end
|
48
31
|
|
@@ -62,13 +45,26 @@ module Foobara
|
|
62
45
|
|
63
46
|
def opened_transactions
|
64
47
|
@opened_transactions ||= []
|
48
|
+
Persistence.sort_transactions(@opened_transactions)
|
65
49
|
end
|
66
50
|
|
67
51
|
def auto_detect_current_transactions
|
68
|
-
|
69
|
-
|
52
|
+
bases = nil
|
53
|
+
|
54
|
+
if respond_to?(:relevant_entity_bases)
|
55
|
+
bases = relevant_entity_bases
|
56
|
+
|
57
|
+
unless bases.nil?
|
58
|
+
return if bases.empty?
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
unless bases
|
63
|
+
classes = relevant_entity_classes
|
64
|
+
return if classes.nil? || classes.empty?
|
70
65
|
|
71
|
-
|
66
|
+
bases = classes.map(&:entity_base).uniq
|
67
|
+
end
|
72
68
|
|
73
69
|
bases.each do |base|
|
74
70
|
tx = base.current_transaction
|
@@ -84,12 +80,35 @@ module Foobara
|
|
84
80
|
|
85
81
|
bases_not_needing_transaction = transactions.map(&:entity_base)
|
86
82
|
|
87
|
-
bases_needing_transaction =
|
83
|
+
bases_needing_transaction = nil
|
84
|
+
|
85
|
+
if respond_to?(:relevant_entity_bases)
|
86
|
+
bases_needing_transaction = relevant_entity_bases
|
87
|
+
|
88
|
+
unless bases_needing_transaction.nil?
|
89
|
+
return if bases_needing_transaction.empty?
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
unless bases_needing_transaction
|
94
|
+
classes = relevant_entity_classes
|
95
|
+
return if classes.nil? || classes.empty?
|
96
|
+
|
97
|
+
bases_needing_transaction = relevant_entity_classes.map(&:entity_base).uniq - bases_not_needing_transaction
|
98
|
+
end
|
99
|
+
|
100
|
+
bases_needing_transaction = Persistence.sort_bases(bases_needing_transaction)
|
88
101
|
|
89
102
|
bases_needing_transaction.each do |entity_base|
|
90
|
-
|
91
|
-
|
92
|
-
|
103
|
+
transaction_mode = if respond_to?(:transaction_mode)
|
104
|
+
self.transaction_mode
|
105
|
+
end
|
106
|
+
transaction = entity_base.transaction(transaction_mode)
|
107
|
+
unless transaction.currently_open?
|
108
|
+
transaction.open!
|
109
|
+
@opened_transactions ||= []
|
110
|
+
@opened_transactions << transaction
|
111
|
+
end
|
93
112
|
transactions << transaction
|
94
113
|
end
|
95
114
|
end
|
@@ -122,4 +141,36 @@ module Foobara
|
|
122
141
|
Persistence::EntityBase.using_transactions(transactions, &)
|
123
142
|
end
|
124
143
|
end
|
144
|
+
|
145
|
+
class TransactionGroup
|
146
|
+
include NestedTransactionable
|
147
|
+
|
148
|
+
class << self
|
149
|
+
def run(mode: nil, entity_classes: nil, bases: nil, &)
|
150
|
+
new(
|
151
|
+
transaction_mode: mode,
|
152
|
+
relevant_entity_classes: entity_classes,
|
153
|
+
relevant_entity_bases: bases
|
154
|
+
).run(&)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
attr_accessor :relevant_entity_classes, :relevant_entity_bases, :transaction_mode
|
159
|
+
|
160
|
+
def initialize(transaction_mode: nil, relevant_entity_classes: nil, relevant_entity_bases: nil)
|
161
|
+
self.relevant_entity_classes = relevant_entity_classes
|
162
|
+
self.relevant_entity_bases = relevant_entity_bases
|
163
|
+
self.transaction_mode = transaction_mode
|
164
|
+
end
|
165
|
+
|
166
|
+
def run(&)
|
167
|
+
open_transaction
|
168
|
+
result = Persistence::EntityBase.using_transactions(transactions, &)
|
169
|
+
commit_transaction
|
170
|
+
result
|
171
|
+
rescue
|
172
|
+
rollback_transaction
|
173
|
+
raise
|
174
|
+
end
|
175
|
+
end
|
125
176
|
end
|
@@ -2,7 +2,7 @@ module Foobara
|
|
2
2
|
# Might be best to rename this to CrudDrivers or CrudDriver instead of Persistence?
|
3
3
|
module Persistence
|
4
4
|
class EntityAttributesCrudDriver
|
5
|
-
attr_accessor :raw_connection, :tables
|
5
|
+
attr_accessor :raw_connection, :tables, :table_prefix
|
6
6
|
|
7
7
|
class << self
|
8
8
|
def has_real_transactions?
|
@@ -10,7 +10,8 @@ module Foobara
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
def initialize(connection_or_credentials = nil)
|
13
|
+
def initialize(connection_or_credentials = nil, table_prefix: nil)
|
14
|
+
self.table_prefix = table_prefix
|
14
15
|
self.raw_connection = open_connection(connection_or_credentials)
|
15
16
|
self.tables = {}
|
16
17
|
end
|
@@ -43,7 +44,26 @@ module Foobara
|
|
43
44
|
def table_for(entity_class)
|
44
45
|
key = entity_class.full_entity_name
|
45
46
|
|
46
|
-
tables[key] ||=
|
47
|
+
tables[key] ||= begin
|
48
|
+
if table_prefix
|
49
|
+
table_name = entity_class.entity_name
|
50
|
+
table_name.gsub!(/^Types::/, "")
|
51
|
+
|
52
|
+
table_name = Util.underscore(entity_class.entity_name)
|
53
|
+
|
54
|
+
table_name = if table_prefix == true
|
55
|
+
"#{Util.underscore(entity_class.domain.scoped_full_name)}_#{table_name}"
|
56
|
+
else
|
57
|
+
"#{table_prefix}_#{table_name}"
|
58
|
+
end
|
59
|
+
|
60
|
+
table_name.gsub!("::", "_")
|
61
|
+
end
|
62
|
+
|
63
|
+
# TODO: this seems like a smell
|
64
|
+
Persistence.bases_need_sorting!
|
65
|
+
self.class::Table.new(entity_class, self, table_name)
|
66
|
+
end
|
47
67
|
end
|
48
68
|
|
49
69
|
# TODO: relocate this to another file?
|
@@ -82,7 +102,13 @@ module Foobara
|
|
82
102
|
|
83
103
|
attr_accessor :table_name, :entity_class, :raw_connection, :crud_driver
|
84
104
|
|
85
|
-
def initialize(entity_class, crud_driver, table_name =
|
105
|
+
def initialize(entity_class, crud_driver, table_name = nil)
|
106
|
+
if table_name.nil?
|
107
|
+
table_name = Util.underscore(entity_class.entity_name)
|
108
|
+
table_name.gsub!(/^types::/, "")
|
109
|
+
table_name.gsub!("::", "_")
|
110
|
+
end
|
111
|
+
|
86
112
|
self.crud_driver = crud_driver
|
87
113
|
self.entity_class = entity_class
|
88
114
|
# what is this used for?
|
@@ -22,6 +22,7 @@ module Foobara
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
+
# returns the entities such that ones on the right are allowed to depend on ones on the left
|
25
26
|
def order_entity_classes(entity_classes)
|
26
27
|
return entity_classes if entity_classes.size <= 1
|
27
28
|
|
@@ -66,7 +67,6 @@ module Foobara
|
|
66
67
|
self.entity_attributes_crud_driver = entity_attributes_crud_driver
|
67
68
|
self.tables = {}
|
68
69
|
self.name = name
|
69
|
-
# TODO: a smell?
|
70
70
|
end
|
71
71
|
|
72
72
|
def register_entity_class(entity_class, table_name: entity_class.full_entity_name)
|
@@ -79,6 +79,10 @@ module Foobara
|
|
79
79
|
tables[table.table_name] = table
|
80
80
|
end
|
81
81
|
|
82
|
+
def entity_classes
|
83
|
+
entity_attributes_crud_driver.tables.values.map(&:entity_class)
|
84
|
+
end
|
85
|
+
|
82
86
|
def transaction_key
|
83
87
|
@transaction_key ||= "foobara:tx:#{name}"
|
84
88
|
end
|
@@ -28,8 +28,7 @@ module Foobara
|
|
28
28
|
|
29
29
|
# TODO: automatically order these by dependency...
|
30
30
|
# TODO: also, consider automatically opening transactions for dependent entities automatically...
|
31
|
-
def transaction(*objects, mode: nil, &
|
32
|
-
# def transaction(mode = nil, existing_transaction: nil)
|
31
|
+
def transaction(*objects, mode: nil, &)
|
33
32
|
bases = objects_to_bases(objects)
|
34
33
|
|
35
34
|
if bases.empty?
|
@@ -39,13 +38,9 @@ module Foobara
|
|
39
38
|
end
|
40
39
|
|
41
40
|
if bases.size == 1
|
42
|
-
bases.first.transaction(mode, &
|
41
|
+
bases.first.transaction(mode, &)
|
43
42
|
else
|
44
|
-
|
45
|
-
proc do
|
46
|
-
base.transaction(mode, &nested_proc)
|
47
|
-
end
|
48
|
-
end.call
|
43
|
+
Foobara::TransactionGroup.run(bases:, mode:, &)
|
49
44
|
end
|
50
45
|
end
|
51
46
|
|
@@ -99,13 +94,15 @@ module Foobara
|
|
99
94
|
end
|
100
95
|
|
101
96
|
def objects_to_bases(objects)
|
102
|
-
|
103
|
-
objects = EntityBase.order_entity_classes(objects)
|
104
|
-
end
|
105
|
-
|
106
|
-
objects.map do |object|
|
97
|
+
unsorted = objects.map do |object|
|
107
98
|
object_to_base(object)
|
108
99
|
end.uniq
|
100
|
+
|
101
|
+
if bases_need_sorting?
|
102
|
+
sort_bases!
|
103
|
+
end
|
104
|
+
|
105
|
+
bases.values & unsorted
|
109
106
|
end
|
110
107
|
|
111
108
|
def object_to_base(object)
|
@@ -131,22 +128,23 @@ module Foobara
|
|
131
128
|
@bases ||= {}
|
132
129
|
end
|
133
130
|
|
134
|
-
def base_for_entity_class_name(entity_class_name)
|
135
|
-
table_for_entity_class_name(entity_class_name).entity_base
|
136
|
-
end
|
137
|
-
|
138
131
|
def base_for_entity_class(entity_class)
|
139
|
-
|
132
|
+
table_for_entity_class(entity_class).entity_base
|
140
133
|
end
|
141
134
|
|
142
|
-
def
|
135
|
+
def table_for_entity_class(entity_class)
|
136
|
+
entity_class_name = entity_class.full_entity_name
|
143
137
|
table = tables_for_entity_class_name[entity_class_name]
|
144
138
|
|
145
139
|
return table if table
|
146
140
|
|
147
|
-
|
148
|
-
|
149
|
-
|
141
|
+
domain = entity_class.domain
|
142
|
+
|
143
|
+
base = domain.foobara_default_entity_base || default_base
|
144
|
+
|
145
|
+
if base
|
146
|
+
table = EntityBase::Table.new(entity_class_name, base)
|
147
|
+
base.register_table(table)
|
150
148
|
tables_for_entity_class_name[entity_class_name] = table
|
151
149
|
else
|
152
150
|
# :nocov:
|
@@ -156,11 +154,120 @@ module Foobara
|
|
156
154
|
end
|
157
155
|
end
|
158
156
|
|
159
|
-
def register_base(
|
160
|
-
|
157
|
+
def register_base(*args, name: nil, table_prefix: nil)
|
158
|
+
base = case args
|
159
|
+
in [EntityBase]
|
160
|
+
args.first
|
161
|
+
in [Class => crud_driver_class, *rest] if crud_driver_class < EntityAttributesCrudDriver
|
162
|
+
unless name
|
163
|
+
# :nocov:
|
164
|
+
raise ArgumentError, "Must provide name: when registering a base with a crud driver class"
|
165
|
+
# :nocov:
|
166
|
+
end
|
167
|
+
|
168
|
+
crud_args, opts = case rest
|
169
|
+
in [Hash]
|
170
|
+
# TODO: test this code path
|
171
|
+
# :nocov:
|
172
|
+
[[], rest]
|
173
|
+
# :nocov:
|
174
|
+
in [] | [Array] | [Array, Hash]
|
175
|
+
rest
|
176
|
+
end
|
177
|
+
|
178
|
+
crud_driver = crud_driver_class.new(*crud_args, **opts, table_prefix:)
|
179
|
+
EntityBase.new(name, entity_attributes_crud_driver: crud_driver)
|
180
|
+
end
|
181
|
+
|
182
|
+
bases_need_sorting!
|
161
183
|
bases[base.name] = base
|
162
184
|
end
|
163
185
|
|
186
|
+
def sort_bases(bases)
|
187
|
+
return bases.dup if bases.size <= 1
|
188
|
+
|
189
|
+
if bases_need_sorting?
|
190
|
+
sort_bases!
|
191
|
+
end
|
192
|
+
|
193
|
+
sorted_bases = self.bases.values & bases
|
194
|
+
|
195
|
+
missing = bases - sorted_bases
|
196
|
+
|
197
|
+
unless missing.empty?
|
198
|
+
# :nocov:
|
199
|
+
raise ArgumentError, "Missing bases: #{missing} are the not registered or something?"
|
200
|
+
# :nocov:
|
201
|
+
# sorted_bases = [*missing, *sorted_bases]
|
202
|
+
end
|
203
|
+
|
204
|
+
sorted_bases
|
205
|
+
end
|
206
|
+
|
207
|
+
def sort_transactions(transactions)
|
208
|
+
return transactions.dup if transactions.size <= 1
|
209
|
+
|
210
|
+
if bases_need_sorting?
|
211
|
+
sort_bases!
|
212
|
+
end
|
213
|
+
|
214
|
+
sorted_bases = bases.values & transactions.map(&:entity_base)
|
215
|
+
|
216
|
+
sorted_bases.map do |base|
|
217
|
+
transactions.find { |tx| tx.entity_base == base }
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# TODO: make this private
|
222
|
+
# TODO: add a callback so objects that are sensitive to this order can update when needed
|
223
|
+
def sort_bases!
|
224
|
+
return if bases.size <= 1
|
225
|
+
|
226
|
+
old_bases = bases.values
|
227
|
+
|
228
|
+
entity_classes = []
|
229
|
+
|
230
|
+
old_bases.each do |base|
|
231
|
+
entity_classes += base.entity_classes
|
232
|
+
end
|
233
|
+
|
234
|
+
return if entity_classes.size <= 1
|
235
|
+
|
236
|
+
entity_classes.select!(&:contains_associations?)
|
237
|
+
|
238
|
+
return if entity_classes.size <= 1
|
239
|
+
|
240
|
+
entity_classes = EntityBase.order_entity_classes(entity_classes)
|
241
|
+
|
242
|
+
new_bases = entity_classes.map(&:entity_base)
|
243
|
+
new_bases.reverse!
|
244
|
+
new_bases.uniq!
|
245
|
+
|
246
|
+
missing = old_bases - new_bases
|
247
|
+
|
248
|
+
unless missing.empty?
|
249
|
+
new_bases = [*new_bases, *missing]
|
250
|
+
end
|
251
|
+
|
252
|
+
@bases_need_sorting = false
|
253
|
+
|
254
|
+
self.last_table_count = table_count
|
255
|
+
|
256
|
+
@bases = new_bases.to_h do |base|
|
257
|
+
[base.name, base]
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def bases_need_sorting!
|
262
|
+
@bases_need_sorting = true
|
263
|
+
end
|
264
|
+
|
265
|
+
def bases_need_sorting?
|
266
|
+
return true if @bases_need_sorting
|
267
|
+
|
268
|
+
@bases_need_sorting = table_count != last_table_count
|
269
|
+
end
|
270
|
+
|
164
271
|
def register_entity(base, entity_class, table_name: entity_class.full_entity_name)
|
165
272
|
base = to_base(base)
|
166
273
|
|
@@ -171,6 +278,14 @@ module Foobara
|
|
171
278
|
def tables_for_entity_class_name
|
172
279
|
@tables_for_entity_class_name ||= {}
|
173
280
|
end
|
281
|
+
|
282
|
+
private
|
283
|
+
|
284
|
+
attr_accessor :last_table_count
|
285
|
+
|
286
|
+
def table_count
|
287
|
+
bases.values.map { |v| v.entity_attributes_crud_driver.tables.size }.sum
|
288
|
+
end
|
174
289
|
end
|
175
290
|
end
|
176
291
|
end
|
data/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foobara
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Miles Georgi
|
@@ -306,6 +306,7 @@ files:
|
|
306
306
|
- projects/entity/src/extensions/builtin_types/entity/casters/record_from_closed_transaction.rb
|
307
307
|
- projects/entity/src/extensions/builtin_types/entity/casters/record_from_current_transaction.rb
|
308
308
|
- projects/entity/src/extensions/builtin_types/entity/validators/model_instance_is_valid.rb
|
309
|
+
- projects/entity/src/extensions/domain/domain_module_extension.rb
|
309
310
|
- projects/entity/src/extensions/type_declarations/handlers/extend_entity_type_declaration.rb
|
310
311
|
- projects/entity/src/extensions/type_declarations/handlers/extend_entity_type_declaration/attributes_handler_desugarizer.rb
|
311
312
|
- projects/entity/src/extensions/type_declarations/handlers/extend_entity_type_declaration/hash_desugarizer.rb
|