Class: Foobara::Types::Type

Inherits:
Value::Processor::Pipeline show all
Includes:
IsManifestable, Concerns::Reflection, Concerns::SupportedProcessorRegistration
Defined in:
foobara-0.0.130/projects/types/src/type.rb,
foobara-0.0.130/projects/types/src/type/concerns/reflection.rb,
foobara-0.0.130/projects/types/src/type/concerns/supported_processor_registration.rb

Overview

TODO: move casting interface to here?

Direct Known Subclasses

DetachedEntityType

Defined Under Namespace

Modules: Concerns

Instance Attribute Summary collapse

Attributes inherited from Value::Processor::Multi

#prioritize

Attributes inherited from Value::Processor

#created_in_namespace, #declaration_data, #parent_declaration_data

Class Method Summary collapse

Instance Method Summary collapse

Methods included from IsManifestable

#foobara_domain, #foobara_organization, #scoped_clear_caches

Methods included from Concerns::Reflection

#deep_types_depended_on, #inspect, #type_at_path, #types_depended_on, #types_to_add_to_manifest

Methods included from Concern

foobara_class_methods_module_for, foobara_concern?, included

Methods included from Concerns::SupportedProcessorRegistration

#all_supported_processor_classes, #find_supported_processor_class, #register_supported_processor_class, #supported_processor_classes

Methods inherited from Value::Processor::Pipeline

foobara_manifest, #process_outcome, #process_value

Methods inherited from Value::Processor::Multi

#error_classes, #possible_errors, #processor_names, #register

Methods inherited from Value::Processor

#always_applicable?, #attribute_name, #build_error, default_declaration_data, #dup_processor, error_class, error_classes, #error_context, #error_message, #error_path, foobara_manifest, #inspect, instance, #method_missing, new_with_agnostic_args, #possible_errors, #priority, #process_outcome, #process_outcome!, #process_value, #process_value!, processor_name, requires_parent_declaration_data?, #respond_to_missing?, #runner, symbol

Constructor Details

#initialize(declaration_data, target_classes:, base_type:, description: nil, name: "anonymous", casters: [], transformers: [], validators: [], element_processors: nil, element_type: nil, element_types: nil, structure_count: nil, processor_classes_requiring_type: nil, sensitive: nil, sensitive_exposed: nil, **opts) ⇒ Type

Returns a new instance of Type.



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'foobara-0.0.130/projects/types/src/type.rb', line 37

def initialize(
  declaration_data,
  target_classes:,
  base_type:,
  description: nil,
  name: "anonymous",
  casters: [],
  transformers: [],
  validators: [],
  element_processors: nil,
  element_type: nil,
  element_types: nil,
  structure_count: nil,
  processor_classes_requiring_type: nil,
  sensitive: nil,
  sensitive_exposed: nil,
  **opts
)
  self.declaration_data = declaration_data
  self.sensitive = sensitive
  self.sensitive_exposed = sensitive_exposed
  self.base_type = base_type
  self.description = description
  self.name = name
  self.casters = [*casters, *base_type&.casters]
  self.transformers = [*transformers, *base_type&.transformers]
  self.validators = [*validators, *base_type&.validators]
  self.element_processors = [*element_processors, *base_type&.element_processors]

  self.structure_count = structure_count
  # TODO: combine these maybe with the term "children_types"?
  self.element_types = element_types
  self.element_type = element_type
  self.target_classes = Util.array(target_classes)
  self.processor_classes_requiring_type = processor_classes_requiring_type

  super(declaration_data, **opts.merge(processors:, prioritize: false))

  apply_all_processors_needing_type!

  validate_processors!
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Foobara::Value::Processor

Instance Attribute Details

#base_typeObject

Returns the value of attribute base_type.



17
18
19
# File 'foobara-0.0.130/projects/types/src/type.rb', line 17

def base_type
  @base_type
end

#castersObject

Returns the value of attribute casters.



17
18
19
# File 'foobara-0.0.130/projects/types/src/type.rb', line 17

def casters
  @casters
end

#descriptionObject

Returns the value of attribute description.



17
18
19
# File 'foobara-0.0.130/projects/types/src/type.rb', line 17

def description
  @description
end

#element_processorsObject

Returns the value of attribute element_processors.



17
18
19
# File 'foobara-0.0.130/projects/types/src/type.rb', line 17

