Class: Lutaml::Model::Attribute

Inherits:
Object
  • Object
show all
Defined in:
lib/lutaml/model/attribute.rb

Constant Summary collapse

ALLOWED_OPTIONS =
i[
  raw
  default
  delegate
  collection
  values
  pattern
  transform
  choice
  sequence
  method_name
  polymorphic
  polymorphic_class
  initialize_empty
  validations
].freeze
MODEL_STRINGS =
[
  Lutaml::Model::Type::String,
  "String",
  :string,
].freeze
ALLOW_OVERRIDING =

Safe methods than can be overriden without any crashing

i[
  display
  validate
  hash
  itself
  taint
  untaint
  trust
  untrust
  methods
  instance_variables
  tap
  extend
  freeze
  encoding
  method
  object_id
].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, type, options = {}) ⇒ Attribute

Returns a new instance of Attribute.



71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/lutaml/model/attribute.rb', line 71

def initialize(name, type, options = {})
  validate_name!(
    name, reserved_methods: Lutaml::Model::Serializable.instance_methods
  )

  @name = name
  @options = options

  validate_presence!(type, options[:method_name])
  @type = type
  process_options!
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



4
5
6
# File 'lib/lutaml/model/attribute.rb', line 4

def name
  @name
end

#optionsObject (readonly)

Returns the value of attribute options.



4
5
6
# File 'lib/lutaml/model/attribute.rb', line 4

def options
  @options
end

Class Method Details

.cast_from_string!(type) ⇒ Object



65
66
67
68
69
# File 'lib/lutaml/model/attribute.rb', line 65

def self.cast_from_string!(type)
  Type.const_get(type)
rescue NameError
  raise ArgumentError, "Unknown Lutaml::Model::Type: #{type}"
end

.cast_from_symbol!(type) ⇒ Object



59
60
61
62
63
# File 'lib/lutaml/model/attribute.rb', line 59

def self.cast_from_symbol!(type)
  Type.lookup(type)
rescue UnknownTypeError
  raise ArgumentError, "Unknown Lutaml::Model::Type: #{type}"
end

.cast_type!(type) ⇒ Object



49
50
51
52
53
54
55
56
57
# File 'lib/lutaml/model/attribute.rb', line 49

def self.cast_type!(type)
  case type
  when Symbol then cast_from_symbol!(type)
  when String then cast_from_string!(type)
  when Class then type
  else
    raise ArgumentError, "Unknown Lutaml::Model::Type: #{type}"
  end
end

Instance Method Details

#build_collection(*args) ⇒ Object



171
172
173
# File 'lib/lutaml/model/attribute.rb', line 171

def build_collection(*args)
  collection_class.new(args.flatten)
end

#cast(value, format, register, options = {}) ⇒ Object



376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/lutaml/model/attribute.rb', line 376

def cast(value, format, register, options = {})
  resolved_type = options[:resolved_type] || type(register)
  return build_collection(value.map { |v| cast(v, format, register, options.merge(resolved_type: resolved_type)) }) if collection_instance?(value) || value.is_a?(Array)

  return value if already_serialized?(resolved_type, value)

  klass = resolve_polymorphic_class(resolved_type, value, options)
  if can_serialize?(klass, value, format)
    klass.apply_mappings(value, format, options.merge(register: register))
  elsif needs_conversion?(klass, value)
    klass.send(:"from_#{format}", value)
  else
    # No need to use register#get_class,
    # can_serialize? method already checks if type is Serializable or not.
    Type.lookup(klass).cast(value)
  end
end

#cast_element(value, register) ⇒ Object



134
135
136
137
138
139
# File 'lib/lutaml/model/attribute.rb', line 134

def cast_element(value, register)
  resolved_type = type(register)
  return resolved_type.new(value) if value.is_a?(Hash) && !hash_type?

  resolved_type.cast(value)
end

#cast_type!(type) ⇒ Object



124
125
126
# File 'lib/lutaml/model/attribute.rb', line 124

def cast_type!(type)
  self.class.cast_type!(type)
end

#cast_value(value, register) ⇒ Object



