Module: Lutaml::Model::Serialize::Builder

Included in:
Lutaml::Model::Serialize
Defined in:
lib/lutaml/model/serialize/builder.rb

Overview

Builder interface for constructing model instances with mixed content.

Provides two syntaxes:

NO MAGIC (Explicit Receiver)

group = Group.new do |g|
g.member(person1)
g.member_key("john")
g.description "\n  He is a good man.\n"
end

MAGIC (instance_eval — No Receiver)

group = Group.new do
member(person1)
member_key("john")
description "\n  He is a good man.\n"
end

Both syntaxes track the order of calls for mixed_content serialization.

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &block) ⇒ Object (private)

Intercept method calls to track order for mixed_content



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/lutaml/model/serialize/builder.rb', line 136

def method_missing(method_name, *args, &block)
  # Check if this is an attribute setter call
  setter_name = :"#{method_name}="
  if args.length == 1 && attribute_exist?(setter_name)
    # Track order before calling the actual setter
    track_order(method_name, args.first, block) if @__order_tracking__

    # Call the actual setter
    send(setter_name, args.first)
  elsif args.empty? && block
    # Block form: attribute { |nested| nested.attr value }
    # This handles nested model construction
    handle_nested_block(method_name, block)
  else
    super
  end
end

Instance Method Details

#initialize(attrs = {}, options = {}, &block) ⇒ Object

Override initialize to support builder block syntax.

When a block is passed, evaluates it in the model's context. For mixed_content models, tracks call order in element_order.

Parameters:

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

    Attribute values (ignored when block given)

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

    Options (ignored when block given)

  • block (Proc, nil)

    Optional builder block



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/lutaml/model/serialize/builder.rb', line 36

def initialize(attrs = {}, options = {}, &block)
  # Call parent initialize
  super(attrs, options)

  return self unless block

  # Enable order tracking for ordered and mixed_content models.
  # `ordered?` is true for both `ordered` and `mixed_content`
  # (mixed_content sets @ordered = true in addition to @mixed_content).
  @__order_tracking__ = ordered?

  # Evaluate the block - use instance_eval for no-receiver style
  # The block's first parameter determines the style:
  # - |g| -> explicit receiver (block.call(g))
  # - no param -> instance_eval (&block)
  if block.arity.zero?
    instance_eval(&block)
  else
    yield(self)
  end

  self
end

#mixed_content?Boolean

Check if this model has mixed_content enabled

Returns:

  • (Boolean)


62
63
64
65
# File 'lib/lutaml/model/serialize/builder.rb', line 62

def mixed_content?
  mapping = self.class.mappings_for(:xml, lutaml_register)
  mapping&.mixed_content? || false
end

#order_tracking_enabled?Boolean

Whether this instance was constructed via a builder block and therefore records mutations into element_order. Parsed models and instances constructed without a block do not track; their element_order (if any) comes from the parser and is treated as the complete source of truth by the serializer.

Returns:

  • (Boolean)


84
85
86
# File 'lib/lutaml/model/serialize/builder.rb', line 84

def order_tracking_enabled?
  @__order_tracking__ ? true : false
end

#ordered?Boolean

Check if this model has ordered or mixed_content enabled. Both set @ordered = true on the XML mapping; mixed_content additionally sets @mixed_content = true. Order tracking is needed for either mode so the builder can emit elements in setter-call order rather than declaration order.

Returns:

  • (Boolean)


73
74
75
76
# File 'lib/lutaml/model/serialize/builder.rb', line 73

def ordered?
  mapping = self.class.mappings_for(:xml, lutaml_register)
  mapping&.ordered? || false
end

#record_mutation(attribute_name, value = nil) ⇒ Object

Record a singular attribute mutation in element_order.

No-op unless order tracking is enabled (i.e. the instance was constructed via a builder block on an ordered/mixed_content model). This is the single entry point for singular mutations and is called from generated setters and getter-with-arg paths so that direct setters (x.foo = v) and appender calls (x.foo(v)) behave identically.

Returns value so generated setters preserve Ruby's setter contract: obj.foo = v evaluates to v. Callers like obj.foo || (obj.foo = []) depend on this.

Parameters:

  • attribute_name (Symbol)

    The attribute being mutated

  • value (Object, nil) (defaults to: nil)

    The value being set; stored as text content for content-mapped attributes, ignored otherwise

Returns:

  • (Object)

    the passed value



105
106
107
108
109
110
# File 'lib/lutaml/model/serialize/builder.rb', line 105

def record_mutation(attribute_name, value = nil)
  return value unless @__order_tracking__

  track_order(attribute_name, value, nil)
  value
end

#record_mutation_collection(attribute_name, value) ⇒ Object

Record a collection-attribute mutation in element_order.

Emits one entry per item so that element_order length matches the number of serialized child elements. No-op when tracking is disabled, when the value is nil/empty, or when the collection's frozen sentinel is preserved (no real data).

Returns value so generated setters preserve Ruby's setter contract (see #record_mutation).

Parameters:

  • attribute_name (Symbol)

    The collection attribute

  • value (Object, nil)

    The value assigned to the collection

Returns:

  • (Object)

    the passed value



125
126
127
128
129
130
131
# File 'lib/lutaml/model/serialize/builder.rb', line 125

def record_mutation_collection(attribute_name, value)
  return value unless @__order_tracking__
  return value if value.nil? || Lutaml::Model::Utils.uninitialized?(value)

  Array(value).each { |item| track_order(attribute_name, item, nil) }
  value
end