foobara 0.0.131 → 0.0.133

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: c95217bebc01ab27c28cdf572b5e19eb2902fd68b1bb4d7328c093b02cfe8dbf
4
- data.tar.gz: 76d32046c22f96ebf9e73d738aa4154fea3edb740e66fab71954abbd8594a35c
3
+ metadata.gz: 267e71a64eccb434c34233c30b65e56d1c69109667c4ba806645324cf53d9581
4
+ data.tar.gz: b870b83c02edd0bb5e117a57f769c1adf6c73abbfae68660688d82cdc91b3b1a
5
5
  SHA512:
6
- metadata.gz: 148fe1548cc4731673ffa299fe1d5ef96a8377cdbc613e3aeff989b16efbc250beea9b58f1b3b1727d0b399b5f1925186c1ac97908c8e2bf6113f4ff0cba1835
7
- data.tar.gz: e49bad06d6f305b4e1716fd56fa47298028c6b699977e95417e9013c95f3a25a90ca8a9b2d96b8a11c9fb5989b56587ee87d7b719d2397b90d95210168a594ce
6
+ metadata.gz: f1226974494886a43bdcf51e00ec0061453d6307e5e031836824747f4b583775edf5b1529fad4280a1520c173e1d3a94253a5bf2885b996d5e7814d426eaac10
7
+ data.tar.gz: e44c33f5c39c9e32c01c5057ad1a747fa95e07bc47e7e5bdf57b896fdc78bfea18e9f2f09661dc6bfb47da325a452108942c99d7e9d789aae3e4b5c945e57aa3
data/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ # [0.0.133] - 2025-06-26
2
+
3
+ - Add StateMachine.states_that_can_perform
4
+ - Add TransformedCommand#raw_inputs
5
+ - Fix several TransactionTable#find_by* bugs that incorrectly match/don't match in-memory changed records
6
+ - Fix bug that looked for associations in generic :attributes types
7
+
8
+ # [0.0.132] - 2025-06-19
9
+
10
+ - Add/improve the description for primary key types in EntityToPrimaryKeyInputsTransformer
11
+ - Support model and tuple types in .has_associations?
12
+ - Move EntityToPrimaryKeyInputsTransformer over from foobara-agent gem
13
+ - Make sure EntitiesToPrimaryKeysSerializer recurses into models/detached entities
14
+ - Fix bugs impacting use of multiple inputs transformers at a time and other issues with typed transformers
15
+
1
16
  # [0.0.131] - 2025-06-16
2
17
 
3
18
  - Extract InMemoryMinimal crud driver specs to foobara-crud-driver-spec-helpers gem
@@ -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
- if inputs_transformers.empty?
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 = begin
451
- transformers = transformers_to_processors(inputs_transformers,
452
- command_class.inputs_type, direction: :to)
453
-
454
- if transformers.size == 1
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
@@ -792,6 +791,10 @@ module Foobara
792
791
  end
793
792
  end
794
793
 
794
+ def raw_inputs
795
+ untransformed_inputs
796
+ end
797
+
795
798
  def method_missing(method_name, ...)
796
799
  if command.respond_to?(method_name)
797
800
  command.send(method_name, ...)
@@ -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
- element_types = type.element_types
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.
@@ -243,11 +253,13 @@ module Foobara
243
253
  contains_associations?(element_type, false)
244
254
  end
245
255
  elsif type.extends?(BuiltinTypes[:attributes])
246
- type.element_types.values.any? do |element_type|
256
+
257
+ type.element_types&.values&.any? do |element_type|
247
258
  if !remove_sensitive || !element_type.sensitive?
248
259
  contains_associations?(element_type, false)
249
260
  end
250
261
  end
262
+
251
263
  elsif type.extends?(BuiltinTypes[:associative_array])
252
264
  element_types = type.element_types
253
265
 
@@ -57,7 +57,7 @@ module Foobara
57
57
 
58
58
  record = current_transaction_table.find_tracked(record_id)
59
59
 
60
- if record
60
+ if record&.loaded?
61
61
  # :nocov:
62
62
  raise "Already loaded for #{attributes}. Bug maybe?"
63
63
  # :nocov:
@@ -14,7 +14,7 @@ module Foobara
14
14
  end
15
15
 
16
16
  def applicable?(model_instance)
17
- !model_instance.skip_validations
17
+ model_instance && !model_instance.skip_validations
18
18
  end
19
19
 
20
20
  def process_value(model_instance)
@@ -130,8 +130,20 @@ module Foobara
130
130
  end
131
131
 
