foobara 0.1.6 → 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 +4 -4
- data/CHANGELOG.md +11 -0
- data/projects/callback/src/block/concerns/block_parameter_not_allowed.rb +3 -3
- data/projects/callback/src/block/concerns/type.rb +2 -0
- data/projects/callback/src/block.rb +4 -2
- data/projects/entity/src/extensions/builtin_types/entity/casters/record_from_closed_transaction.rb +26 -0
- data/projects/entity/src/extensions/builtin_types/entity/casters/record_from_current_transaction.rb +22 -0
- data/projects/entity/src/extensions/type_declarations/handlers/extend_entity_type_declaration/to_type_transformer.rb +2 -3
- data/projects/entity/src/sensitive_type_removers/entity.rb +6 -7
- data/projects/entity/src/sensitive_value_removers/entity.rb +9 -2
- data/projects/types/src/type.rb +112 -28
- data/projects/value/src/processor/casting.rb +21 -39
- data/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 497323362ef9b9bcf4a7d17cb9400b84701e73dc464f3ff318e6a52b6ee3bbf6
|
4
|
+
data.tar.gz: ee7bc81ea47e850b90e569c4295bd914832c3604277790edd6db153e143515a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: acc9a5fed9bdfa6f7c19d74652b6c2bf770a24c17880968dc476289d2231ea51b906c538ee3d10dc65365c74d9d97f9f747ae3c97400789ce30e60babc9ca6b2
|
7
|
+
data.tar.gz: 03225ae2b838efc6816fe072b0f94ea6193552bb1253159aaa749ec471c18d4ea821c399cba711f6f6246cf1c38686d398bb4cd5c6385daebf91014a3ab88436
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
# [0.1.8] - 2025-08-25
|
2
|
+
|
3
|
+
- Memoize various parts of Type
|
4
|
+
- Eliminate DoesNotNeedCastIf* processors
|
5
|
+
|
6
|
+
# [0.1.7] - 2025-08-25
|
7
|
+
|
8
|
+
- Go back to using :detached_entity for entities that have had sensitive types removed to
|
9
|
+
avoid casting problems with multiple entities with the same name
|
10
|
+
- Remove private attributes as if they were sensitive attributes when exposing models
|
11
|
+
|
1
12
|
# [0.1.6] - 2025-08-25
|
2
13
|
|
3
14
|
- Allow constructing a thunk-like record when removing sensitive values from a thunk
|
@@ -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
|
-
#
|
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
|
-
|
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?
|
data/projects/entity/src/extensions/builtin_types/entity/casters/record_from_closed_transaction.rb
ADDED
@@ -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
|
data/projects/entity/src/extensions/builtin_types/entity/casters/record_from_current_transaction.rb
ADDED
@@ -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.
|
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
|
@@ -7,14 +7,13 @@ module Foobara
|
|
7
7
|
|
8
8
|
if strict_type_declaration != new_type_declaration
|
9
9
|
if new_type_declaration[:type] == :entity
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
# It's important that we don't create another entity with different attributes
|
11
|
+
# or various things like crud drivers or type transformers can become confused.
|
12
|
+
# So we will create it as a detached_entity instead.
|
13
|
+
new_type_declaration[:type] = :detached_entity
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
end
|
15
|
+
if new_type_declaration[:model_base_class] == "Foobara::Entity"
|
16
|
+
new_type_declaration[:model_base_class] = "Foobara::DetachedEntity"
|
18
17
|
end
|
19
18
|
end
|
20
19
|
end
|
@@ -8,7 +8,7 @@ module Foobara
|
|
8
8
|
elsif record.persisted?
|
9
9
|
# We will assume that we do not need to clean up the primary key itself as
|
10
10
|
# we will assume we don't allow sensitive primary keys for now.
|
11
|
-
thunkish = to_type.target_class.
|
11
|
+
thunkish = to_type.target_class.send(build_method, record.class.primary_key_attribute => record.primary_key)
|
12
12
|
thunkish.skip_validations = true
|
13
13
|
thunkish.mutable = false
|
14
14
|
thunkish
|
@@ -20,7 +20,14 @@ module Foobara
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def build_method
|
23
|
-
:
|
23
|
+
if to_type.extends?(BuiltinTypes[:entity])
|
24
|
+
# TODO: figure out a way to test this path
|
25
|
+
# :nocov:
|
26
|
+
:build
|
27
|
+
# :nocov:
|
28
|
+
else
|
29
|
+
:new
|
30
|
+
end
|
24
31
|
end
|
25
32
|
end
|
26
33
|
end
|
data/projects/types/src/type.rb
CHANGED
@@ -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
|
@@ -116,6 +117,12 @@ module Foobara
|
|
116
117
|
def has_sensitive_types?
|
117
118
|
return true if sensitive?
|
118
119
|
|
120
|
+
# TODO: this is a hack... come up with a better/separate way to detect types with private attributes
|
121
|
+
if declaration_data.is_a?(::Hash)
|
122
|
+
private = declaration_data[:private]
|
123
|
+
return true if private.is_a?(::Array) && !private.empty?
|
124
|
+
end
|
125
|
+
|
119
126
|
if element_type
|
120
127
|
return true if element_type.has_sensitive_types?
|
121
128
|
end
|
@@ -162,9 +169,34 @@ module Foobara
|
|
162
169
|
category.delete_if { |p| p.symbol == symbol }
|
163
170
|
|
164
171
|
category << processor
|
172
|
+
clear_caches
|
173
|
+
end
|
174
|
+
end
|
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
|
165
189
|
end
|
166
190
|
end
|
167
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
|
+
|
168
200
|
def remove_processor_by_symbol(symbol)
|
169
201
|
[
|
170
202
|
casters,
|
@@ -178,6 +210,7 @@ module Foobara
|
|
178
210
|
end
|
179
211
|
supported_processor_classes&.each { |processor_hash| processor_hash.delete(symbol) }
|
180
212
|
processor_classes_requiring_type&.delete_if { |p| p.symbol == symbol }
|
213
|
+
clear_caches
|
181
214
|
end
|
182
215
|
|
183
216
|
def each_processor_class_requiring_type(&block)
|
@@ -212,6 +245,8 @@ module Foobara
|
|
212
245
|
end
|
213
246
|
end
|
214
247
|
end
|
248
|
+
|
249
|
+
clear_caches
|
215
250
|
end
|
216
251
|
|
217
252
|
def target_class
|
@@ -293,11 +328,52 @@ module Foobara
|
|
293
328
|
base_type&.extends_type?(type)
|
294
329
|
end
|
295
330
|
|
331
|
+
def processors=(...)
|
332
|
+
clear_caches
|
333
|
+
super
|
334
|
+
end
|
335
|
+
|
296
336
|
def type_symbol=(type_symbol)
|
297
337
|
@scoped_path ||= type_symbol.to_s.split("::")
|
338
|
+
clear_caches
|
298
339
|
@type_symbol = type_symbol.to_sym
|
299
340
|
end
|
300
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
|
+
|
301
377
|
def full_type_symbol
|
302
378
|
return @full_type_symbol if defined?(@full_type_symbol)
|
303
379
|
|
@@ -307,17 +383,16 @@ module Foobara
|
|
307
383
|
end
|
308
384
|
|
309
385
|
def processors
|
310
|
-
[
|
386
|
+
@processors ||= [
|
311
387
|
value_caster,
|
312
388
|
value_transformer,
|
313
389
|
value_validator,
|
314
390
|
element_processor
|
315
|
-
].compact
|
391
|
+
].compact.sort_by(&:priority)
|
316
392
|
end
|
317
393
|
|
318
394
|
def value_caster
|
319
|
-
|
320
|
-
# return @value_caster if defined?(@value_caster)
|
395
|
+
return @value_caster if defined?(@value_caster)
|
321
396
|
|
322
397
|
# We make this exception for :duck because it will match any instance of
|
323
398
|
# Object but AllowNil will match nil which is also an instance of Object.
|
@@ -329,16 +404,19 @@ module Foobara
|
|
329
404
|
true
|
330
405
|
end
|
331
406
|
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
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
|
338
416
|
end
|
339
417
|
|
340
418
|
def applicable?(value)
|
341
|
-
value_caster.can_cast?(value)
|
419
|
+
!value_caster.needs_cast?(value) || value_caster.can_cast?(value)
|
342
420
|
end
|
343
421
|
|
344
422
|
foobara_delegate :needs_cast?, to: :value_caster
|
@@ -356,24 +434,30 @@ module Foobara
|
|
356
434
|
# method in the instance of the processor as needed. This means it can't really memoize stuff. Should we create
|
357
435
|
# an instance of something from the instance of the processor and then ask it questions?? TODO: try this
|
358
436
|
def value_transformer
|
359
|
-
|
360
|
-
|
361
|
-
|
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
|
362
442
|
end
|
363
443
|
|
364
444
|
# TODO: figure out how to safely memoize stuff so like this for performance reasons
|
365
445
|
# A good way, but potentially a decent amount of work, is to have a class that takes value to its initialize
|
366
446
|
# method.
|
367
447
|
def value_validator
|
368
|
-
|
369
|
-
|
370
|
-
|
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
|
371
453
|
end
|
372
454
|
|
373
455
|
def element_processor
|
374
|
-
|
375
|
-
|
376
|
-
|
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
|
377
461
|
end
|
378
462
|
|
379
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
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
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
|
-
|
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(
|
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
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.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
|