foobara 0.1.7 → 0.1.8

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: c106dcffef98c13b230ca60f3433638583db9c1d6a9779995baa073cc1ddff00
4
- data.tar.gz: 4159835c178cf602fd7fdedd20d5ed030f37dfb5721b6806847f12dbaa7379cd
3
+ metadata.gz: 497323362ef9b9bcf4a7d17cb9400b84701e73dc464f3ff318e6a52b6ee3bbf6
4
+ data.tar.gz: ee7bc81ea47e850b90e569c4295bd914832c3604277790edd6db153e143515a6
5
5
  SHA512:
6
- metadata.gz: d352b2e66aa89e993a741da14506decbc13bcde49a71d23225349b409f8871aaf9216c4a6ec38e325adace318ef01a90b078b596bed15df39cc3422fd2b59dbf
7
- data.tar.gz: 3bf9fd820cddfaf432c704df09797ce100084507b450df4de8d2bcc4c0294ca77f5009ba51d750a7fb66161108e953a323fa7482ef35b56e6139e1acc8d90948
6
+ metadata.gz: acc9a5fed9bdfa6f7c19d74652b6c2bf770a24c17880968dc476289d2231ea51b906c538ee3d10dc65365c74d9d97f9f747ae3c97400789ce30e60babc9ca6b2
7
+ data.tar.gz: 03225ae2b838efc6816fe072b0f94ea6193552bb1253159aaa749ec471c18d4ea821c399cba711f6f6246cf1c38686d398bb4cd5c6385daebf91014a3ab88436
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ # [0.1.8] - 2025-08-25
2
+
3
+ - Memoize various parts of Type
4
+ - Eliminate DoesNotNeedCastIf* processors
5
+
1
6
  # [0.1.7] - 2025-08-25
2
7
 
3
8
  - Go back to using :detached_entity for entities that have had sensitive types removed to
@@ -3,15 +3,15 @@ module Foobara
3
3
  class Block
4
4
  module Concerns
5
5
  module BlockParameterNotAllowed
6
+ class BlockParameterNotAllowedError < StandardError; end
7
+
6
8
  private
7
9
 
8
10
  def validate_original_block!
9
11
  super
10
12
 
11
13
  if takes_block?
12
- # :nocov:
13
- raise ArgumentError, "#{type} callback is not allowed to accept a block"
14
- # :nocov:
14
+ raise BlockParameterNotAllowedError, "#{type} callback is not allowed to accept a block"
15
15
  end
16
16
  end
17
17
  end
@@ -6,6 +6,8 @@ module Foobara
6
6
  include Concern
7
7
 
8
8
  module ClassMethods
9
+ # TODO: consider renaming this to symbol? Could be confused with Foobara::Type concept
10
+ # Returns things like :before, :after, :around, :error to indicate what type of callback it is
9
11
  def type
10
12
  @type ||= Util.non_full_name_underscore(self)&.gsub(/_block$/, "")&.to_sym
11
13
  end
@@ -26,7 +26,9 @@ module Foobara
26
26
  validate_original_block!
27
27
  end
28
28
 
29
- foobara_delegate :type, to: :class
29
+ def type
30
+ self.class.type
31
+ end
30
32
 
31
33
  def call(...)
32
34
  to_proc.call(...)
@@ -48,7 +50,7 @@ module Foobara
48
50
  end
49
51
 
50
52
  def takes_block?
51
- @takes_block ||= original_block.parameters.last&.first&.==(:block)
53
+ @takes_block ||= original_block.parameters.last&.first&.==(:block) || false
52
54
  end
53
55
 
54
56
  def has_one_or_zero_positional_args?