def element_processors
  @element_processors
end

#element_typeObject



88
89
90
# File 'foobara-0.0.130/projects/types/src/type.rb', line 88

def element_type
  @element_type || base_type&.element_type
end

#element_typesObject



92
93
94
# File 'foobara-0.0.130/projects/types/src/type.rb', line 92

def element_types
  @element_types || base_type&.element_types
end

#is_builtinObject

Returns the value of attribute is_builtin.



17
18
19
# File 'foobara-0.0.130/projects/types/src/type.rb', line 17

def is_builtin
  @is_builtin
end

#nameObject

Returns the value of attribute name.



17
18
19
# File 'foobara-0.0.130/projects/types/src/type.rb', line 17

def name
  @name
end

#processor_classes_requiring_typeObject

Returns the value of attribute processor_classes_requiring_type.



17
18
19
# File 'foobara-0.0.130/projects/types/src/type.rb', line 17

def processor_classes_requiring_type
  @processor_classes_requiring_type
end

#raw_declaration_dataObject

Returns the value of attribute raw_declaration_data.



17
18
19
# File 'foobara-0.0.130/projects/types/src/type.rb', line 17

def raw_declaration_data
  @raw_declaration_data
end

#sensitiveObject

Returns the value of attribute sensitive.



17
18
19
# File 'foobara-0.0.130/projects/types/src/type.rb', line 17

def sensitive
  @sensitive
end

#sensitive_exposedObject

Returns the value of attribute sensitive_exposed.



17
18
19
# File 'foobara-0.0.130/projects/types/src/type.rb', line 17

def sensitive_exposed
  @sensitive_exposed
end

#structure_countObject

Returns the value of attribute structure_count.



17
18
19
# File 'foobara-0.0.130/projects/types/src/type.rb', line 17

def structure_count
  @structure_count
end

#target_classesObject

Returns the value of attribute target_classes.



17
18
19
# File 'foobara-0.0.130/projects/types/src/type.rb', line 17

def target_classes
  @target_classes
end

#transformersObject

Returns the value of attribute transformers.



17
18
19
# File 'foobara-0.0.130/projects/types/src/type.rb', line 17

def transformers
  @transformers
end

#type_symbolObject

Returns the value of attribute type_symbol.



32
33
34
# File 'foobara-0.0.130/projects/types/src/type.rb', line 32

def type_symbol
  @type_symbol
end

#validatorsObject

Returns the value of attribute validators.



17
18
19
# File 'foobara-0.0.130/projects/types/src/type.rb', line 17

def validators
  @validators
end

Class Method Details

.requires_declaration_data?Boolean

Returns:

  • (Boolean)


12
13
14
# File 'foobara-0.0.130/projects/types/src/type.rb', line 12

def requires_declaration_data?
  true
end

Instance Method Details

#applicable?(value) ⇒ Boolean

Returns:

  • (Boolean)


299
300
301
# File 'foobara-0.0.130/projects/types/src/type.rb', line 299

def applicable?(value)
  value_caster.can_cast?(value)
end

#apply_all_processors_needing_type!Object



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'foobara-0.0.130/projects/types/src/type.rb', line 114

def apply_all_processors_needing_type!
  each_processor_class_requiring_type do |processor_class|
    # TODO: is this a smell?
    processor = processor_class.new(self)

    category = case processor
               when Value::Caster
                 casters
               when Value::Validator
                 # :nocov:
                 validators
                 # :nocov:
               when Value::Transformer
                 # :nocov:
                 transformers
                 # :nocov:
               when Types::ElementProcessor
                 # :nocov:
                 element_processors
                 # :nocov:
               else
                 # TODO: add validator that these are all fine so we don't have to bother here...
                 # :nocov:
                 raise "Not sure where to put #{processor}"
                 # :nocov:
               end

    symbol = processor.symbol
    category.delete_if { |p| p.symbol == symbol }

    category << processor
  end
end

#base_type_for_manifestObject



436
437
438
# File 'foobara-0.0.130/projects/types/src/type.rb', line 436

def base_type_for_manifest
  base_type
end

#builtin?Boolean

Returns:

  • (Boolean)


432
433
434
# File 'foobara-0.0.130/projects/types/src/type.rb', line 432

def builtin?
  is_builtin
end

#cast(value) ⇒ Object