132
132
  def find_by_attribute_containing(attribute_name, value)
133
+ found_type = entity_class.attributes_type.type_at_path("#{attribute_name}.#")
134
+
135
+ if value
136
+ value = restore_attributes(value, found_type)
137
+ end
138
+
133
139
  all.find do |found_attributes|
134
- found_attributes[attribute_name]&.include?(value)
140
+ found_attributes[attribute_name].any? do |found_value|
141
+ if found_value
142
+ found_value = restore_attributes(found_value, found_type)
143
+ end
144
+
145
+ found_value == value
146
+ end
135
147
  end
136
148
  end
137
149
 
@@ -291,7 +303,10 @@ module Foobara
291
303
  if outcome.success?
292
304
  outcome.result
293
305
  else
306
+ # TODO: figure out how to test this code path
307
+ # :nocov:
294
308
  object
309
+ # :nocov:
295
310
  end
296
311
  end
297
312
  end
@@ -260,6 +260,7 @@ module Foobara
260
260
  value = entity_class.attributes_type.element_types[attribute_name].element_type.process_value!(value)
261
261
 
262
262
  tracked_records.each do |record|
263
+ next unless record.loaded? || record.created?
263
264
  next if hard_deleted?(record)
264
265
 
265
266
  # TODO: what if there are multiple??
@@ -274,7 +275,22 @@ module Foobara
274
275
  )
275
276
 
276
277
  if found_attributes
277
- entity_class.loaded(normalize_attributes(found_attributes))
278
+ found_attributes = normalize_attributes(found_attributes)
279
+ record_id = primary_key_for_attributes(found_attributes)
280
+
281
+ record = find_tracked(record_id)
282
+
283
+ if record
284
+ # was already considered among tracked records if loaded? is true so do not return it as
285
+ # it has changed and no longer matches
286
+ unless record.loaded?
287
+ loading(record) do
288
+ record.successfully_loaded(found_attributes)
289
+ end
290
+ end
291
+ else
292
+ entity_class.loaded(found_attributes)
293
+ end
278
294
  end
279
295
  end
280
296
 
@@ -286,6 +302,7 @@ module Foobara
286
302
  end
287
303
 
288
304
  tracked_records.each do |record|
305
+ next unless record.loaded? || record.created?
289
306
  next if hard_deleted?(record)
290
307
 
291
308
  if entity_attributes_crud_driver_table.matches_attributes_filter?(record.attributes, attributes_filter)
@@ -293,17 +310,37 @@ module Foobara
293
310
  end
294
311
  end
295
312
 
296
- found_attributes = entity_attributes_crud_driver_table.find_by(attributes_filter)
313
+ found_attributes_enumerator = entity_attributes_crud_driver_table.find_many_by(attributes_filter)
297
314
 
298
- if found_attributes
315
+ found_attributes_enumerator.each do |found_attributes|
299
316
  found_attributes = normalize_attributes(found_attributes)
300
317
  record_id = primary_key_for_attributes(found_attributes)
301
318
 
302
319
  record = find_tracked(record_id)
303
- return record if record
304
320
 
305
- entity_class.loaded(found_attributes)
321
+ if record
322
+ if record.loaded?
323
+ # was already considered among tracked records if loaded? is true so do not return it as
324
+ # it has changed and no longer matches
325
+ # TODO: figure out how to test this code path
326
+ # :nocov:
327
+ record = nil
328
+ # :nocov:
329
+ else
330
+ loading(record) do
331
+ record.successfully_loaded(found_attributes)
332
+ end
333
+ end
334
+ else
335
+ record = entity_class.loaded(found_attributes)
336
+ end
337
+
338
+ if record
339
+ return record
340
+ end
306
341
  end
342
+
343
+ nil
307
344
  end
308
345
 
309
346
  def find_many_by(attributes_filter)
@@ -353,9 +390,28 @@ module Foobara
353
390
 
354
391
  next if yielded_ids.include?(record_id)
355
392
 
356
- record = find_tracked(record_id) || entity_class.loaded(found_attributes)
393
+ record = find_tracked(record_id)
394
+
395
+ if record
396
+ if record.loaded?
397
+ # was already considered among tracked records if loaded? is true so do not return it as
398
+ # it has changed and no longer matches
399
+ # TODO: figure out how to test this code path
400
+ # :nocov:
401
+ record = nil
402
+ # :nocov:
403
+ else
404
+ loading(record) do
405
+ record.successfully_loaded(found_attributes)
406
+ end
407
+ end
408
+ else
409
+ record = entity_class.loaded(found_attributes)
410
+ end
357
411
 
