Class: Lutaml::Model::Choice

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

Constant Summary collapse

INTERNAL_ATTRIBUTES =

Rebound at mapping finalize: sequence-nested choices are built against the mapping object, validation resolves through the mapper class (lutaml-model#687).

i[@flat_attributes].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model, min, max, format: nil) ⇒ Choice

Returns a new instance of Choice.



15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/lutaml/model/choice.rb', line 15

def initialize(model, min, max, format: nil)
  @attributes = []
  @model = model
  @min = min
  @max = max
  @format = format

  if @min.negative? || @max.negative?
    raise Lutaml::Model::InvalidChoiceRangeError.new(@min,
                                                     @max)
  end
end

Instance Attribute Details

#attributesObject (readonly)

Returns the value of attribute attributes.



6
7
8
# File 'lib/lutaml/model/choice.rb', line 6

def attributes
  @attributes
end

#formatObject

Returns the value of attribute format.



7
8
9
# File 'lib/lutaml/model/choice.rb', line 7

def format
  @format
end

#maxObject (readonly)

Returns the value of attribute max.



6
7
8
# File 'lib/lutaml/model/choice.rb', line 6

def max
  @max
end

#minObject (readonly)

Returns the value of attribute min.



6
7
8
# File 'lib/lutaml/model/choice.rb', line 6

def min
  @min
end

#modelObject

Returns the value of attribute model.



7
8
9
# File 'lib/lutaml/model/choice.rb', line 7

def model
  @model
end

Instance Method Details

#==(other) ⇒ Object



28
29
30
31
32
33
# File 'lib/lutaml/model/choice.rb', line 28

def ==(other)
  @attributes == other.attributes &&
    @min == other.min &&
    @max == other.max &&
    @model == other.model
end

#attribute(name, type, options = {}) ⇒ Object



35
36
37
38
# File 'lib/lutaml/model/choice.rb', line 35

def attribute(name, type, options = {})
  options[:choice] = self
  @attributes << @model.attribute(name, type, options)
end

#can_render_empty?(_object) ⇒ Boolean

Returns:

  • (Boolean)


103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/lutaml/model/choice.rb', line 103

def can_render_empty?(_object)
  # Check if ALL attributes in the choice have render_empty: true
  # This allows empty instances of required elements to pass validation
  return false unless @format

  mapping = @model.mappings_for(@format)
  return false unless mapping&.elements

  @attributes.all? do |attribute|
    next true if attribute.is_a?(Choice) # Nested choices handled separately

    rule = mapping.elements.find { |r| r.to == attribute.name }
    rule&.render_empty?
  end
rescue StandardError
  false
end

#choice(min: 1, max: 1, &block) ⇒ Object



70
71
72
73
74
# File 'lib/lutaml/model/choice.rb', line 70

def choice(min: 1, max: 1, &block)
  @attributes << Choice.new(@model, min, max, format: @format).tap do |c|
    c.instance_eval(&block)
  end
end

#deep_duplicate(new_model, register = nil) ⇒ Object



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/lutaml/model/choice.rb', line 153

def deep_duplicate(new_model, register = nil)
  choice = self.class.new(new_model, @min, @max, format: @format)
  attrs = @attributes.map do |attr|
    next attr.deep_duplicate(new_model, register) if attr.is_a?(Choice)

    choice_attr = new_model.attributes(register)[attr.name]
    next if choice_attr.nil?

    choice_attr.options[:choice] = choice
    choice_attr
  end

  choice.attributes.concat(attrs.compact)
  choice
end

#flat_attributesObject



76
77
78
79
80
# File 'lib/lutaml/model/choice.rb', line 76

def flat_attributes
  @flat_attributes ||= @attributes.flat_map do |attribute|
    attribute.is_a?(Choice) ? attribute.flat_attributes : attribute
  end
end

#import_model_attributes(model, register_id = nil) ⇒ Object



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
147
148
149
150
151
# File 'lib/lutaml/model/choice.rb', line 121