305
306
307
# File 'foobara-0.0.130/projects/types/src/type.rb', line 305

def cast(value)
  value_caster.process_value(value)
end

#cast!(value) ⇒ Object



309
310
311
# File 'foobara-0.0.130/projects/types/src/type.rb', line 309

def cast!(value)
  value_caster.process_value!(value)
end

#each_processor_class_requiring_type(&block) ⇒ Object



163
164
165
166
167
168
169
# File 'foobara-0.0.130/projects/types/src/type.rb', line 163

def each_processor_class_requiring_type(&block)
  base_type&.each_processor_class_requiring_type(&block)

  processor_classes_requiring_type&.each do |processor_class|
    block.call(processor_class)
  end
end

#element_processorObject



332
333
334
335
336
# File 'foobara-0.0.130/projects/types/src/type.rb', line 332

def element_processor
  if element_processors && !element_processors.empty?
    Value::Processor::Pipeline.new(processors: element_processors)
  end
end

#extends?(type) ⇒ Boolean

Returns:

  • (Boolean)


215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'foobara-0.0.130/projects/types/src/type.rb', line 215

def extends?(type)
  case type
  when Type
    extends_type?(type)
  when Symbol, String
    concrete_type = created_in_namespace.foobara_lookup_type(type)
    if concrete_type.nil?
      # :nocov:
      raise "No type found for #{type}"
      # :nocov:
    end

    extends_type?(concrete_type)
  else
    # :nocov:
    raise ArgumentError, "Expected a Type or a Symbol/String, but got #{type.inspect}"
    # :nocov:
  end
end

#extends_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'foobara-0.0.130/projects/types/src/type.rb', line 235

def extends_type?(type)
  return true if self == type

  unless type
    # :nocov:
    raise ArgumentError, "Expected a type but got nil"
    # :nocov:
  end

  if registered?
    if type.registered?
      if type.foobara_manifest_reference == foobara_manifest_reference
        return true
      end
    end
  end

  base_type&.extends_type?(type)
end

#foobara_manifestObject



375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# File 'foobara-0.0.130/projects/types/src/type.rb', line 375

def foobara_manifest
  to_include = TypeDeclarations.foobara_manifest_context_to_include
  remove_sensitive = TypeDeclarations.foobara_manifest_context_remove_sensitive?

  types = []

  types_depended_on.each do |dependent_type|
    if dependent_type.registered?
      types << dependent_type.foobara_manifest_reference
      if to_include
        to_include << dependent_type
      end
    end
  end

  possible_errors_manifests = possible_errors.map do |possible_error|
    [possible_error.key.to_s, possible_error.foobara_manifest]
  end.sort.to_h

  declaration_data = self.declaration_data

  if remove_sensitive
    declaration_data = TypeDeclarations.remove_sensitive_types(declaration_data)
  end

  h = Util.remove_blank(
    name:,
    target_classes: target_classes.map(&:name).sort,
    declaration_data:,
    types_depended_on: types.sort,
    possible_errors: possible_errors_manifests,
    builtin: builtin?
  ).merge(description:, base_type: base_type_for_manifest&.full_type_name&.to_sym)

  if sensitive?
    h[:sensitive] = true
  end

  if sensitive_exposed?
    h[:sensitive_exposed] = true
  end

  h.merge!(
    supported_processor_manifest.merge(
      Util.remove_blank(processors: processor_manifest)
    )
  )

  target_classes.sort_by(&:name).each do |target_class|
    if target_class.respond_to?(:foobara_manifest)
      h.merge!(target_class.foobara_manifest)
    end
  end

  super.merge(h)
end

#foobara_manifest_referenceObject

TODO: put this somewhere else



371
372
373
# File 'foobara-0.0.130/projects/types/src/type.rb', line 371

def foobara_manifest_reference
  scoped_full_name
end

#full_type_nameObject



353
354
355
# File 'foobara-0.0.130/projects/types/src/type.rb', line 353

def full_type_name
  scoped_full_name
end

#full_type_symbolObject



260
261
262
263
264
265
266
# File 'foobara-0.0.130/projects/types/src/type.rb', line 260

def full_type_symbol
  return @full_type_symbol if defined?(@full_type_symbol)

  @full_type_symbol ||= if scoped_path_set?
                          scoped_full_name.to_sym
                        end