128
129
130
131
132
# File 'lib/lutaml/model/attribute.rb', line 128

def cast_value(value, register)
  return cast_element(value, register) unless collection_instance?(value)

  build_collection(value.map { |v| cast_element(v, register) })
end

#collectionObject



149
150
151
# File 'lib/lutaml/model/attribute.rb', line 149

def collection
  @options[:collection]
end

#collection?Boolean

Returns:

  • (Boolean)


153
154
155
# File 'lib/lutaml/model/attribute.rb', line 153

def collection?
  collection || false
end

#collection_classObject



161
162
163
164
165
# File 'lib/lutaml/model/attribute.rb', line 161

def collection_class
  return Array unless custom_collection?

  collection
end

#collection_instance?(value) ⇒ Boolean

Returns:

  • (Boolean)


167
168
169
# File 'lib/lutaml/model/attribute.rb', line 167

def collection_instance?(value)
  value.is_a?(collection_class)
end

#collection_rangeObject



398
399
400
401
402
# File 'lib/lutaml/model/attribute.rb', line 398

def collection_range
  return unless collection?

  collection.is_a?(Range) ? collection : 0..Float::INFINITY
end

#deep_dupObject



423
424
425
# File 'lib/lutaml/model/attribute.rb', line 423

def deep_dup
  self.class.new(name, unresolved_type, Utils.deep_dup(options))
end

#default(register = Lutaml::Model::Config.default_register) ⇒ Object



183
184
185
# File 'lib/lutaml/model/attribute.rb', line 183

def default(register = Lutaml::Model::Config.default_register)
  cast_value(default_value(register), register)
end

#default_set?(register) ⇒ Boolean

Returns:

  • (Boolean)


199
200
201
# File 'lib/lutaml/model/attribute.rb', line 199

def default_set?(register)
  !Utils.uninitialized?(default_value(register))
end

#default_value(register) ⇒ Object



187
188
189
190
191
192
193
194
195
196
197
# File 'lib/lutaml/model/attribute.rb', line 187

def default_value(register)
  if delegate
    type(register).attributes[to].default(register)
  elsif options[:default].is_a?(Proc)
    options[:default].call
  elsif options.key?(:default)
    options[:default]
  else
    Lutaml::Model::UninitializedClass.instance
  end
end

#delegateObject



104
105
106
# File 'lib/lutaml/model/attribute.rb', line 104

def delegate
  @options[:delegate]
end

#derived?Boolean

Returns:

  • (Boolean)


100
101
102
# File 'lib/lutaml/model/attribute.rb', line 100

def derived?
  unresolved_type.nil?
end

#enum?Boolean

Returns:

  • (Boolean)


179
180
181
# File 'lib/lutaml/model/attribute.rb', line 179

def enum?
  !enum_values.empty?
end

#enum_valuesObject



207
208
209
# File 'lib/lutaml/model/attribute.rb', line 207

def enum_values
  @options.key?(:values) ? @options[:values] : []
end

#execute_validations!(value) ⇒ Object

execute custom validations on the attribute value i.e presence: true, numericality: true, etc



272
273
274
275
276
277
278
279
280
281
# File 'lib/lutaml/model/attribute.rb', line 272

def execute_validations!(value)
  return true if Utils.blank?(value)

  memoization_container = {}
  errors = Lutaml::Model::Validator.call(value, validations, memoization_container)

  return if errors.empty?

  raise Lutaml::Model::ValidationFailedError.new(errors)
end

#hash_type?Boolean

Returns:

  • (Boolean)


141
142
143
# File 'lib/lutaml/model/attribute.rb', line 141

def hash_type?
  type == Lutaml::Model::Type::Hash
end

#initialize_empty?Boolean

Returns:

  • (Boolean)


116
117
118
# File 'lib/lutaml/model/attribute.rb', line 116

def initialize_empty?
  @options[:initialize_empty]
end

#method_nameObject



112
113
114
# File 'lib/lutaml/model/attribute.rb', line 112

def method_name
  @options[:method_name]
end

#patternObject



203
204
205
# File 'lib/lutaml/model/attribute.rb', line 203