@@ -0,0 +1,26 @@
1
+ require_relative "primary_key"
2
+
3
+ module Foobara
4
+ module BuiltinTypes
5
+ module Entity
6
+ module Casters
7
+ class RecordFromClosedTransaction < PrimaryKey
8
+ def applicable?(value)
9
+ if value.is_a?(entity_class) && value.persisted?
10
+ tx = value.class.entity_base.current_transaction
11
+
12
+ if tx&.currently_open?
13
+ # TODO: might be safer/more performant to store the transaction on the record?
14
+ !value.class.entity_base.current_transaction.tracking?(value)
15
+ end
16
+ end
17
+ end
18
+
19
+ def transform(record)
20
+ super(record.primary_key)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ require_relative "primary_key"
2
+
3
+ module Foobara
4
+ module BuiltinTypes
5
+ module Entity
6
+ module Casters
7
+ class RecordFromCurrentTransaction < PrimaryKey
8
+ def applicable?(value)
9
+ if value.is_a?(entity_class)
10
+ tx = entity_class.entity_base.current_transaction
11
+ !tx&.currently_open? || tx.tracking?(value)
12
+ end
13
+ end
14
+
15
+ def transform(record)
16
+ record
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -7,12 +7,11 @@ module Foobara
7
7
  super.tap do |outcome|
8
8
  if outcome.success?
9
9
  type = outcome.result
10
+ type.cast_even_if_instance_of_target_type = true
10
11
  entity_class = type.target_class
11
12
 
12
13
  unless entity_class.can_be_created_through_casting?
13
- type.casters = type.casters.reject do |caster|
14
- caster.is_a?(Foobara::BuiltinTypes::Entity::Casters::Hash)
15
- end
14
+ type.remove_caster_instances_of(Foobara::BuiltinTypes::Entity::Casters::Hash)
16
15
  end
17
16
  end
18
17
  end
@@ -15,20 +15,21 @@ module Foobara
15
15
  end
16
16
 
17
17
  attr_accessor :base_type,
18
- :casters,
19
- :transformers,
20
- :validators,
21
- :element_processors,
22
18
  :structure_count,
23
19
  :is_builtin,
24
20
  :name,
25
- :target_classes,
26
21
  :description,
27
22
  :sensitive,
28
- :sensitive_exposed,
29
- :processor_classes_requiring_type
23
+ :sensitive_exposed
30
24
 
31
- attr_reader :type_symbol
25
+ attr_reader :type_symbol,
26
+ :casters,
27
+ :transformers,
28
+ :validators,
29
+ :target_classes,
30
+ :processor_classes_requiring_type,
31
+ :element_processors,
32
+ :cast_even_if_instance_of_target_type
32
33
 
33
34
  attr_writer :element_types,
34
35
  :element_type
@@ -168,9 +169,34 @@ module Foobara
168
169
  category.delete_if { |p| p.symbol == symbol }
169
170
 
170
171
  category << processor
172
+ clear_caches
171
173
  end
172
174
  end
173
175
 
176
+ def clear_caches
177
+ [
178
+ :@value_validator,
179
+ :@processors,
180
+ :@value_caster,
181
+ :@value_transformer,
182
+ :@element_processor,
183
+ :@possible_errors,
184
+ :@processors_without_casters
185
+ ].each do |instance_variable|
186
+ if instance_variable_defined?(instance_variable)
187
+ remove_instance_variable(instance_variable)
188
+ end
189
+ end
190
+ end
191
+
192
+ def remove_caster_instances_of(klass)
193
+ self.casters = casters.reject do |caster|
194
+ caster.is_a?(klass)
195
+ end
196
+
197
+ clear_caches
198
+ end
199
+
174
200
  def remove_processor_by_symbol(symbol)