end

#has_sensitive_types?Boolean

Returns:

  • (Boolean)


96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'foobara-0.0.130/projects/types/src/type.rb', line 96

def has_sensitive_types?
  return true if sensitive?

  if element_type
    return true if element_type.has_sensitive_types?
  end

  if element_types
    types = if element_types.is_a?(::Hash)
              element_types.values
            else
              [*element_types]
            end

    types.any?(&:has_sensitive_types?)
  end
end

#processor_manifestObject



474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
# File 'foobara-0.0.130/projects/types/src/type.rb', line 474

def processor_manifest
  to_include = TypeDeclarations.foobara_manifest_context_to_include

  casters_manifest = []
  transformers_manifest = []
  validators_manifest = []
  caster_classes_manifest = []
  transformer_classes_manifest = []
  validator_classes_manifest = []

  casters.each do |caster|
    klass = caster.class
    if to_include
      to_include << klass
    end
    caster_classes_manifest << klass.foobara_manifest_reference

    if caster.scoped_path_set?
      if to_include
        to_include << caster
      end
      casters_manifest << caster.foobara_manifest_reference
    end
  end

  transformers.each do |transformer|
    klass = transformer.class
    if to_include
      to_include << klass
    end
    transformer_classes_manifest << klass.foobara_manifest_reference

    if transformer.scoped_path_set?
      if to_include
        to_include << transformer
      end
      transformers_manifest << transformer.foobara_manifest_reference
    end
  end

  validators.each do |validator|
    klass = validator.class
    if to_include
      to_include << klass
    end
    validator_classes_manifest << klass.foobara_manifest_reference

    if validator.scoped_path_set?
      if to_include
        to_include << validator
      end
      validators_manifest << validator.foobara_manifest_reference
    end
  end

  Util.remove_blank(
    casters: casters_manifest.sort,
    caster_classes: caster_classes_manifest.sort,
    transformers: transformers_manifest.sort,
    transformer_classes: transformer_classes_manifest.sort,
    validators: validators_manifest.sort,
    validator_classes: validator_classes_manifest.sort
  )
end

#processorsObject



268
269
270
271
272
273
274
275
# File 'foobara-0.0.130/projects/types/src/type.rb', line 268

def processors
  [
    value_caster,
    value_transformer,
    value_validator,
    element_processor
  ].compact
end

#reference_or_declaration_data(declaration_data = self.declaration_data) ⇒ Object



357
358
359
360
361
362
363
364
365
366
367
368
# File 'foobara-0.0.130/projects/types/src/type.rb', line 357

def reference_or_declaration_data(declaration_data = self.declaration_data)
  remove_sensitive = TypeDeclarations.foobara_manifest_context_remove_sensitive?

  if registered?
    # TODO: we should just use the symbol and nothing else in this context instead of a hash with 1 element.
    { type: foobara_manifest_reference.to_sym }
  elsif remove_sensitive
    TypeDeclarations.remove_sensitive_types(declaration_data)
  else
    declaration_data
  end
end

#registered?Boolean

Returns:

  • (Boolean)


539
540
541
# File 'foobara-0.0.130/projects/types/src/type.rb', line 539

def registered?
  !!type_symbol
end

#remove_processor_by_symbol(symbol) ⇒ Object



148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'foobara-0.0.130/projects/types/src/type.rb', line 148

def remove_processor_by_symbol(symbol)
  [
    casters,
    element_processors,
    processor_classes_requiring_type,
    processors,
    transformers,
    validators
  ].each do |processor_collection|
    processor_collection&.delete_if { |p| p.symbol == symbol }
  end
  supported_processor_classes&.each { |processor_hash| processor_hash.delete(symbol) }
  processor_classes_requiring_type&.delete_if { |p| p.symbol == symbol }
end

#sensitive?Boolean

Returns:

  • (Boolean)


80
81
82
# File 'foobara-0.0.130/projects/types/src/type.rb', line 80

def sensitive?
  sensitive
end

#sensitive_exposed?Boolean

Returns:

  • (Boolean)


84
85
86
# File 'foobara-0.0.130/projects/types/src/type.rb', line 84

def sensitive_exposed?
  sensitive_exposed
end

#supported_processor_manifestObject



440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
# File 'foobara-0.0.130/projects/types/src/type.rb', line 440

