docs/bugreports/BUGREPORT_element_order_render_empty
Bug Report: render_empty: :as_blank not respected in element_order serialization
Status: FIXED
This bug has been resolved. The ordered applier now correctly handles empty
collections with render_empty: :as_blank and render_empty: :as_nil.
See spec/lutaml/model/render_empty_spec.rb:248 for the test coverage.
Original Report
Summary
When render_empty: :as_blank is set on a collection element mapping, empty collections should be serialized as self-closing XML elements (e.g., `). However, this only works when the model was **not** parsed from XML. Models parsed from XML haveelement_orderset, which causes them to use a different serialization path that ignores therender_empty` option.
Environment
- lutaml-model version: 0.8.0+ (main branch)
- Ruby version: 3.4.8
Steps to Reproduce
require 'lutaml/model' class Subject < Lutaml::Model::Serializable attribute :authority, :string attribute :topic, :string, collection: true xml do element "subject" map_element "topic", to: :topic, render_empty: :as_blank end end # Fresh model - works correctly s1 = Subject.new(authority: 'lcsh', topic: []) puts s1.to_xml # Output: # ✓ Empty topic IS rendered # Parsed model - render_empty is ignored xml = '' s2 = Subject.from_xml(xml) puts s2.topic.inspect # => [] puts s2.to_xml # Output: # ✗ Empty topic is NOT rendered
Expected Behavior
Both fresh and parsed models should serialize empty collections as self-closing elements when render_empty: :as_blank is set.
Root Cause
The element_order tracking mechanism, used for round-trip XML preservation, has a separate code path in process_collection_item:
# lib/lutaml/xml/transformation/ordered_applier.rb, line 198-210 def process_collection_item(_root, rule, value, object, element_indices, _options) index = element_indices[object.name] value_length = value.respond_to?(:length) ? value.length : value.size if index < value_length # ← Empty collection fails this check single_value = value[index] element_indices[object.name] += 1 yield(:apply_single, rule, single_value) if block_given? end # No handling for empty collections when render_empty: :as_blank is set end
When value is an empty array []:
index= 0value_length= 00 < 0isfalse- The rule is never applied, so empty elements are not rendered
Suggested Fix
In process_collection_item, check render_empty option before skipping:
def process_collection_item(_root, rule, value, object, element_indices, _options) index = element_indices[object.name] value_length = value.respond_to?(:length) ? value.length : value.size if index < value_length single_value = value[index] element_indices[object.name] += 1 yield(:apply_single, rule, single_value) if block_given? elsif index == 0 && value_length == 0 && rule.render_empty == :as_blank # Render empty element when render_empty: :as_blank is set yield(:apply_empty, rule, nil) if block_given? end end
Alternatively, the element_order path should delegate to the same skip logic used by the normal serialization path, which already respects render_empty.