Class: Foobara::Types::Type

Inherits:
Value::Processor::Pipeline show all
Includes:
IsManifestable, Concerns::Reflection, Concerns::SupportedProcessorRegistration
Defined in:
foobara-0.5.8/projects/typesystem/projects/types/src/type.rb,
foobara-0.5.8/projects/typesystem/projects/types/src/type/concerns/reflection.rb,
foobara-0.5.8/projects/typesystem/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_class, error_classes, #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_declaration_data?, requires_parent_declaration_data?, #requires_parent_declaration_data?, #respond_to_missing?, #runner, symbol, #symbol

Constructor Details

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

Returns a new instance of Type.



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

def initialize(
  declaration_data,
  target_classes:,
  base_type:,
  description: nil,
  name: "anonymous",
  casters: [],
  transformers: [],
  validators: [],
  element_processors: 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
  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 'projects/typesystem/projects/types/src/type.rb', line 17

def base_type
  @base_type
end

#cast_even_if_instance_of_target_typeObject

Returns the value of attribute cast_even_if_instance_of_target_type.



27
28
29
# File 'projects/typesystem/projects/types/src/type.rb', line 27

def cast_even_if_instance_of_target_type
  @cast_even_if_instance_of_target_type
end

#castersObject

Returns the value of attribute casters.



27
28
29
# File 'projects/typesystem/projects/types/src/type.rb', line 27

def casters
  @casters
end

#descriptionObject

Returns the value of attribute description.



17
18
19
# File 'projects/typesystem/projects/types/src/type.rb', line 17

def description
  @description
end

#element_processorsObject

Returns the value of attribute element_processors.



27
28
29
# File 'projects/typesystem/projects/types/src/type.rb', line 27

def element_processors
  @element_processors
end

#element_typeObject



94
95
96
97
98
99
100
101
102
# File 'projects/typesystem/projects/types/src/type.rb', line 94

def element_type
  lru_cache.cached([self, :element_type]) do
    if element_type_loader
      element_type_loader.resolve(self)
    else
      base_type&.element_type
    end
  end
end

#element_type_loaderObject

Returns the value of attribute element_type_loader.



17
18
19
# File 'projects/typesystem/projects/types/src/type.rb', line 17

def element_type_loader
  @element_type_loader
end

#element_typesObject



104
105
106
107
108
109
110
111
112
# File 'projects/typesystem/projects/types/src/type.rb', line 104

def element_types
  lru_cache.cached([self, :element_types]) do
    if element_types_loader
      element_types_loader.resolve(self)
    else
      base_type&.element_types
    end
  end
end

#element_types_loaderObject

Returns the value of attribute element_types_loader.



17
18
19
# File 'projects/typesystem/projects/types/src/type.rb', line 17

def element_types_loader
  @element_types_loader
end

#is_builtinObject

Returns the value of attribute is_builtin.



17
18
19
# File 'projects/typesystem/projects/types/src/type.rb', line 17

def is_builtin
  @is_builtin
end

#nameObject

Returns the value of attribute name.



17
18
19
# File 'projects/typesystem/projects/types/src/type.rb', line 17

def name
  @name
end

#processor_classes_requiring_typeObject

Returns the value of attribute processor_classes_requiring_type.



27
28
29
# File 'projects/typesystem/projects/types/src/type.rb', line 27

def processor_classes_requiring_type
  @processor_classes_requiring_type
end

#sensitiveObject

Returns the value of attribute sensitive.



17
18
19
# File 'projects/typesystem/projects/types/src/type.rb', line 17

def sensitive
  @sensitive
end

#sensitive_exposedObject

Returns the value of attribute sensitive_exposed.



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

def structure_count
  @structure_count
end

#target_classesObject

Returns the value of attribute target_classes.



27
28
29
# File 'projects/typesystem/projects/types/src/type.rb', line 27

def target_classes
  @target_classes
end

#transformersObject

Returns the value of attribute transformers.



27
28
29
# File 'projects/typesystem/projects/types/src/type.rb', line 27

def transformers
  @transformers
end

#type_symbolObject

Returns the value of attribute type_symbol.



27
28
29
# File 'projects/typesystem/projects/types/src/type.rb', line 27

def type_symbol
  @type_symbol
end

#validatorsObject

Returns the value of attribute validators.



27
28
29
# File 'projects/typesystem/projects/types/src/type.rb', line 27

def validators
  @validators
end

Class Method Details

.requires_declaration_data?Boolean

Returns:

  • (Boolean)


12
13
14
# File 'projects/typesystem/projects/types/src/type.rb', line 12

def requires_declaration_data?
  true
end

Instance Method Details

#add_caster(processor) ⇒ Object



286
287
288
289
# File 'projects/typesystem/projects/types/src/type.rb', line 286

def add_caster(processor)
  casters << processor
  clear_caches
end

#applicable?(value) ⇒ Boolean

Returns:

  • (Boolean)


357
358
359
# File 'projects/typesystem/projects/types/src/type.rb', line 357

def applicable?(value)
  !value_caster.needs_cast?(value) || value_caster.can_cast?(value)
end

#base_type_for_manifestObject



539
540
541
# File 'projects/typesystem/projects/types/src/type.rb', line 539

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)


535
536
537
# File 'projects/typesystem/projects/types/src/type.rb', line 535

def builtin?
  is_builtin
end

#cast(value) ⇒ Object



365
366
367
# File 'projects/typesystem/projects/types/src/type.rb', line 365

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

#cast!(value) ⇒ Object



369
370
371
# File 'projects/typesystem/projects/types/src/type.rb', line 369

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