def supported_processor_manifest
  to_include = TypeDeclarations.foobara_manifest_context_to_include

  supported_casters = []
  supported_transformers = []
  supported_validators = []
  supported_processors = []

  all_supported_processor_classes.each do |processor_class|
    if to_include
      to_include << processor_class
    end

    target = if processor_class < Value::Caster
               supported_casters
             elsif processor_class < Value::Validator
               supported_validators
             elsif processor_class < Value::Transformer
               supported_transformers
             else
               supported_processors
             end

    target << processor_class.foobara_manifest_reference
  end

  Util.remove_blank(
    supported_casters: supported_casters.sort,
    supported_transformers: supported_transformers.sort,
    supported_validators: supported_validators.sort,
    supported_processors: supported_processors.sort
  )
end

#target_classObject



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'foobara-0.0.130/projects/types/src/type.rb', line 197

def target_class
  if target_classes.empty?
    # :nocov:
    # TODO: We really need a better error message when we hit this point in the code path.
    # One thing that can cause this is if you create a custom type called :model but it isn't loaded
    # yet and we accidentally are referring to the builtin :model type.  This error message doesn't reveal
    # that you need to require the custom :model.
    raise "No target classes"
    # :nocov:
  elsif target_classes.size > 1
    # :nocov:
    raise "Cannot use #target_class because this type has multiple target_classes"
    # :nocov:
  end

  target_classes.first
end

#validate_processors!Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'foobara-0.0.130/projects/types/src/type.rb', line 171

def validate_processors!
  all = [casters, transformers, validators, element_processors]

  all.each do |processor_group|
    processor_group.each.with_index do |processor, index|
      if processor.requires_parent_declaration_data?
        processor_group[index] = processor.dup_processor(parent_declaration_data: declaration_data)
      end
    end

    processor_group.group_by(&:symbol).each_pair do |symbol, members|
      if members.size > 1
        if members.map { |m| m.class.name }.uniq.size == members.size
          members[1..].each do |member|
            processor_group.delete(member)
          end
        else
          # :nocov:
          raise "Type #{name} has multiple processors with symbol #{symbol}"
          # :nocov:
        end
      end
    end
  end
end

#validation_errors(value) ⇒ Object

TODO: some way of memoizing these values? Would need to introduce a new class that takes the value to its constructor



340
341
342
343
344
345
346
347
348
349
350
351
# File 'foobara-0.0.130/projects/types/src/type.rb', line 340

def validation_errors(value)
  value = cast!(value)
  if value_transformer
    value = value_transformer.process_value!(value)
  end

  if value_validator
    value_validator.process_value(value).errors
  else
    []
  end
end

#value_casterObject



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'foobara-0.0.130/projects/types/src/type.rb', line 277

def value_caster
  # TODO: figure out what would be needed to successfully memoize this
  # return @value_caster if defined?(@value_caster)

  # We make this exception for :duck because it will match any instance of
  # Object but AllowNil will match nil which is also an instance of Object.
  # This results in two matching casters. Instead of figuring out a way to make one
  # conditional on the other we will just turn off this unique enforcement for :duck
  enforce_unique = if declaration_data.is_a?(::Hash)
                     declaration_data[:type] != :duck
                   else
                     true
                   end

  Value::Processor::Casting.new(
    { cast_to: declaration_data },
    casters:,
    target_classes:,
    enforce_unique:
  )
end

#value_transformerObject

TODO: an interesting thought… we have Processor and then a subclass of Processor and then an instance of processor that encapsulates the declaration_data for that processor. But then we pass ‘value` to every method in the instance of the processor as needed. This means it can’t really memoize stuff. Should we create an instance of something from the instance of the processor and then ask it questions?? TODO: try this



317
318
319
320
321
# File 'foobara-0.0.130/projects/types/src/type.rb', line 317

def value_transformer
  if transformers && !transformers.empty?
    Value::Processor::Pipeline.new(processors: transformers)
  end
end

#value_validatorObject

TODO: figure out how to safely memoize stuff so like this for performance reasons A good way, but potentially a decent amount of work, is to have a class that takes value to its initialize method.



326
327
328
329
330
# File 'foobara-0.0.130/projects/types/src/type.rb', line 326

def value_validator
  if validators && !validators.empty?
    Value::Processor::Pipeline.new(processors: validators)
  end
end