175
201
  [
176
202
  casters,
@@ -184,6 +210,7 @@ module Foobara
184
210
  end
185
211
  supported_processor_classes&.each { |processor_hash| processor_hash.delete(symbol) }
186
212
  processor_classes_requiring_type&.delete_if { |p| p.symbol == symbol }
213
+ clear_caches
187
214
  end
188
215
 
189
216
  def each_processor_class_requiring_type(&block)
@@ -218,6 +245,8 @@ module Foobara
218
245
  end
219
246
  end
220
247
  end
248
+
249
+ clear_caches
221
250
  end
222
251
 
223
252
  def target_class
@@ -299,11 +328,52 @@ module Foobara
299
328
  base_type&.extends_type?(type)
300
329
  end
301
330
 
331
+ def processors=(...)
332
+ clear_caches
333
+ super
334
+ end
335
+
302
336
  def type_symbol=(type_symbol)
303
337
  @scoped_path ||= type_symbol.to_s.split("::")
338
+ clear_caches
304
339
  @type_symbol = type_symbol.to_sym
305
340
  end
306
341
 
342
+ def cast_even_if_instance_of_target_type=(flag)
343
+ clear_caches
344
+ @cast_even_if_instance_of_target_type = flag
345
+ end
346
+
347
+ def casters=(processors)
348
+ clear_caches
349
+ @casters = processors
350
+ end
351
+
352
+ def transformers=(processors)
353
+ clear_caches
354
+ @transformers = processors
355
+ end
356
+
357
+ def validators=(processors)
358
+ clear_caches
359
+ @validators = processors
360
+ end
361
+
362
+ def target_classes=(processors)
363
+ clear_caches
364
+ @target_classes = processors
365
+ end
366
+
367
+ def processor_classes_requiring_type=(processors)
368
+ clear_caches
369
+ @processor_classes_requiring_type = processors
370
+ end
371
+
372
+ def element_processors=(processors)
373
+ clear_caches
374
+ @element_processors = processors
375
+ end
376
+
307
377
  def full_type_symbol
308
378
  return @full_type_symbol if defined?(@full_type_symbol)
309
379
 
@@ -313,17 +383,16 @@ module Foobara
313
383
  end
314
384
 
315
385
  def processors
316
- [
386
+ @processors ||= [
317
387
  value_caster,
318
388
  value_transformer,
319
389
  value_validator,
320
390
  element_processor
321
- ].compact
391
+ ].compact.sort_by(&:priority)
322
392
  end
323
393
 
324
394
  def value_caster
325
- # TODO: figure out what would be needed to successfully memoize this
326
- # return @value_caster if defined?(@value_caster)
395
+ return @value_caster if defined?(@value_caster)
327
396
 
328
397
  # We make this exception for :duck because it will match any instance of
329
398
  # Object but AllowNil will match nil which is also an instance of Object.
@@ -335,16 +404,19 @@ module Foobara
335
404
  true
336
405
  end
337
406
 
338
- Value::Processor::Casting.new(
339
- { cast_to: reference_or_declaration_data },
340
- casters:,
341
- target_classes:,
342
- enforce_unique:
343
- )
407
+ Namespace.use created_in_namespace do
408
+ @value_caster = Value::Processor::Casting.new(
409
+ { cast_to: reference_or_declaration_data },
410
+ casters:,
411
+ target_classes:,
412
+ enforce_unique:,
413
+ cast_even_if_instance_of_target_type:
414
+ )
415
+ end
344
416
  end
345
417
 
346
418
  def applicable?(value)
347
- value_caster.can_cast?(value)
419
+ !value_caster.needs_cast?(value) || value_caster.can_cast?(value)
348
420
  end
349
421
 
350
422
  foobara_delegate :needs_cast?, to: :value_caster
@@ -362,24 +434,30 @@ module Foobara
362
434
  # method in the instance of the processor as needed. This means it can't really memoize stuff. Should we create
363
435
  # an instance of something from the instance of the processor and then ask it questions?? TODO: try this
364
436
  def value_transformer
365
- if transformers && !transformers.empty?
366
- Value::Processor::Pipeline.new(processors: transformers)
367
- end
437
+ return @value_transformer if defined?(@value_transformer)
438
+
439
+ @value_transformer = if transformers && !transformers.empty?
440
+ Value::Processor::Pipeline.new(processors: transformers)
441
+ end
368
442
  end
369
443
 
370
444
  # TODO: figure out how to safely memoize stuff so like this for performance reasons
371
445
  # A good way, but potentially a decent amount of work, is to have a class that takes value to its initialize
372
446
  # method.
373
447
  def value_validator
374
- if validators && !validators.empty?
375
- Value::Processor::Pipeline.new(processors: validators)
376
- end
448
+ return @value_validator if defined?(@value_validator)
449
+
450
+ @value_validator = if validators && !validators.empty?
451
+ Value::Processor::Pipeline.new(processors: validators)
452
+ end
377
453
  end
378
454
 
379
455
  def element_processor
380
- if element_processors && !element_processors.empty?
381
- Value::Processor::Pipeline.new(processors: element_processors)
382
- end
456
+ return @element_processor if defined?(@element_processor)
457
+
458
+ @element_processor = if element_processors && !element_processors.empty?
459
+ Value::Processor::Pipeline.new(processors: element_processors)
460
+ end
383
461
  end
384
462
 
385
463
  # TODO: some way of memoizing these values? Would need to introduce a new class that takes the value to its
@@ -37,57 +37,34 @@ module Foobara
37
37
  end
38
38
  end
39
39
 
40
- attr_accessor :target_classes
40
+ attr_accessor :target_classes, :cast_even_if_instance_of_target_type
41
41
 
42
- def initialize(*, casters:, target_classes: nil, **)
42
+ def initialize(*, casters:, target_classes: nil, cast_even_if_instance_of_target_type: nil, **)
43
43
  self.target_classes = Util.array(target_classes)
44
44
 
45
- processors = [
46
- *does_not_need_cast_processor,
47
- *casters
48
- ]
45
+ if cast_even_if_instance_of_target_type
46
+ self.cast_even_if_instance_of_target_type = true
47
+ end
48
+
49
+ super(*, processors: casters, **)
50
+ end
49
51
 
50
- super(*, processors:, **)
52
+ def process_value(value)
53
+ if cast_even_if_instance_of_target_type || needs_cast?(value)
54
+ super
55
+ else
56
+ Outcome.success(value)
57
+ end
51
58
  end
52
59
 
53
60
  def needs_cast?(value)
54
- !does_not_need_cast_processor.applicable?(value)
61
+ target_classes.none? { |klass| value.is_a?(klass) }
55
62
  end
56
63
 
57
64
  def can_cast?(value)
58
65
  processors.any? { |processor| processor.applicable?(value) }
59
66
  end
60
67
 
61
- def does_not_need_cast_processor
62
- return @does_not_need_cast_processor if defined?(@does_not_need_cast_processor)
63
-
64
- errorified_name = target_classes.map do |c|
65
- if c.name
66
- c.name
67
- elsif c.respond_to?(:foobara_name)
68
- c.foobara_name
69
- else
70
- # TODO: test this code path
71
- # :nocov:
72
- "Anon"
73
- # :nocov:
74
- end
75
- end.map { |name| name.split("::").last }.sort.join("Or")
76
-
77
- class_name = "NoCastNeededIfIsA#{errorified_name}"
78
-
79
- @does_not_need_cast_processor = if target_classes && !target_classes.empty?
80
- Caster.subclass(
81
- name: class_name,
82
- applicable?: ->(value) {
83
- target_classes.any? { |target_class| value.is_a?(target_class) }
84
- },
85
- applies_message: "be a #{target_classes.map(&:name).join(" or ")}",
86
- cast: ->(value) { value }
87
- ).instance
88
- end
89
- end
90
-
91
68
  def error_message(value)
92
69
  type = declaration_data[:cast_to]
93
70
 
@@ -101,7 +78,12 @@ module Foobara
101
78
  end
102
79
 
103
80
  def applies_message
104
- Util.to_or_sentence(processors.map(&:applies_message).flatten)
81
+ Util.to_or_sentence(
82
+ [
83
+ "be a #{target_classes.map(&:name).join(" or ")}",
84
+ *processors.map(&:applies_message).flatten
85
+ ]
86
+ )
105
87
  end
106
88
 
107
89
  def error_context(value)
data/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Foobara
2
2
  module Version
3
- VERSION = "0.1.7".freeze
3
+ VERSION = "0.1.8".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.7
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miles Georgi
@@ -304,6 +304,8 @@ files:
304
304
  - projects/entity/src/extensions/builtin_types/entity.rb
305
305
  - projects/entity/src/extensions/builtin_types/entity/casters/hash.rb
306
306
  - projects/entity/src/extensions/builtin_types/entity/casters/primary_key.rb
307
+ - projects/entity/src/extensions/builtin_types/entity/casters/record_from_closed_transaction.rb
308
+ - projects/entity/src/extensions/builtin_types/entity/casters/record_from_current_transaction.rb
307
309
  - projects/entity/src/extensions/builtin_types/entity/validators/model_instance_is_valid.rb
308
310
  - projects/entity/src/extensions/type_declarations/handlers/extend_entity_type_declaration.rb
309
311
  - projects/entity/src/extensions/type_declarations/handlers/extend_entity_type_declaration/attributes_handler_desugarizer.rb