#clear_cachesObject



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'projects/typesystem/projects/types/src/type.rb', line 138

def clear_caches
  [
    :@value_validator,
    :@processors,
    :@value_caster,
    :@value_transformer,
    :@element_processor,
    :@possible_errors,
    :@processors_without_casters
  ].each do |instance_variable|
    if instance_variable_defined?(instance_variable)
      remove_instance_variable(instance_variable)
    end
  end
end

#declaration_data_for_manifestObject



444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
# File 'projects/typesystem/projects/types/src/type.rb', line 444

def declaration_data_for_manifest
  data = declaration_data

  if data.is_a?(::Hash) && data[:type] == :attributes
    if data.key?(:defaults)
      defaults = data[:defaults]

      if defaults.is_a?(::Hash) && defaults.values.any? { it.is_a?(Proc) }
        cleaned_defaults = defaults.transform_values do |value|
          if value.is_a?(Proc)
            "[<Lazily Set>]"
          else
            value
          end
        end

        data = data.merge(defaults: cleaned_defaults)
      end
    end
  end

  data
end

#derived?Boolean

Returns:

  • (Boolean)


82
83
84
# File 'projects/typesystem/projects/types/src/type.rb', line 82

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

#each_processor_class_requiring_type(&block) ⇒ Object



178
179
180
181
182
183
184
# File 'projects/typesystem/projects/types/src/type.rb', line 178

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



396
397
398
399
400
401
402
# File 'projects/typesystem/projects/types/src/type.rb', line 396

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

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

#extends?(type) ⇒ Boolean

Returns:

  • (Boolean)


204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'projects/typesystem/projects/types/src/type.rb', line 204

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)


224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'projects/typesystem/projects/types/src/type.rb', line 224

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)


245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'projects/typesystem/projects/types/src/type.rb', line 245

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



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
520
521
522
523
524
525
526
527
528
529
530
531
# File 'projects/typesystem/projects/types/src/type.rb', line 473

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 = declaration_data_for_manifest

  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



469
470
471
# File 'projects/typesystem/projects/types/src/type.rb', line 469

def foobara_manifest_reference
  scoped_full_name
end

#full_type_nameObject



419
420
421
# File 'projects/typesystem/projects/types/src/type.rb', line 419

def full_type_name
  scoped_full_name
end

#full_type_symbolObject



316
317
318
319
320
321
322
# File 'projects/typesystem/projects/types/src/type.rb', line 316

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)


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

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

#needs_cast?(value) ⇒ Boolean

Returns:

  • (Boolean)


361
362
363
# File 'projects/typesystem/projects/types/src/type.rb', line 361

def needs_cast?(value)
  value_caster.needs_cast?(value)
end

#primitive?Boolean

TODO: replace the concept of builtin? with primitive?

Returns:

  • (Boolean)


78
79
80
# File 'projects/typesystem/projects/types/src/type.rb', line 78

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

#processorsObject



324
325
326
327
328
329
330
331
# File 'projects/typesystem/projects/types/src/type.rb', line 324

def processors
  @processors ||= [
    value_caster,
    value_transformer,
    value_validator,
    element_processor
  ].compact.sort_by(&:priority)
end

#processors=Object



265
266
267
268
# File 'projects/typesystem/projects/types/src/type.rb', line 265

def processors=(...)
  clear_caches
  super
end

#reference_or_declaration_data(declaration_data = self.declaration_data) ⇒ Object



427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
# File 'projects/typesystem/projects/types/src/type.rb', line 427

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

#reference_or_declaration_data_for_manifestObject



423
424
425
# File 'projects/typesystem/projects/types/src/type.rb', line 423

def reference_or_declaration_data_for_manifest
  reference_or_declaration_data(declaration_data_for_manifest)
end

#registered?Boolean

Returns:

  • (Boolean)


543
544
545
# File 'projects/typesystem/projects/types/src/type.rb', line 543

def registered?
  !!type_symbol
end

#remove_caster_instances_of(klass) ⇒ Object



154
155
156
157
158
159
160
# File 'projects/typesystem/projects/types/src/type.rb', line 154

def remove_caster_instances_of(klass)
  self.casters = casters.reject do |caster|
    caster.is_a?(klass)
  end

  clear_caches
end

#remove_processor_by_symbol(symbol) ⇒ Object



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'projects/typesystem/projects/types/src/type.rb', line 162

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 }
  clear_caches
end

#sensitive?Boolean

Returns:

  • (Boolean)


86
87
88
# File 'projects/typesystem/projects/types/src/type.rb', line 86

def sensitive?
  sensitive
end

#sensitive_exposed?Boolean

Returns:

  • (Boolean)


90
91
92
# File 'projects/typesystem/projects/types/src/type.rb', line 90

def sensitive_exposed?
  sensitive_exposed
end

#target_classObject



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'projects/typesystem/projects/types/src/type.rb', line 186

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

#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



406
407
408
409
410
411
412
413
414
415
416
417
# File 'projects/typesystem/projects/types/src/type.rb', line 406

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



333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'projects/typesystem/projects/types/src/type.rb', line 333

def value_caster
  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

  Namespace.use created_in_namespace do
    @value_caster = Value::Processor::Casting.new(
      { cast_to: reference_or_declaration_data },
      casters:,
      target_classes:,
      enforce_unique:,
      cast_even_if_instance_of_target_type:
    )
  end
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



377
378
379
380
381
382
383
# File 'projects/typesystem/projects/types/src/type.rb', line 377

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

  @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.



388
389
390
391
392
393
394
# File 'projects/typesystem/projects/types/src/type.rb', line 388

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

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