def pattern
  options[:pattern]
end

#polymorphic?Boolean

Returns:

  • (Boolean)


96
97
98
# File 'lib/lutaml/model/attribute.rb', line 96

def polymorphic?
  @options[:polymorphic_class]
end

#process_options!Object



416
417
418
419
420
421
# File 'lib/lutaml/model/attribute.rb', line 416

def process_options!
  validate_options!(@options)
  @raw = !!@options[:raw]
  @validations = @options[:validations]
  set_default_for_collection if collection?
end

#raw?Boolean

Returns:

  • (Boolean)


175
176
177
# File 'lib/lutaml/model/attribute.rb', line 175

def raw?
  @raw
end

#sequenced_appearance_count(element_order, mapped_name, current_index) ⇒ Object



404
405
406
407
408
409
410
411
412
413
414
# File 'lib/lutaml/model/attribute.rb', line 404

def sequenced_appearance_count(element_order, mapped_name, current_index)
  elements = element_order[current_index..]
  element_count = elements.take_while { |element| element == mapped_name }.count
  return element_count if element_count.between?(*collection_range.minmax)

  raise Lutaml::Model::ElementCountOutOfRangeError.new(
    mapped_name,
    element_count,
    collection_range,
  )
end

#serializable?(register) ⇒ Boolean

Returns:

  • (Boolean)


394
395
396
# File 'lib/lutaml/model/attribute.rb', line 394

def serializable?(register)
  type(register) <= Serialize
end

#serialize(value, format, register, options = {}) ⇒ Object



363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/lutaml/model/attribute.rb', line 363

def serialize(value, format, register, options = {})
  value ||= build_collection if collection? && initialize_empty?
  return value if value.nil? || Utils.uninitialized?(value)
  return value if derived?

  resolved_type = options[:resolved_type] || type(register)
  serialize_options = options.merge(resolved_type: resolved_type)
  return serialize_array(value, format, register, serialize_options) if collection_instance?(value)
  return serialize_model(value, format, register, options) if resolved_type <= Serialize

  serialize_value(value, format, resolved_type)
end

#setterObject



145
146
147
# File 'lib/lutaml/model/attribute.rb', line 145

def setter
  :"#{@name}="
end

#singular?Boolean

Returns:

  • (Boolean)


157
158
159
# File 'lib/lutaml/model/attribute.rb', line 157

def singular?
  !collection?
end

#transformObject



108
109
110
# File 'lib/lutaml/model/attribute.rb', line 108

def transform
  @options[:transform] || {}
end

#transform_export_methodObject



215
216
217
# File 'lib/lutaml/model/attribute.rb', line 215

def transform_export_method
  transform[:export]
end

#transform_import_methodObject



211
212
213
# File 'lib/lutaml/model/attribute.rb', line 211

def transform_import_method
  transform[:import]
end

#type(register_id = nil) ⇒ Object



84
85
86
87
88
89
90
# File 'lib/lutaml/model/attribute.rb', line 84

def type(register_id = nil)
  return if unresolved_type.nil?

  register_id ||= Lutaml::Model::Config.default_register
  register = Lutaml::Model::GlobalRegister.lookup(register_id)
  register.get_class_without_register(unresolved_type)
end

#unresolved_typeObject



92
93
94
# File 'lib/lutaml/model/attribute.rb', line 92

def unresolved_type
  @type
end

#valid_collection!(value, caller) ⇒ Object



327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/lutaml/model/attribute.rb', line 327

def valid_collection!(value, caller)
  raise Lutaml::Model::CollectionTrueMissingError.new(name, caller) if collection_instance?(value) && !collection?

  return true unless collection?

  # Allow any value for unbounded collections
  return true if options[:collection] == true

  unless collection_instance?(value)
    raise Lutaml::Model::CollectionCountOutOfRangeError.new(
      name,
      value,
      options[:collection],
    )
  end

  range = options[:collection]
  return true unless range.is_a?(Range)

  if range.is_a?(Range) && range.end.nil?
    if value.size < range.begin
      raise Lutaml::Model::CollectionCountOutOfRangeError.new(
        name,
        value,
        range,
      )
    end
  elsif range.is_a?(Range) && !range.cover?(value.size)
    raise Lutaml::Model::CollectionCountOutOfRangeError.new(
      name,
      value,
      range,
    )
  end
