Class: Foobara::Types::Type

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



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

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

def casters
  @casters
end

#descriptionObject

Returns the value of attribute description.



17
18
19
# File 'foobara-0.1.7/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.1.7/projects/types/src/type.rb', line 17

def element_processors
  @element_processors
end

#element_typeObject



96
97
98
99
100
101
102
103
104
# File 'foobara-0.1.7/projects/types/src/type.rb', line 96

def element_type
  type = @element_type || base_type&.element_type

  if type.is_a?(::Symbol)
    type = @element_type = TypeDeclarations::LazyElementTypes.const_get(type).resolve(self)
  end

  type
end

#element_typesObject



106
107
108
109
110
111
112
113
114
# File 'foobara-0.1.7/projects/types/src/type.rb', line 106

def element_types
  types = @element_types || base_type&.element_types

  if types.is_a?(::Symbol)
    types = @element_types = TypeDeclarations::LazyElementTypes.const_get(types).resolve(self)
  end

  types
end

#is_builtinObject

Returns the value of attribute is_builtin.



17
18
19
# File 'foobara-0.1.7/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.1.7/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.1.7/projects/types/src/type.rb', line 17

def processor_classes_requiring_type
  @processor_classes_requiring_type
end

#sensitiveObject

Returns the value of attribute sensitive.



17
18
19
# File 'foobara-0.1.7/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.1.7/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.1.7/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.1.7/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.1.7/projects/types/src/type.rb', line 17

def transformers
  @transformers
end

#type_symbolObject

Returns the value of attribute type_symbol.



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

def type_symbol
  @type_symbol
end

#validatorsObject

Returns the value of attribute validators.



17
18
19
# File 'foobara-0.1.7/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.1.7/projects/types/src/type.rb', line 12

def requires_declaration_data?
  true
end

Instance Method Details

#applicable?(value) ⇒ Boolean

Returns:

  • (Boolean)


346
347
348
# File 'foobara-0.1.7/projects/types/src/type.rb', line 346

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

#apply_all_processors_needing_type!Object



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'foobara-0.1.7/projects/types/src/type.rb', line 140

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



488
489
490
# File 'foobara-0.1.7/projects/types/src/type.rb', line 488

def base_type_for_manifest
  base_type
end

#builtin?Boolean

TODO: replace the concept of builtin? with primitive? and delete this method since primitive? already exists.

Returns:

  • (Boolean)


484
485
486
# File 'foobara-0.1.7/projects/types/src/type.rb', line 484

def builtin?
  is_builtin
end

#cast(value) ⇒ Object



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

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

#cast!(value) ⇒ Object



356
357
358
# File 'foobara-0.1.7/projects/types/src/type.rb', line 356

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

#derived?Boolean

Returns:

  • (Boolean)


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

def derived?
  declaration_data.is_a?(::Hash)
end

#each_processor_class_requiring_type(&block) ⇒ Object



189
190
191
192
193
194
195
# File 'foobara-0.1.7/projects/types/src/type.rb', line 189

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



379
380
381
382
383
# File 'foobara-0.1.7/projects/types/src/type.rb', line 379

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

#extends?(type) ⇒ Boolean

Returns:

  • (Boolean)


241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'foobara-0.1.7/projects/types/src/type.rb', line 241

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_directly?(type) ⇒ Boolean

Returns:

  • (Boolean)


261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'foobara-0.1.7/projects/types/src/type.rb', line 261

def extends_directly?(type)
  case type
  when Type
    base_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_directly?(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)


282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'foobara-0.1.7/projects/types/src/type.rb', line 282

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



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

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

  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

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

  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



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

def foobara_manifest_reference
  scoped_full_name
end

#full_type_nameObject



400
401
402
# File 'foobara-0.1.7/projects/types/src/type.rb', line 400

def full_type_name
  scoped_full_name
end

#full_type_symbolObject



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

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)


116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'foobara-0.1.7/projects/types/src/type.rb', line 116

def has_sensitive_types?
  return true if sensitive?

  # TODO: this is a hack... come up with a better/separate way to detect types with private attributes
  if declaration_data.is_a?(::Hash)
    private = declaration_data[:private]
    return true if private.is_a?(::Array) && !private.empty?
  end

  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

#primitive?Boolean

TODO: replace the concept of builtin? with primitive?

Returns:

  • (Boolean)


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

def primitive?
  declaration_data.is_a?(::Symbol)
end

#processor_manifestObject



526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
# File 'foobara-0.1.7/projects/types/src/type.rb', line 526

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



315
316
317
318
319
320
321
322
# File 'foobara-0.1.7/projects/types/src/type.rb', line 315

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

#reference_or_declaration_data(declaration_data = self.declaration_data) ⇒ Object



404
405
406
407
408
409
410
411
412
413
414
415
# File 'foobara-0.1.7/projects/types/src/type.rb', line 404

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.
    foobara_manifest_reference.to_sym
  elsif remove_sensitive
    TypeDeclarations.remove_sensitive_types(declaration_data)
  else
    declaration_data
  end
end

#registered?Boolean

Returns:

  • (Boolean)


591
592
593
# File 'foobara-0.1.7/projects/types/src/type.rb', line 591

def registered?
  !!type_symbol
end

#remove_processor_by_symbol(symbol) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'foobara-0.1.7/projects/types/src/type.rb', line 174

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)


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

def sensitive?
  sensitive
end

#sensitive_exposed?Boolean

Returns:

  • (Boolean)


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

def sensitive_exposed?
  sensitive_exposed
end

#supported_processor_manifestObject



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

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



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'foobara-0.1.7/projects/types/src/type.rb', line 223

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



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'foobara-0.1.7/projects/types/src/type.rb', line 197

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



387
388
389
390
391
392
393
394
395
396
397
398
# File 'foobara-0.1.7/projects/types/src/type.rb', line 387

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



324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'foobara-0.1.7/projects/types/src/type.rb', line 324

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: reference_or_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



364
365
366
367
368
# File 'foobara-0.1.7/projects/types/src/type.rb', line 364

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.



373
374
375
376
377
# File 'foobara-0.1.7/projects/types/src/type.rb', line 373

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