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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b5f241d83e9591db56a0c1021eb2544bf701ba2ddb0c0a47f257fa6797bafbf
4
- data.tar.gz: 98f0be44ed8abf5b4af0ef24da0e9c4e095956b24038726dabf369ad2051860a
3
+ metadata.gz: d6975f14259f6fa733c19dc1877d1868ef90b2f72a6408ba49cf1d0706ca69fe
4
+ data.tar.gz: ab1c0f032c8f653c2b6f5d2b24aa1eded6a5cec1ec6994ee905e90d87edcb157
5
5
  SHA512:
6
- metadata.gz: '08de40d305155c09dab3a0e513fa53bc06e11cd7c848a21104ff5dae158d4b09d13957ce1c697fec98291d98e9ef1db4b88e497d04c60664010edbf3ca0a8180'
7
- data.tar.gz: 7b781e2547614126578f134adf0aab7465a0ecc79fa27b1cd34fbe54716019ec740327f94044861478d13c5e2bcf39fa0ebcb68a736a2b56ec0aa636364bbdbd
6
+ metadata.gz: ff85faf59d67f5d1cfeab25f3520633df1747025aa760428af91c0b6e100893ff722f9a5dcac86246009842a0b2a0930ae18be26baabedaba42b46aa94f36472
7
+ data.tar.gz: 13ca3101da2f61f2d181a10a751e5e299722195cfdf1057a4216cf4bbf592728a2ec0cbbbfdc315827e74b6d579ba6e57fc5d31bdd7ab84323c48cffd6ba238b
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.4.6
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
@@ -11,7 +11,7 @@ module Foobara
11
11
 
12
12
  module ClassMethods
13
13
  def entity_base
14
- @entity_base ||= Foobara::Persistence.base_for_entity_class_name(full_entity_name)
14
+ @entity_base ||= Foobara::Persistence.base_for_entity_class(self)
15
15
  end
16
16
  end
17
17
 
@@ -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
- relevant_entity_classes = relevant_entity_classes_for_type(type)
22
+ entity_classes = relevant_entity_classes_for_type(type)
23
23
 
24
- if relevant_entity_classes.empty?
24
+ if entity_classes.empty?
25
25
  return yield
26
26
  end
27
27
 
28
- tx_class = Class.new
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
- classes = relevant_entity_classes
69
- return if classes.nil? || classes.empty?
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
- bases = classes.map(&:entity_base).uniq
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 = relevant_entity_classes.map(&:entity_base).uniq - bases_not_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
- transaction = entity_base.transaction
91
- transaction.open!
92
- opened_transactions << transaction
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] ||= self.class::Table.new(entity_class, self)
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 = Util.underscore(entity_class.entity_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?
@@ -1,6 +1,7 @@
1
1
  module Foobara
2
2
  module Persistence
3
3
  class EntityBase
4
+ # TODO: this class seems useless and confusing since we have two other Table classes
4
5
  class Table
5
6
  attr_accessor :table_name, :entity_base
6
7
 
@@ -57,7 +57,6 @@ module Foobara
57
57
  entity_class = entity_class.class
58
58
  end
59
59
 
60
- # TODO: so much passing self around...
61
60
  unless entity_base == entity_class.entity_base
62
61
  # :nocov:
63
62
  raise "#{entity_class} is from a different entity base! Cannot proceed."
@@ -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, &block)
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, &block)
41
+ bases.first.transaction(mode, &)
43
42
  else
44
- bases.inject(block) do |nested_proc, base|
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
- if objects.size > 1 && objects.all? { |o| o.is_a?(::Class) && o < Entity }
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
- table_for_entity_class_name(entity_class.full_entity_name).entity_base
132
+ table_for_entity_class(entity_class).entity_base
140
133
  end
141
134
 
142
- def table_for_entity_class_name(entity_class_name)
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
- if default_base
148
- table = EntityBase::Table.new(entity_class_name, default_base)
149
- default_base.register_table(table)
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(base)
160
- # TODO: add some validations here
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
@@ -1,6 +1,6 @@
1
1
  module Foobara
2
2
  module Version
3
- VERSION = "0.1.16".freeze
3
+ VERSION = "0.2.1".freeze
4
4
  MINIMUM_RUBY_VERSION = ">= 3.4.0".freeze
5
5
  end
6
6
  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.1.16
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