def import_model_attributes(model, register_id = nil)
  if later_importable?(model)
    return import_model_later(model, :import_model_attributes)
  end

  root_model_error(model)
  register_id ||= Lutaml::Model::Config.default_register

  if register_id != :default
    # Non-default register: store in register-specific storage only
    current_record = @model.register_records[register_id] ||= {
      attributes: {}, choice_attributes: []
    }
    imported_attributes = Utils.deep_dup(model.attributes(register_id))
    imported_attributes.each_value do |attr|
      attr.options[:choice] = self
    end
    current_record[:attributes].merge!(imported_attributes)
    return
  end

  # Default register: store in class-level storage
  imported_attributes = Utils.deep_dup(model.attributes.values)
  imported_attributes.each do |attr|
    attr.options[:choice] = self
    @model.define_attribute_methods(attr)
  end
  @attributes.concat(imported_attributes)
  attrs_hash = imported_attributes.to_h { |attr| [attr.name, attr] }
  @model.attributes.merge!(attrs_hash)
end

#optional_empty_choice?(count) ⇒ Boolean

Returns:

  • (Boolean)


183
184
185
186
187
188
189
# File 'lib/lutaml/model/choice.rb', line 183

def optional_empty_choice?(count)
  count.zero? && @attributes.any? do |attr|
    next attr.optional_empty_choice?(count) if attr.is_a?(self.class)

    optional_attribute?(attr)
  end
end

#pretty_print_instance_variablesObject



191
192
193
# File 'lib/lutaml/model/choice.rb', line 191

def pretty_print_instance_variables
  (instance_variables - INTERNAL_ATTRIBUTES).sort
end

#remove_attribute(name) ⇒ Boolean

Remove an attribute from this choice block

Parameters:

  • name (Symbol)

    The attribute name to remove

Returns:

  • (Boolean)

    true if the attribute was removed



60
61
62
63
64
65
66
67
68
# File 'lib/lutaml/model/choice.rb', line 60

def remove_attribute(name)
  attr = @attributes.find { |a| !a.is_a?(Choice) && a.name == name }
  return nil unless attr

  @attributes.delete(attr)
  attr.options.delete(:choice)
  invalidate_cache!
  attr
end

#restrict(name, options = {}) ⇒ Symbol

Restrict options on a predefined or imported attribute within this choice

Parameters:

  • name (Symbol)

    The attribute name to restrict

  • options (Hash) (defaults to: {})

    New options to merge

Returns:

  • (Symbol)

    The attribute name



45
46
47
48
49
50
51
52
53
54
# File 'lib/lutaml/model/choice.rb', line 45

def restrict(name, options = {})
  @model.restrict(name, options)
  attr = @model.attributes[name]
  unless @attributes.include?(attr)
    attr.options[:choice] = self
    @attributes << attr
  end
  invalidate_cache!
  name
end

#validate_content!(object, register = nil) ⇒ Object



91
92
93
94
95
96
97
98
99
100
101
# File 'lib/lutaml/model/choice.rb', line 91

def validate_content!(object, register = nil)
  validated_attributes = []
  valid = valid_attributes(object, validated_attributes, register)

  # Allow empty choice if it can render empty elements
  if valid.none? && can_render_empty?(object)
    return
  end

  validate_count_errors!(valid.count, validated_attributes)
end

#validate_count_errors!(count, attributes) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/lutaml/model/choice.rb', line 169

def validate_count_errors!(count, attributes)
  return if count.between?(@min, @max)
  return if optional_empty_choice?(count)

  if count < @min
    raise Lutaml::Model::ChoiceLowerBoundError.new(attributes,
                                                   @min)
  end
  if count > @max
    raise Lutaml::Model::ChoiceUpperBoundError.new(attributes,
                                                   @max)
  end
end

#validate_sequence_content!(elements, appearance_count = 0, register = nil) ⇒ Object



82
83
84
85
86
87
88
89
# File 'lib/lutaml/model/choice.rb', line 82

def validate_sequence_content!(elements, appearance_count = 0,
register = nil)
  choices_hash = ::Hash.new { |h, k| h[k] = 0 }
  choices_hash[self] = appearance_count
  current_index = validate_choices(elements, choices_hash, register)
  raise_errors(choices_hash)
  current_index
end