358
- yielder << record
412
+ if record
413
+ yielder << record
414
+ end
359
415
  end
360
416
  end
361
417
  end
@@ -374,6 +430,7 @@ module Foobara
374
430
 
375
431
  Enumerator.new do |yielder|
376
432
  tracked_records.each do |record|
433
+ next unless record.loaded? || record.created?
377
434
  next if hard_deleted?(record)
378
435
 
379
436
  record_values = record.read_attribute(attribute_name)
@@ -401,9 +458,28 @@ module Foobara
401
458
 
402
459
  next if yielded_ids.include?(record_id)
403
460
 
404
- record = find_tracked(record_id) || entity_class.loaded(found_attributes)
461
+ record = find_tracked(record_id)
462
+
463
+ if record
464
+ # TODO: test this code path
465
+ # :nocov:
466
+ if record.loaded?
467
+ # was already considered among tracked records if loaded? is true so do not return it as
468
+ # it has changed and no longer matches
469
+ record = nil
470
+ else
471
+ loading(record) do
472
+ record.successfully_loaded(found_attributes)
473
+ end
474
+ end
475
+ # :nocov:
476
+ else
477
+ record = entity_class.loaded(found_attributes)
478
+ end
405
479
 
406
- yielder << record
480
+ if record
481
+ yielder << record
482
+ end
407
483
  end
408
484
  end
409
485
  end
@@ -422,6 +498,7 @@ module Foobara
422
498
 
423
499
  Enumerator.new do |yielder|
424
500
  tracked_records.each do |record|
501
+ next unless record.loaded? || record.created?
425
502
  next if hard_deleted?(record)
426
503
 
427
504
  record_value = record.read_attribute(attribute_name)
@@ -445,9 +522,28 @@ module Foobara
445
522
 
446
523
  next if yielded_ids.include?(record_id)
447
524
 
448
- record = find_tracked(record_id) || entity_class.loaded(found_attributes)
525
+ record = find_tracked(record_id)
526
+
527
+ if record
528
+ # TODO: test this code path
529
+ # :nocov:
530
+ if record.loaded?
531
+ # was already considered among tracked records if loaded? is true so do not return it as
532
+ # it has changed and no longer matches
533
+ record = nil
534
+ else
535
+ loading(record) do
536
+ record.successfully_loaded(found_attributes)
537
+ end
538
+ end
539
+ # :nocov:
540
+ else
541
+ record = entity_class.loaded(found_attributes)
542
+ end
449
543
 
450
- yielder << record
544
+ if record
545
+ yielder << record
546
+ end
451
547
  end
452
548
  end
453
549
  end
@@ -2,6 +2,7 @@ module Foobara
2
2
  require_project_file("state_machine", "sugar")
3
3
  require_project_file("state_machine", "callbacks")
4
4
  require_project_file("state_machine", "validations")
5
+ require_project_file("state_machine", "transitions")
5
6
 
6
7
  # TODO: allow quick creation of a statemachine either through better options to #initialize or a
7
8
  # .for method.
@@ -10,6 +11,7 @@ module Foobara
10
11
  include Callbacks
11
12
  include Validations
12
13
  include TransitionLog
14
+ include Transitions
13
15
 
14
16
  class InvalidTransition < StandardError; end
15
17
 
@@ -0,0 +1,22 @@
1
+ module Foobara
2
+ class StateMachine
3
+ module Transitions
4
+ include Concern
5
+
6
+ module ClassMethods
7
+ def states_that_can_perform(transition)
8
+ states = []
9
+ transition = transition.to_sym
10
+
11
+ transition_map.each_pair do |from, transitions|
12
+ if transitions.key?(transition)
13
+ states << from
14
+ end
15
+ end
16
+
17
+ states
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -22,6 +22,10 @@ module Foobara
22
22
  attr_reader :from_type, :to_type
23
23
  end
24
24
 
25
+ def always_applicable?
26
+ true
27
+ end
28
+
25
29
  def from_type_declaration
26
30
  nil
27
31
  end
@@ -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
 
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.131
4
+ version: 0.0.133
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
@@ -405,6 +406,7 @@ files:
405
406
  - projects/state_machine/src/state_machine.rb
406
407
  - projects/state_machine/src/sugar.rb
407
408
  - projects/state_machine/src/transition_log.rb
409
+ - projects/state_machine/src/transitions.rb
408
410
  - projects/state_machine/src/validations.rb
409
411
  - projects/type_declarations/lib/foobara/type_declarations.rb
410
412
  - projects/type_declarations/src/attributes.rb