end

#valid_pattern!(value, resolved_type) ⇒ Object



237
238
239
240
241
242
243
244
245
246
# File 'lib/lutaml/model/attribute.rb', line 237

def valid_pattern!(value, resolved_type)
  return true unless resolved_type == Lutaml::Model::Type::String
  return true unless pattern

  unless pattern.match?(value)
    raise Lutaml::Model::PatternNotMatchedError.new(name, pattern, value)
  end

  true
end

#valid_value!(value) ⇒ Object



219
220
221
222
223
224
225
226
227
228
229
# File 'lib/lutaml/model/attribute.rb', line 219

def valid_value!(value)
  return true if value.nil? && singular?
  return true unless enum?
  return true if Utils.uninitialized?(value)

  unless valid_value?(value)
    raise Lutaml::Model::InvalidValueError.new(name, value, enum_values)
  end

  true
end

#valid_value?(value) ⇒ Boolean

Returns:

  • (Boolean)


231
232
233
234
235
# File 'lib/lutaml/model/attribute.rb', line 231

def valid_value?(value)
  return true unless options[:values]

  options[:values].include?(value)
end

#validate_collection_rangeObject



296
297
298
299
300
301
302
303
304
305
306
# File 'lib/lutaml/model/attribute.rb', line 296

def validate_collection_range
  range = @options[:collection]
  return if range == true
  return if custom_collection?

  unless range.is_a?(Range)
    raise ArgumentError, "Invalid collection range: #{range}"
  end

  validate_range!(range)
end

#validate_polymorphic(value, resolved_type) ⇒ Object



283
284
285
286
287
288
# File 'lib/lutaml/model/attribute.rb', line 283

def validate_polymorphic(value, resolved_type)
  return value.all? { |v| validate_polymorphic!(v, resolved_type) } if value.is_a?(Array)
  return true unless options[:polymorphic]

  valid_polymorphic_type?(value, resolved_type)
end

#validate_polymorphic!(value, resolved_type) ⇒ Object



290
291
292
293
294
# File 'lib/lutaml/model/attribute.rb', line 290

def validate_polymorphic!(value, resolved_type)
  return true if validate_polymorphic(value, resolved_type)

  raise Lutaml::Model::PolymorphicError.new(value, options, resolved_type)
end

#validate_range!(range) ⇒ Object



308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/lutaml/model/attribute.rb', line 308

def validate_range!(range)
  if range.begin.nil?
    raise ArgumentError,
          "Invalid collection range: #{range}. Begin must be specified."
  end

  if range.begin.negative?
    raise ArgumentError,
          "Invalid collection range: #{range}. " \
          "Begin must be non-negative."
  end

  if range.end && range.end < range.begin
    raise ArgumentError,
          "Invalid collection range: #{range}. " \
          "End must be greater than or equal to begin."
  end
end

#validate_value!(value, register) ⇒ Object

Check if the value to be assigned is valid for the attribute

Currently there are 2 validations

1. Value should be from the values list if they are defined
   e.g values: ["foo", "bar"] is set then any other value for this
       attribute will raise `Lutaml::Model::InvalidValueError`

2. Value count should be between the collection range if defined
   e.g if collection: 0..5 is set then the value greater then 5
       will raise `Lutaml::Model::CollectionCountOutOfRangeError`


258
259
260
261
262
263
264
265
266
267
268
# File 'lib/lutaml/model/attribute.rb', line 258

def validate_value!(value, register)
  # Use the default value if the value is nil
  value = default(register) if value.nil?
  resolved_type = type(register)

  valid_value!(value) &&
    valid_collection!(value, self) &&
    valid_pattern!(value, resolved_type) &&
    validate_polymorphic!(value, resolved_type) &&
    execute_validations!(value)
end

#validationsObject



120
121
122
# File 'lib/lutaml/model/attribute.rb', line 120

def validations
  @options[:validations]
end