Class: Foobara::Types::Type

Inherits:
Value::Processor::Pipeline show all
Includes:
IsManifestable, Concerns::Reflection, Concerns::SupportedProcessorRegistration
Defined in:
foobara-0.0.110/projects/types/src/type.rb,
foobara-0.0.110/projects/types/src/type/concerns/reflection.rb,
foobara-0.0.110/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(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
# File 'foobara-0.0.110/projects/types/src/type.rb', line 37

def initialize(
  *,
  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.sensitive = sensitive
  self.sensitive_exposed = sensitive_exposed
  self.base_type = base_type
  self.description = description
  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.name = name
  self.target_classes = Util.array(target_classes)
  self.processor_classes_requiring_type = processor_classes_requiring_type

  super(*, **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.110/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.110/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.110/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.110/projects/types/src/type.rb', line 17

def element_processors
  @element_processors
end

#element_typeObject



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

def element_type
  @element_type || base_type&.element_type
end

#element_typesObject



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

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.110/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.110/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.110/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.110/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.110/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.110/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.110/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.110/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.110/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.110/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.110/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.110/projects/types/src/type.rb', line 12

def requires_declaration_data?
  true
end

Instance Method Details

#applicable?(value) ⇒ Boolean

Returns:

  • (Boolean)


281
282
283
# File 'foobara-0.0.110/projects/types/src/type.rb', line 281

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

#apply_all_processors_needing_type!Object



113
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
# File 'foobara-0.0.110/projects/types/src/type.rb', line 113

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



418
419
420
# File 'foobara-0.0.110/projects/types/src/type.rb', line 418

def base_type_for_manifest
  base_type
end

#builtin?Boolean

Returns:

  • (Boolean)


414
415
416
# File 'foobara-0.0.110/projects/types/src/type.rb', line 414

def builtin?
  is_builtin
end

#cast(value) ⇒ Object



287
288
289
# File 'foobara-0.0.110/projects/types/src/type.rb', line 287

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

#cast!(value) ⇒ Object



291
292
293
# File 'foobara-0.0.110/projects/types/src/type.rb', line 291

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

#each_processor_class_requiring_type(&block) ⇒ Object



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

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



314
315
316
317
318
# File 'foobara-0.0.110/projects/types/src/type.rb', line 314

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

#extends?(type) ⇒ Boolean

Returns:

  • (Boolean)


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

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)


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

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



357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
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
# File 'foobara-0.0.110/projects/types/src/type.rb', line 357

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



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

def foobara_manifest_reference
  scoped_full_name
end

#full_type_nameObject



335
336
337
# File 'foobara-0.0.110/projects/types/src/type.rb', line 335

def full_type_name
  scoped_full_name
end

#full_type_symbolObject



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

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)


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

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



456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
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
# File 'foobara-0.0.110/projects/types/src/type.rb', line 456

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



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

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

#reference_or_declaration_data(declaration_data = self.declaration_data) ⇒ Object



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

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)


521
522
523
# File 'foobara-0.0.110/projects/types/src/type.rb', line 521

def registered?
  !!type_symbol
end

#remove_processor_by_symbol(symbol) ⇒ Object



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

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)


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

def sensitive?
  sensitive
end

#sensitive_exposed?Boolean

Returns:

  • (Boolean)


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

def sensitive_exposed?
  sensitive_exposed
end

#supported_processor_manifestObject



422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
# File 'foobara-0.0.110/projects/types/src/type.rb', line 422

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



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

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



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

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



322
323
324
325
326
327
328
329
330
331
332
333
# File 'foobara-0.0.110/projects/types/src/type.rb', line 322

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



276
277
278
279
# File 'foobara-0.0.110/projects/types/src/type.rb', line 276

def value_caster
  # TODO: how can declaration_data be blank? That seems really strange...
  Value::Processor::Casting.new({ cast_to: declaration_data }, casters:, target_classes:)
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



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

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.



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

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