<?xml version="1.0" encoding="UTF-8"?>
<metanorma xmlns="https://www.metanorma.org/ns/standoc" type="semantic" version="2.6.5" schema-version="v2.0.4" flavor="ribose">
<bibdata type="standard">
<title language="en" format="text/plain">LutaML — Model Collections</title>
<docidentifier primary="true" type="Ribose">RS 3005</docidentifier><docnumber>RS 3005</docnumber><date type="published"><on>2025-02-20</on></date><contributor><role type="author"/><organization>
<name>Ribose Inc.</name>
<address><formattedAddress>167-169 Great Portland Street<br/>5th Floor<br/>London<br/>W1W 5PF<br/>United Kingdom</formattedAddress></address></organization></contributor><contributor><role type="author"/><person>
<name><completename>Ronald Tse</completename></name>
<affiliation><organization>
<name>Ribose</name>
</organization></affiliation></person></contributor><contributor><role type="publisher"/><organization>
<name>Ribose Inc.</name>
<address><formattedAddress>167-169 Great Portland Street<br/>5th Floor<br/>London<br/>W1W 5PF<br/>United Kingdom</formattedAddress></address></organization></contributor><edition>1.0</edition><language>en</language><script>Latn</script><status><stage>published</stage></status><copyright><from>2025</from><owner><organization>
<name>Ribose Inc.</name>
<address><formattedAddress>167-169 Great Portland Street<br/>5th Floor<br/>London<br/>W1W 5PF<br/>United Kingdom</formattedAddress></address></organization></owner></copyright><ext><doctype>standard</doctype><flavor>ribose</flavor><editorialgroup><committee type="technical">LutaML</committee></editorialgroup><security>unrestricted</security></ext></bibdata><metanorma-extension><presentation-metadata><name>TOC Heading Levels</name><value>2</value></presentation-metadata><presentation-metadata><name>HTML TOC Heading Levels</name><value>2</value></presentation-metadata><presentation-metadata><name>DOC TOC Heading Levels</name><value>2</value></presentation-metadata><presentation-metadata><name>PDF TOC Heading Levels</name><value>2</value></presentation-metadata></metanorma-extension>
<boilerplate><copyright-statement>

<clause id="_e6d5240d-324b-88ca-a190-956ea435c32e" obligation="normative">
<p id="_96cc8bbb-9d9a-ff6c-98e9-9a7a9b0a5def">© Ribose Inc. 2025</p>
</clause>
</copyright-statement>

<legal-statement>

<clause id="_7c9b34cc-5853-55f8-f0b6-1d5149135a25" obligation="normative">
<p id="_50bbfa02-61c3-8332-bad8-61e7389d1808">All rights reserved. Unless otherwise specified, no part of this publication may be reproduced or utilized otherwise in any form or by any means, electronic or mechanical, including photocopying, or posting on the internet or an intranet, without prior written permission. Permission can be requested from the address below.</p>
</clause>
</legal-statement>

<feedback-statement>

<clause id="_836ec555-8fe8-ed0b-96d7-ba7230d4cfaa" obligation="normative">
<p id="boilerplate-name" align="left">Ribose Inc.</p>

<p id="boilerplate-address" align="left">167-169 Great Portland Street<br/>5th Floor<br/>London<br/>W1W 5PF<br/>United Kingdom<br/><br/><link target="mailto:copyright@ribose.com"/><br/><link target="https://www.ribose.com">www.ribose.com</link></p>
</clause>
</feedback-statement>
</boilerplate><sections><clause id="_scope" type="scope" obligation="normative">
<title>Scope</title>
<p id="_c767ed86-6fba-cddd-ee1e-c8cbc49702cc">This document specifies the collection capabilities in LutaML Model, which enable:</p>

<ul id="_9b0fdb27-32ea-c998-a636-481eeb751c92"><li><p id="_f24b8668-21d2-f65e-1dc6-8507f7ded227">Definition of collections within models</p>
</li>
<li><p id="_907520f7-2b67-fc32-66ff-51798ab7feb1">Serialization of collections to different formats</p>
</li>
<li><p id="_e231679a-39eb-7bfd-1378-2bfb52c92559">Mapping of collection elements to different representations</p>
</li>
</ul>
</clause>



<terms id="_terms_and_definitions" obligation="normative">
<title>Terms and definitions</title><p id="_1fbc7bf1-a8de-c3eb-ee88-3b4cf950e61b">For the purposes of this document, the following terms and definitions apply.</p>

<term id="term-collection"><preferred><expression>
<name>collection</name>
</expression>
</preferred>
<definition><verbal-definition><p id="_6073f750-230d-1dfd-e310-bfa761f12f46">specialized class that defines a container for multiple instances of a model type</p></verbal-definition></definition>
 </term>

<term id="term-collection-element"><preferred><expression>
<name>collection element</name>
</expression>
</preferred>
<definition><verbal-definition><p id="_6380710f-c117-d59a-354b-57a7a976d2fa">individual instance within a collection</p></verbal-definition></definition>
 </term>

<term id="term-collection-mapping"><preferred><expression>
<name>collection mapping</name>
</expression>
</preferred>
<definition><verbal-definition><p id="_acdc5c5a-ab90-c228-4807-4880316b1de8">rules that define how collection elements are serialized and deserialized</p></verbal-definition></definition>
 </term>
</terms>

<clause id="_collection_types" obligation="normative">
<title>Collection types</title>
<clause id="_general" obligation="normative">
<title>General</title>
<p id="_3ce3dd57-276a-9503-4a0c-c3633947a4c6">LutaML Model provides the <tt>Lutaml::Model::Collection</tt> class for defining collections of model instances.</p>
</clause>

<clause id="_configuration" obligation="normative">
<title>Configuration</title>
<clause id="_instances_attribute_modeltype" obligation="normative">
<title><tt>instances {attribute}, {ModelType}</tt></title>
<p id="_22a9ca58-55e0-a4b4-928d-be624e80cdaf">Defined at the class level.</p>

<p id="_b9de7bbb-4e20-3939-2306-8989e1dcaef4">Defines the collection attribute and the model type of the collection elements.</p>
</clause>

<clause id="_map_instances_to_attribute" obligation="normative">
<title><tt>map_instances to: {attribute}</tt></title>
<p id="_5483d277-de4d-054e-33d1-e4a4c273e21d">Defined within the <tt>key_value</tt> block. In the <tt>xml</tt> block, the <tt>map_attribute</tt> and <tt>map_element</tt> directives are used instead.</p>

<p id="_c3dc7996-c69f-7c97-4e30-70b9bf4d46b7">This directive maps individual array elements to the defined <tt>instances</tt>
attribute. These are the items considered part of the Collection and reflected
as Enumerable elements.<note id="_a7d265b5-2d59-348d-7a10-0432974b406b"><p id="_8b06fa33-606f-b1a5-cfe8-e1e0d12e9dda">The <tt>map_instances</tt> directive is only used in the <tt>key_value</tt> block.</p>
</note></p>


</clause>
</clause>

<clause id="_root_collections" obligation="normative">
<title>Root collections</title>
<p id="_8f60c4fb-83f9-0d9b-9d6b-adf88dfa839a">These are simple collections store multiple instances of the same model type, placed at the root level of the serialization format.</p>

<sourcecode id="_289bd164-d062-d6f1-fdad-733754403432" lang="xml">
<name>Simple collection in XML with models each containing an attribute <tt>name</tt></name>
<body>&lt;name&gt;Item One&lt;/name&gt;
&lt;name&gt;Item Two&lt;/name&gt;
&lt;name&gt;Item Three&lt;/name&gt;</body></sourcecode>


<sourcecode id="_536c9445-16cf-dc2b-68fe-9dfed931a931" lang="yaml">
<name>Simple collection in YAML with models each containing an attribute <tt>name</tt></name>
<body>---
- name: Item One
- name: Item Two
- name: Item Three</body></sourcecode>


<p id="_63c33cab-cee0-6235-0590-1dcddd8bdad3">Syntax:</p>

<sourcecode id="_d704008a-bdcf-4dec-b821-bdd40e68f2aa" lang="ruby"><body>class MyCollection &lt; Lutaml::Model::Collection
  instances :items, ModelType
end

class ModelType &lt; Lutaml::Model::Serializable
  attribute :name, :string
end</body></sourcecode>


<example id="_50417edc-fbd4-8b13-aaf8-065fb8ce5ad4"><sourcecode id="_62aea87c-66a2-5544-f4d6-67dd6501fcaf" lang="ruby"><body>class Title &lt; Lutaml::Model::Serializable
  attribute :content, :string
end

class TitleCollection &lt; Lutaml::Model::Collection
  instances :titles, Title

  xml do
    no_root # default
    map_element "title", to: :titles
  end

  key_value do
    no_root # default
    map_instances to: :titles
  end
end</body></sourcecode>  <sourcecode id="_073a96d6-5842-6cfa-92e7-21f297c44bd9" lang="xml"><body>&lt;title&gt;
  &lt;content&gt;Title One&lt;/content&gt;
&lt;/title&gt;
&lt;title&gt;
  &lt;content&gt;Title Two&lt;/content&gt;
&lt;/title&gt;
&lt;title&gt;
  &lt;content&gt;Title Three&lt;/content&gt;
&lt;/title&gt;</body></sourcecode>  <sourcecode id="_14e1ccd1-d841-1109-d470-7fbc0b0d8405" lang="yaml"><body>---
- content: Title One
- content: Title Two
- content: Title Three</body></sourcecode>  <sourcecode id="_1397d9fd-f6bc-55fa-e9fa-b8d9689606c7" lang="json"><body>[
  {"content": "Title One"},
  {"content": "Title Two"},
  {"content": "Title Three"}
]</body></sourcecode>  <sourcecode id="_c2219041-4a2e-f2e5-7326-f771080c8f9f" lang="ruby"><body>titles = TitleCollection.from_yaml(yaml_data)
titles.count
# =&gt; 3
titles.first.content
# =&gt; "Title One"</body></sourcecode> </example>
</clause>

<clause id="_named_collections" obligation="normative">
<title>Named collections</title>
<p id="_b424a147-0663-fc85-0584-8a84d9208923">Named collections are collections wrapped inside a name or a key.</p>

<sourcecode id="_be780025-240a-589e-60b0-e6ae966f1cfe" lang="xml">
<name>Named collections in XML with models each containing an attribute <tt>name</tt></name>
<body>&lt;names&gt;
  &lt;name&gt;Item One&lt;/name&gt;
  &lt;name&gt;Item Two&lt;/name&gt;
  &lt;name&gt;Item Three&lt;/name&gt;
&lt;/names&gt;</body></sourcecode>


<sourcecode id="_e55eee0b-149f-1095-b7b9-6463bdd556b1" lang="yaml">
<name>Named collections in YAML with models each containing an attribute <tt>name</tt></name>
<body>---
names:
- name: Item One
- name: Item Two
- name: Item Three</body></sourcecode>


<p id="_4b2ffea7-d77a-804f-0c38-fae0a85722c3">Syntax:</p>

<sourcecode id="_1977d969-b764-ff94-bf36-93bb8ccde872" lang="ruby"><body>class MyCollection &lt; Lutaml::Model::Collection
  instances :items, ModelType

  xml do
    root "name-of-xml-container-element"
  end

  key_value do
    root "name-of-key-value-container-element"
  end
end

class ModelType &lt; Lutaml::Model::Serializable
  attribute :name, :string
end</body></sourcecode>


<p id="_85875f63-37a7-d00b-cd0c-c3114bdcb326">A direct element collection can be alternatively modeled as a “Model class with an attribute” that contains the collection of instances. In this case, the attribute will be an Array object, which does not contain additional attributes and methods.</p>

<example id="_ae3ff3ee-3241-a87b-a0a3-8ab2c73bb78e"><sourcecode id="_9cb90c73-6bee-ed60-e3bc-e601935deac6" lang="ruby"><body>class DirectTitleCollection &lt; Lutaml::Model::Collection
  instances :items, Title

  xml do
    root "titles"
    map_instances to: :items
  end
end

class Title &lt; Lutaml::Model::Serializable
  attribute :title, :string
end</body></sourcecode>  <sourcecode id="_b57db837-3083-72df-3469-a418b9f9ce63" lang="xml"><body>&lt;titles&gt;
  &lt;title&gt;Title One&lt;/title&gt;
  &lt;title&gt;Title Two&lt;/title&gt;
  &lt;title&gt;Title Three&lt;/title&gt;
&lt;/titles&gt;</body></sourcecode>  <sourcecode id="_c8878cb8-0df3-7e57-7c0a-a8edc584694d" lang="yaml"><body>---
titles:
- title: Title One
- title: Title Two
- title: Title Three</body></sourcecode>  <sourcecode id="_ff64131b-298f-ac45-4eb2-f187a353557e" lang="json"><body>{
  "titles": [
    {"title": "Title One"},
    {"title": "Title Two"},
    {"title": "Title Three"}
  ]
}</body></sourcecode>  <sourcecode id="_49ee1364-ceb6-f4e4-2fa0-794243b50c1f" lang="ruby"><body>titles = DirectTitleCollection.from_yaml(yaml_data)
titles.count
# =&gt; 3
titles.first.title
# =&gt; "Title One"
titles.last.title
# =&gt; "Title Three"</body></sourcecode> </example>

<example id="_88da9f3f-732a-dcdb-0082-93a7533e2321"><sourcecode id="_aa92c881-c986-efaf-fd7a-e59bd59027de" lang="ruby"><body>class NameType &lt; Lutaml::Model::Serializable
  attribute :name, :string
end

class NamedCollection &lt; Lutaml::Model::Collection
  instances :names, NameType

  xml do
    root_name "names"
    map_element "item", to: :names
  end

  key_value do
    root_name "names"
    map_instances to: :names
  end
end</body></sourcecode>  <sourcecode id="_4e07d84e-b48c-9624-7889-881556873886" lang="xml"><body>&lt;names&gt;
  &lt;item&gt;
    &lt;name&gt;Item One&lt;/name&gt;
  &lt;/item&gt;
  &lt;item&gt;
    &lt;name&gt;Item Two&lt;/name&gt;
  &lt;/item&gt;
  &lt;item&gt;
    &lt;name&gt;Item Three&lt;/name&gt;
  &lt;/item&gt;
&lt;/names&gt;</body></sourcecode>  <sourcecode id="_7a7b977f-3460-7cd1-f456-74b0df859c04" lang="yaml"><body>---
names:
  - name: Item One
  - name: Item Two
  - name: Item Three</body></sourcecode>  <sourcecode id="_8276fe86-ec99-a16a-928b-abf186c18c05" lang="json"><body>{
  "names": [
    {"name": "Item One"},
    {"name": "Item Two"},
    {"name": "Item Three"}
  ]
}</body></sourcecode> </example>
</clause>

<clause id="_nested_collections" obligation="normative">
<title>Nested collections</title>
<p id="_d57daabe-e776-d9b2-c2a0-945ee862a831">Collections can be nested within other models and define their own serialization rules.</p>

<example id="_ed2c03af-a8dc-03ad-e0ce-60a08d4fc543"><sourcecode id="_7e2290db-7f22-e4bf-6c22-2d90bc0d8a9c" lang="ruby"><body>class Title &lt; Lutaml::Model::Serializable
  attribute :title, :string
end

class TitleCollection &lt; Lutaml::Model::Collection
  instances :items, Title

  xml do
    root "title-group"
    map_element "artifact", to: :items
  end
end

class BibItem &lt; Lutaml::Model::Serializable
  attribute :titles, TitleCollection

  xml do
    root "bibitem"
    # This overrides the collection's root "title-group"
    map_element "titles", to: :titles
  end
end</body></sourcecode>  <sourcecode id="_be49fb31-fdcb-c668-beec-cd817c2104eb" lang="xml"><body>&lt;bibitem&gt;
  &lt;titles&gt;
    &lt;title&gt;Title One&lt;/title&gt;
    &lt;title&gt;Title Two&lt;/title&gt;
    &lt;title&gt;Title Three&lt;/title&gt;
  &lt;/titles&gt;
&lt;/bibitem&gt;</body></sourcecode> </example>
</clause>

<clause id="_keyed_collections_serialization_formats_only" obligation="normative">
<title>Keyed collections (serialization formats only)</title>
<clause id="_general_2" obligation="normative">
<title>General</title>
<p id="_74b6bc60-46f2-97a2-24f3-ca708b3000d6">Keyed collections store instances with unique keys.</p>

<admonition id="_838a7702-b74b-45d6-5f40-88c375f6c6f4" type="warning"><p id="_bfbce016-5022-c2c6-adc5-445d7df89076">Keyed collections were previously handled through<link target="https://github.com/lutaml/lutaml-model#collection-with-keyed-elements-keyed-collection">Collection with keyed elements (keyed collection)</link>. However, this new mechanism is much easier to understand.</p>
</admonition>
<p id="_e554e587-9111-93d5-506d-31d1dc031173">In key-value serialization formats, a key can be used to uniquely identify each
instance. This usage allows for enforcing uniqueness in the collection.<note id="_00d4d80f-9f0d-809a-07a9-e67070de20fe"><p id="_28f89c4d-e417-3770-78d0-56647ed6b3a0">The concept of keyed collections does not typically apply to XML collections.</p>
</note></p>



<p id="_b99eb986-ed5b-6c66-94ad-f8ff9335e93e">There are two types of values in a keyed collection:</p>

<ol id="_62764c0e-bf50-c45b-72ce-e2fda96afc13" type="arabic"><li><p id="_6f68da20-db5c-5650-a2fa-b72e89993af0">When the value is a “model instance”. This is called the “keyed model collection”. Refer to <xref target="keyed-model-collections"/> for more information.</p>
</li>
<li><p id="_7aad1c2b-81c1-1803-a5d4-cc5ca1d4a18f">When the value is a “primitive type”. This is called the “keyed value collection”. Refer to <xref target="keyed-value-collections"/> for more information.</p>
</li>
</ol>

<p id="_355a9983-7b88-62ad-940a-aa656caa4a3b">The mechanism for defining keyed collections is slightly different for both types.</p>
</clause>

<clause id="_map_key_and_map_value" obligation="normative">
<title><tt>map_key</tt> and <tt>map_value</tt></title>
<p id="_6fe465b3-4f34-6eae-8a8f-11b1c201fe1b">The <tt>map_key</tt> method specifies that the unique key is to be moved into an attribute belonging to the instance model.</p>

<p id="_8b3f0bf7-7ee6-2fdb-f03c-762bb41ff8e9">Syntax:</p>

<sourcecode id="_875f6db8-e90c-1242-6cde-dc995053b272" lang="ruby"><body>key_value do
  map_key to_instance: {instance-attribute-name}
end</body></sourcecode>


<p id="_120747f3-998a-6961-ef21-ac596d8556b5">Where,</p>

<dl id="_0fb7b515-9157-15a0-78c8-e55e3855b4a3"><dt><tt>to_instance</tt></dt>
<dd><p id="_e3d3d604-8852-0b20-9f5c-f8ef55ed9280">Refers to the attribute name in the instance that contains the key.</p>
</dd>
<dt><tt>{key_attribute}</tt></dt>
<dd><p id="_b1306ebf-03a8-e357-f52b-e6dc0e1a273e">The attribute name in the instance that contains the key.</p>
</dd>
</dl>

<p id="_767a779a-18ed-e061-c930-9a5e0594c86f">The <tt>map_value</tt> method specifies that the value (the object referenced by the unique key) is to be moved into an attribute belonging to the instance model.</p>

<p id="_f480cf8f-10a8-9cc4-9b63-78eb6d791455">Syntax:</p>

<sourcecode id="_ebfe1d5c-eaae-84a3-201c-51f5f42d7668" lang="ruby"><body>key_value do
  # basic pattern
  map_value {operation}: [*argument]

  # to_instance
  map_value to_instance: {instance-attribute-name}

  # as_instance
  map_value as_attribute: {instance-attribute-name}
end</body></sourcecode>


<p id="_04b0562b-c59c-461b-796e-fc3ea3f6e4cb">Where,</p>

<dl id="_7901153b-6117-c636-56aa-c217a8dd7ebc"><dt><tt>{operation}</tt></dt>
<dd><p id="_390fabac-edb3-27d9-b823-c60295f3464d">The operation to be performed on the key-referenced value. Accepts the following values.</p>
<dl id="_ec66a963-b5a2-9d4e-df98-ece16b2ca3d2"><dt><tt>to_instance</tt></dt>
<dd><p id="_062b22cb-b5b6-80e0-deca-809c94e2b1d2">Each value includes multiple attributes. Map all those attributes into one attribute belonging to the instance model.</p>
</dd>
<dt><tt>as_attribute</tt></dt>
<dd><p id="_8a2fbe4f-4248-3744-212c-8a6e04853f6e">Each value is of a primitive type. Map that primitive type value into an attribute belonging to the instance model.</p>
</dd>
</dl>
</dd>
<dt><tt>{instance-attribute-name}</tt></dt>
<dd><p id="_57ac79a1-0065-6f49-3efb-24301a93fd34">The attribute name in the instance that will contain the value.</p>
</dd>
</dl>
</clause>

<clause id="keyed-model-collections" obligation="normative">
<title>Keyed model collections</title>
<p id="_0c120e92-f26f-9eff-a064-56ccc872e7d4">In keyed model collections, the collection contains multiple model instances. Within the collection, the unique key identifies individual models.</p>

<p id="_2a8759b5-baf3-e7da-aa27-90b78e98bfe8">This can be thought as the case where the unique key is moved into the model instance as a model attribute.</p>

<example id="_001ec1e8-bcb9-3ef5-fee9-a59086f093af">
<name>Sample of a keyed model collection</name>
<sourcecode id="_e7daa4d9-83c9-f91f-cf78-23e6599eb345" lang="yaml"><body>---
author_01:
  name: Author One
author_02:
  name: Author Two
author_03:
  name: Author Three</body></sourcecode>

</example>

<p id="_9048fb7d-beb9-01ac-2f94-95347a15ad71">Here we only need the <tt>map_key</tt> method to define the key attribute in the instance.</p>

<p id="_b7b3d99a-f19f-27ea-8711-9434e7b80d21">Syntax:</p>

<sourcecode id="_ba9d58ec-1866-f0ee-722a-6e5e466395da" lang="ruby"><body>class ModelType &lt; Lutaml::Model::Serializable
  attribute :key_attribute, :string
  # ... additional attributes
end

class KeyedCollection &lt; Lutaml::Model::Collection
  instances :items, ModelType

  key_value do
    map_key to_instance: :key_attribute <callout target="_983a4c32-dfcc-d97b-4ea8-c5c94c2d0313">1</callout>
    map_instances to: :items <callout target="_e92c4fd2-a97e-cbee-e804-c9606b5cd253">2</callout>
  end
end</body><annotation id="_983a4c32-dfcc-d97b-4ea8-c5c94c2d0313"><p id="_77d4f08f-0d10-e936-f1fb-793e61757125">The <tt>:key_attribute</tt> is the attribute name inside the <tt>ModelType</tt> used to uniquely identify each instance.</p>
</annotation><annotation id="_e92c4fd2-a97e-cbee-e804-c9606b5cd253"><p id="_ff9bf6cf-f909-4357-e227-9384f5ec75d8">The <tt>:items</tt> attribute is the collection attribute containing the instances.</p>
</annotation></sourcecode>



<example id="_936a7328-b0bf-0d28-de95-76de4f9cbe6c"><sourcecode id="_e59bbf45-531b-abcb-c586-b3c88e474340" lang="ruby"><body>class Author &lt; Lutaml::Model::Serializable
  attribute :id, :string
  attribute :name, :string
end

class AuthorCollection &lt; Lutaml::Model::Collection
  instances :authors, Author

  key_value do
    map_key to_instance: :id # This refers to 'authors[].id'
    map_instances to: :authors
  end
end</body></sourcecode>  <sourcecode id="_2b85494d-75a0-cf54-f014-ad9874a878d4" lang="yaml"><body>---
author_01:
  name: Author One
author_02:
  name: Author Two
author_03:
  name: Author Three</body></sourcecode>  <sourcecode id="_328116de-499d-4ab3-bcb6-9ca000d6cdb4" lang="json"><body>{
  "author_01": {"name": "Author One"},
  "author_02": {"name": "Author Two"},
  "author_03": {"name": "Author Three"}
}</body></sourcecode>  <sourcecode id="_b211a5f0-86ee-a8d2-91c0-e9576ba3ac33" lang="ruby"><body>authors = AuthorCollection.from_yaml(yaml_data)
authors.first.id
# =&gt; "author_01"
authors.first.name
# =&gt; "Author One"</body></sourcecode> </example>
</clause>
</clause>

<clause id="keyed-value-collections" obligation="normative">
<title>Keyed value collections</title>
<p id="_3227d75b-89e5-659c-c0ac-c9155b54057d">A keyed value collection is a collection of primitive values (not models) that are keyed.</p>

<example id="_45f650af-ef7d-9c2c-3f62-bc602a63e544"><sourcecode id="_d3dc1e6e-f18f-9da0-1b97-112613afce44" lang="yaml"><body>---
author_01: true
author_02: false
author_03: true</body></sourcecode> </example>

<p id="_105197f3-9259-9e51-38a6-7716db4bad84">Here we need to use both <tt>map_key</tt> and <tt>map_value</tt> methods to define the key attribute in the instance.</p>

<p id="_b27a4c6c-ed5e-721c-980c-f1e7b7139b57">Syntax:</p>

<sourcecode id="_ab58acee-5b0c-20c2-273d-7f4a223928ca" lang="ruby"><body>class ModelType &lt; Lutaml::Model::Serializable
  attribute :key_attribute, :string
  # ... additional attributes
end

class KeyedCollection &lt; Lutaml::Model::Collection
  instances :items, ModelType

  key_value do
    map_key to_instance: :key_attribute <callout target="_758cbc89-ddce-699f-38eb-4b9dbb364afe">1</callout>
    map_value as_attribute: :value_attribute <callout target="_7b81d123-3756-de53-96ab-aae69be643b8">2</callout>
    map_instances to: :items <callout target="_fd38b9c3-82a8-bb0a-e428-71aa6e301b13">3</callout>
  end
end</body><annotation id="_758cbc89-ddce-699f-38eb-4b9dbb364afe"><p id="_02e0bbde-bc2a-8a23-0e67-942f310d63bc">The <tt>:key_attribute</tt> is the attribute name inside the <tt>ModelType</tt> used to
uniquely identify each instance.</p>
</annotation><annotation id="_7b81d123-3756-de53-96ab-aae69be643b8"><p id="_87a862e9-279c-7c81-06d8-20fb7347d320">The <tt>:value_attribute</tt> is the attribute name inside the <tt>ModelType</tt> used to
uniquely identify each instance.</p>
</annotation><annotation id="_fd38b9c3-82a8-bb0a-e428-71aa6e301b13"><p id="_a2fe138a-d71d-b95b-4e0f-cc5db0556bb9">The <tt>:items</tt> attribute is the collection attribute containing the instances.</p>
</annotation></sourcecode>



<example id="_fe36d21c-9a7e-358b-37dd-271195624506"><sourcecode id="_43bddb39-5e74-6bf2-8b4d-bc143a0fe603" lang="ruby"><body>class AuthorAvailability &lt; Lutaml::Model::Serializable
  attribute :id, :string
  attribute :available, :boolean
end

class AuthorCollection &lt; Lutaml::Model::Collection
  instances :authors, AuthorAvailability

  key_value do
    map_key to_instance: :id # This refers to 'authors[].id'
    map_value as_attribute: :available # This refers to 'authors[].available'
    map_instances to: :authors
  end
end</body></sourcecode>  <sourcecode id="_2a1f995c-d3d3-b9dc-e02f-1a251f99f082" lang="yaml"><body>---
author_01: true
author_02: false
author_03: true</body></sourcecode>  <sourcecode id="_3af596b1-8f5a-042f-82d8-94ea218992dd" lang="json"><body>{
  "author_01": true,
  "author_02": false,
  "author_03": true
}</body></sourcecode>  <sourcecode id="_2dbe91ff-894b-f039-3023-ddf141fd1646" lang="ruby"><body>authors = AuthorCollection.from_yaml(yaml_data)
authors.first.id
# =&gt; "author_01"
authors.first.available
# =&gt; true</body></sourcecode> </example>
</clause>
</clause>

<clause id="_collection_serialization" obligation="normative">
<title>Collection serialization</title>
<clause id="_general_3" obligation="normative">
<title>General</title>
<p id="_d4085185-3dbb-06fa-593d-c107e61a956d">Collections support multiple serialization formats through format-specific mapping rules.</p>
</clause>

<clause id="_xml_serialization" obligation="normative">
<title>XML serialization</title>
<p id="_8a0f8ce7-cea5-c54c-b3b1-aa798ddedb0a">XML serialization defines how collection elements are represented in XML.</p>

<p id="_c4b7130e-93b5-3091-1d8b-30437f9af01c">The <tt>xml</tt> block is used to define XML serialization rules for the collection.</p>

<p id="_7fc93c6f-fda7-51ac-5bea-02af9a1960e4">In a collection, the following directives are available:</p>

<ul id="_0b83fe75-ecb2-a625-ad09-7dbeb7afcb1e"><li><p id="_d48d1a10-1af5-7db5-6d94-7b84082a6f4b"><tt>root</tt> — Specifies the XML container element name</p>
</li>
<li><p id="_39e70205-b414-62d8-9516-737ba2354091"><tt>no_root</tt> — Disables the root element for the collection</p>
</li>
<li><p id="_ffc4f4ae-30dd-4e72-f197-894bdf052648"><tt>map_element</tt> — Specifies how individual elements are represented</p>
</li>
<li><p id="_73a90139-1fbd-4ef6-1ff1-de1029cc5387"><tt>map_attribute</tt> — Maps an attribute to the XML output</p>
</li>
<li><p id="_52a880c1-4ddf-4d8d-a2f3-670bead91b3b"><tt>map_instances</tt> — Maps the collection instances to the XML output</p>
</li>
</ul>

<example id="_49b56343-8e91-0b6d-5d50-328cf0081db0"><p id="_eead5ab4-77d2-3604-97e1-4e25b75d26bd">The <tt>root</tt> directive specifies the XML container element name.</p>

<sourcecode id="_a7dfce17-c369-f198-ce60-b696db3476c3" lang="ruby"><body>xml do
  root "container-name"
end</body></sourcecode>

</example>

<example id="_b0dff0f5-43bf-0bcc-1e16-938fcfd817b6"><p id="_f8556aca-5318-8ca3-4ace-5d8aa921af24">The <tt>map_element</tt> directive specifies how individual elements are represented.</p>

<sourcecode id="_c0462144-b47b-e726-ea4d-ba66290b0e6f" lang="ruby"><body>xml do
  map_element "element-name", to: :collection_attribute
end</body></sourcecode>

</example>

<example id="_a70e1a8a-9017-57e2-4250-edb1d608cef1"><p id="_e0312656-1b8c-1cbf-6b98-15d966f62fe8">The <tt>map_attribute</tt> directive maps an attribute to the XML output.</p>

<sourcecode id="_2b93f92a-e388-e852-1f12-c7d56d05f35d" lang="ruby"><body>xml do
  map_attribute "attribute-name", to: :attribute_name
end</body></sourcecode>

</example>
</clause>

<clause id="_yaml_serialization" obligation="normative">
<title>YAML serialization</title>
<p id="_6b029422-df08-9e99-47b1-9818424c8148">YAML serialization defines how collection elements are represented in YAML.</p>

<example id="_c5dd691a-4de4-8485-0c3e-b91c20077892"><sourcecode id="_c4c4720e-e18a-351c-019d-d052dc8362d7" lang="ruby"><body>class AuthorCollection &lt; Lutaml::Model::Collection
  instances :items, Author

  yaml do
    map_instances :items
  end
end</body></sourcecode> </example>

<p id="_cc58cf6e-17b2-1be0-f081-e4a2ecdac724">Produces:</p>

<sourcecode id="_d1f25ac6-eed8-6f22-e0bb-19c38fa096ce" lang="yaml"><body>---
authors:
  - name: Author 1
    bio: Bio 1
  - name: Author 2
    bio: Bio 2</body></sourcecode>

</clause>

<clause id="_json_serialization" obligation="normative">
<title>JSON serialization</title>
<p id="_0beb2fe0-79b3-30a5-a0dd-d16acb15e516">JSON serialization defines how collection elements are represented in JSON.</p>

<example id="_254b5d29-a229-5cf7-21bd-4bc83a503cfc"><sourcecode id="_c1d518c0-ea55-bf30-d167-92729b605082" lang="ruby"><body>class AuthorCollection &lt; Lutaml::Model::Collection
  instances :items, Author

  json do
    root_key "authors"
    map_instances :items
  end
end</body></sourcecode>


<p id="_d144d790-5e25-cfb2-609a-0486344b48e9">Produces:</p>

<sourcecode id="_55303de4-7e42-0dc0-eb11-585b79be46ae" lang="json"><body>{
  "authors": [
    {"name": "Author 1", "bio": "Bio 1"},
    {"name": "Author 2", "bio": "Bio 2"}
  ]
}</body></sourcecode>

</example>
</clause>
</clause>

<clause id="_collection_mapping_rules" obligation="normative">
<title>Collection mapping rules</title>
<clause id="_general_4" obligation="normative">
<title>General</title>
<p id="_c0d38ed7-ee29-7b33-20c4-4281d840c13a">Collection mapping rules determine how collection elements are serialized and deserialized.</p>
</clause>

<clause id="_root_element_override" obligation="normative">
<title>Root element override</title>
<p id="_504be507-2da1-6ed5-5f15-26c315165584">The root element name can be overridden at the collection usage point.</p>

<example id="_316e5034-040f-9037-f29e-9e83da68f134"><sourcecode id="_7d534df6-62cb-799f-d8f4-8744161d8b4d" lang="ruby"><body>class Title &lt; Lutaml::Model::Serializable
  attribute :title, :string
end

class TitleCollection &lt; Lutaml::Model::Collection
  instances :items, Title

  xml do
    root "title-group"
    map_element "artifact", to: :items
  end
end

class BibItem &lt; Lutaml::Model::Serializable
  attribute :titles, TitleCollection

  xml do
    root "bibitem"
    # This overrides the collection's root "title-group"
    map_element "titles", to: :titles
  end
end</body></sourcecode>  <sourcecode id="_79c08b49-1e44-af70-b2b3-9cbd0bcb3e16" lang="xml"><body>&lt;bibitem&gt;
  &lt;titles&gt;
    &lt;title&gt;Title One&lt;/title&gt;
    &lt;title&gt;Title Two&lt;/title&gt;
    &lt;title&gt;Title Three&lt;/title&gt;
  &lt;/titles&gt;
&lt;/bibitem&gt;</body></sourcecode> </example>

<example id="_3f858900-84f0-fa43-7301-d969e2c425fb"><sourcecode id="_1a11e653-2699-22f7-96f6-941ed8c20eef" lang="ruby"><body>class Title &lt; Lutaml::Model::Serializable
  attribute :title, :string
end

class TitleCollection &lt; Lutaml::Model::Collection
  instances :items, Title

  xml do
    root "title-group"
    # This overrides the element's root "title"
    map_element "artifact", to: :items
  end
end

class BibItem &lt; Lutaml::Model::Serializable
  attribute :titles, TitleCollection

  xml do
    root "bibitem"
    map_element "title-group", to: :titles
  end
end</body></sourcecode>  <sourcecode id="_0f50c719-b6ed-0697-198f-5e9a1f6b30bd" lang="xml"><body>&lt;bibitem&gt;
  &lt;title-group&gt;
    &lt;artifact&gt;Title One&lt;/artifact&gt;
    &lt;artifact&gt;Title Two&lt;/artifact&gt;
    &lt;artifact&gt;Title Three&lt;/artifact&gt;
  &lt;/title-group&gt;
&lt;/bibitem&gt;</body></sourcecode> </example>
</clause>
</clause>

<clause id="_collection_behaviors" obligation="normative">
<title>Collection behaviors</title>
<clause id="_enumerable_interface" obligation="normative">
<title>Enumerable interface</title>
<p id="_836b4f3a-0481-b999-c224-dc1ffed679f0">Collections implement the Ruby <tt>Enumerable</tt> interface, providing standard collection operations.</p>

<p id="_1de76a94-323a-7714-9f5e-bddb1be420be">Collections allows the following sample <tt>Enumerable</tt> methods:</p>

<ul id="_e2a4565a-c974-5749-9162-98d5358455a7"><li><p id="_4aabd946-63cd-65be-26a1-d346b92b9efb"><tt>each</tt> — Iterate over collection items</p>
</li>
<li><p id="_f0567064-03f9-be1f-c090-e2563d73b1ec"><tt>map</tt> — Transform collection items</p>
</li>
<li><p id="_385b9fb7-1df7-1a40-f782-0b730215119f"><tt>select</tt> — Filter collection items</p>
</li>
<li><p id="_382b69af-f23b-7a25-6fd2-3a68b0453225"><tt>find</tt> — Find items matching criteria</p>
</li>
<li><p id="_0fda0b22-cf25-8921-a2a3-a7807ad890ab"><tt>reduce</tt> — Aggregate collection items</p>
</li>
</ul>

<example id="_305cb8c4-011c-318c-e9b4-c1a5f21d38d6"><sourcecode id="_0d1c6963-82ec-b34a-c6a8-5be8a24ac5ff" lang="ruby"><body>class AuthorCollection &lt; Lutaml::Model::Collection
  instances :items, Author
end

authors = AuthorCollection.new

# Iterate
authors.each { |author| puts author.name }

# Transform
author_names = authors.map { |author| author.name }

# Filter
active_authors = authors.select { |author| author.active? }</body></sourcecode> </example>
</clause>

<clause id="_collection_validation" obligation="normative">
<title>Collection validation</title>
<p id="_75a0dc81-9bd8-d904-3483-c304bb964230">Collections can define validation rules for their elements.</p>

<p id="_cfb80c6f-16f1-2c0d-ac4c-d398953950b2">Syntax:</p>

<sourcecode id="_6b1358da-01bf-49b6-296d-a25f88d96829" lang="ruby"><body>class ValidatedCollection &lt; Lutaml::Model::Collection
  instances :items, ModelType do
    validates :attribute, presence: true
    validate :custom_validation
  end
end</body></sourcecode>


<example id="_ee2f2c59-2ead-9824-639e-7b4058f3135e"><sourcecode id="_18a8aaee-4493-7c2f-016b-53ace3a654cf" lang="ruby"><body>class PublicationCollection &lt; Lutaml::Model::Collection
  instances :items, Publication do
    validates :title, presence: true
    validates :year, numericality: { greater_than: 1900 }

    validate :must_have_author

    def must_have_author
      errors.add(:base, "Publication must have an author") unless author.present?
    end
  end
end</body></sourcecode> </example>
</clause>

<clause id="_collection_initialization" obligation="normative">
<title>Collection initialization</title>
<p id="_60271d9b-729a-a169-34ef-f3029d22f1d1">Collections can be initialized with an array of items or through individual item addition.</p>

<example id="_d374b0a5-edb5-b947-45ff-ff3412e70374"><sourcecode id="_9063de3a-e4ae-0898-6aa7-0af287a0a025" lang="ruby"><body>class AuthorCollection &lt; Lutaml::Model::Collection
  instances :items, Author
end

authors = AuthorCollection.new([
  Author.new(name: "Author 1"),
  Author.new(name: "Author 2")
])</body></sourcecode> </example>

<example id="_c13b154f-a08d-4eb8-c3c5-2dce11009197"><sourcecode id="_751f8a41-dcf9-8bec-dc07-dd93ed2695df" lang="ruby"><body>authors = AuthorCollection.new
authors &lt;&lt; Author.new(name: "Author 1")
authors.push(Author.new(name: "Author 2"))</body></sourcecode> </example>
</clause>
</clause>

<clause id="_advanced_collection_features" obligation="normative">
<title>Advanced collection features</title>
<clause id="_ordered_collections" obligation="normative">
<title>Ordered collections</title>
<p id="_21ed322b-1513-2459-b5eb-3a272e2e369e">Collections that maintain a specific ordering of elements.</p>

<example id="_ac160d00-fd3e-14a7-9192-55cce7f0052b"><sourcecode id="_81d99fbe-aaba-37d8-c505-58bc7e82c179" lang="ruby"><body>class OrderedCollection &lt; Lutaml::Model::Collection
  instances :items, ModelType
  ordered by: "date", order: :desc
end

class ModelType &lt; Lutaml::Model::Serializable
  attribute :date, :datetime
end</body></sourcecode> </example>
</clause>
</clause>

<clause id="_operations" obligation="normative">
<title>Operations</title>
<clause id="_collection_level_operations" obligation="normative">
<title>Collection-level operations</title>
<p id="_b0719431-93c9-8d45-0fae-d845150f2713">Collections can be combined using set operations to create new collections.</p>

<p id="_361cff2b-5c85-1ad7-c3f1-104d18020924">Syntax:</p>

<sourcecode id="_0efa253b-e039-8552-4ee3-2fe187e2eee5" lang="ruby"><body># Union
collection1.union(collection2)

# Intersection
collection1.intersection(collection2)

# Difference
collection1.difference(collection2)</body></sourcecode>


<example id="_ea87fc84-809c-d703-1d2d-9e9d211fd9a2"><sourcecode id="_1cd6bb5d-6faa-1e21-a85c-700b4f9ab2e8" lang="ruby"><body>class AuthorCollection &lt; Lutaml::Model::Collection
  instances :items, Author
end

# Usage
authors1 = AuthorCollection.new([
  Author.new(name: "Author 1"),
  Author.new(name: "Author 2")
])

authors2 = AuthorCollection.new([
  Author.new(name: "Author 2"),
  Author.new(name: "Author 3")
])

combined = authors1.union(authors2)
common = authors1.intersection(authors2)
unique = authors1.difference(authors2)</body></sourcecode> </example>
</clause>

<clause id="_enumerable_methods" obligation="normative">
<title>Enumerable methods</title>
<p id="_597e8bcb-97a6-cec1-a5c0-67b177f0d65a">Collections inherit from Enumerable and support standard enumeration methods.</p>

<p id="_304af227-7587-e07a-66ba-faa7c02a083b">Collections can be filtered using predicate methods to create new collections.</p>

<p id="_d4151afe-7907-961c-3a6b-ba3434b63a10">Syntax:</p>

<sourcecode id="_36d5b14c-4043-dde5-449c-d3ea988cea87" lang="ruby"><body>collection.filter(predicate)
collection.reject(predicate)
collection.select(predicate)</body></sourcecode>


<example id="_d9d0bfa9-45d4-eda2-7642-75b08531f10b"><sourcecode id="_36dd358d-7dbb-63bb-05b1-06138f77e91e" lang="ruby"><body>class PublicationCollection &lt; Lutaml::Model::Collection
  instances :items, Publication

  def published
    self.class.new(
      items.select { |item| item.status == 'published' }
    )
  end

  def by_year(year)
    self.class.new(
      items.select { |item| item.year == year }
    )
  end

  def by_author(author_name)
    self.class.new(
      items.select { |item| item.author == author_name }
    )
  end
end

# Usage
publications = PublicationCollection.new(items)
published_2023 = publications.published.by_year(2023)</body></sourcecode> </example>

<p id="_14fc657e-5132-0272-66b9-e524eb564a27">Collections can be transformed using mapping methods to create new collections.</p>

<p id="_9b08109a-e919-e406-c0d1-8ad76de0a61a">Syntax:</p>

<sourcecode id="_99b6220d-a9eb-979f-f1b3-fdb09de5e453" lang="ruby"><body>collection.count
collection.sum(attribute)
collection.average(attribute)
collection.group_by(attribute)</body></sourcecode>


<example id="_427e176a-fd56-1241-c336-2dc1d9d59299"><sourcecode id="_bdfc83e8-b2f7-20f0-2a87-870af5d71347" lang="ruby"><body>class PublicationCollection &lt; Lutaml::Model::Collection
  instances :items, Publication

  def total_citations
    items.sum(&amp;:citation_count)
  end

  def average_rating
    items.sum(&amp;:rating).to_f / items.count
  end

  def by_category
    items.group_by(&amp;:category)
  end

  def statistics
    {
      total_items: count,
      total_citations: total_citations,
      average_rating: average_rating,
      by_status: items.group_by(&amp;:status).transform_values(&amp;:count)
    }
  end
end</body></sourcecode> </example>

<p id="_73e59cec-af22-ba7c-c9f2-ead384aee70a">Collections can be transformed while maintaining their collection nature.</p>

<p id="_6e72106b-c4d7-1004-652f-4cedfdce5248">Syntax:</p>

<sourcecode id="_1a5bb0b3-3bce-5ab3-75b6-ff19924c0de6" lang="ruby"><body>collection.map(transform)
collection.flat_map(transform)</body></sourcecode>


<example id="_88c83727-cb8e-e8f2-c799-7d83473a804f"><sourcecode id="_fb461c0d-07d0-7b55-bb33-56f13f5bb51d" lang="ruby"><body>class CitationCollection &lt; Lutaml::Model::Collection
  instances :items, Citation

  def to_references
    ReferenceCollection.new(
      items.map { |citation| citation.to_reference }
    )
  end

  def normalize
    items.each do |citation|
      citation.normalize!
    end
    self
  end
end</body></sourcecode> </example>
</clause>
</clause>


</sections><annex id="_tutorial_building_a_car_parts_database" obligation="normative">
<title>Tutorial: Building a car parts database</title>
<p id="_607fdd9a-f35c-585f-7479-e5304b947774">This tutorial demonstrates building a car parts database using LutaML collections, progressively adding features to show different collection capabilities.</p>

<clause id="_step_1_basic_collection" obligation="normative">
<title>Step 1: Basic collection</title>
<ul id="_577408d3-fc21-73a9-0f69-270cd8f2b2fb">
<name>Learning outcomes</name>
<li><p id="_4b97183d-6029-8d6d-6604-35016fa78c64">Create a basic LutaML model</p>
</li>
<li><p id="_1d4b197a-8b8a-7df0-7506-ffda2690dbcc">Instantiate a simple collection</p>
</li>
<li><p id="_bdbe790e-22af-d73b-e86f-c5443a2c23c9">Use basic enumeration methods</p>
</li>
</ul>

<example id="_7d92342b-bdfa-0bd9-2979-0dbc682c453f"><p id="_0e5ca4ca-c77a-8ead-9f69-94a59b7884a4">First, define the basic model:</p>

<sourcecode id="_80ce6e4d-b17e-d5d9-813a-937ced00f7d6" lang="ruby"><body>class CarPart &lt; Lutaml::Model::Serializable
  attribute :name, :string
  attribute :description, :string
  attribute :price, :float
end</body></sourcecode>


<p id="_05a83887-e906-14f0-724b-21e9fc62cdef">Then create a simple collection:</p>

<sourcecode id="_98acfc12-4525-0c77-5bba-174da542dee1" lang="ruby"><body>class PartsCollection &lt; Lutaml::Model::Collection
  instances :parts, CarPart
end

# Create and use the collection
parts = PartsCollection.new([
  CarPart.new(name: "Engine Block", price: 1500.0),
  CarPart.new(name: "Brake Pad", price: 50.0)
])

# Basic enumeration
parts.each { |part| puts part.name }
parts.count  # =&gt; 2</body></sourcecode>

</example>

<ul id="_cf43a182-a9f2-0cb1-871f-3b58b2059cdc">
<name>Key points</name>
<li><p id="_9fd9339a-63c9-65df-63e3-cbb00e0c7b3f">Collections inherit from Enumerable</p>
</li>
<li><p id="_f79382bb-67d6-2484-cbcb-54e89ce6a273">Collections maintain type safety through the <tt>instances</tt> declaration</p>
</li>
<li><p id="_25a3b731-f247-c9f6-8bbd-5e70f87bea41">Basic enumeration methods are available out of the box</p>
</li>
</ul>
</clause>

<clause id="_step_2_adding_serialization" obligation="normative">
<title>Step 2: Adding serialization</title>
<ul id="_4d47ce52-c34a-09d3-4f33-ae2389f50363">
<name>Learning outcomes</name>
<li><p id="_90252bbb-ec97-e681-2b60-d787e2272517">Configure XML serialization</p>
</li>
<li><p id="_f62033dc-89e8-2d00-1834-b699ca13691d">Configure YAML serialization</p>
</li>
<li><p id="_8078d3ed-d7e3-db2e-cff5-a4c10739d6a8">Understand format mapping differences</p>
</li>
</ul>

<example id="_66e8011c-aa3d-5f3f-593c-4946cad89838"><p id="_30b06f8a-ab55-cf68-a4bc-dca632f0bcd9">Enhance the collection with serialization rules:</p>

<sourcecode id="_af72edf5-cb28-6aa1-fe82-d67d5034a37d" lang="ruby"><body>class PartsCollection &lt; Lutaml::Model::Collection
  instances :parts, CarPart

  xml do
    root "parts-catalog"
    map_element "part", to: :parts
  end

  yaml do
    sequence "parts"
  end
end

# Create some parts
parts = PartsCollection.new([
  CarPart.new(name: "Engine Block", price: 1500.0),
  CarPart.new(name: "Brake Pad", price: 50.0)
])

# XML output
parts.to_xml</body></sourcecode>


<p id="_6d446cca-3d87-383e-532f-37da1289b0d7">Produces:</p>

<sourcecode id="_b1ae07c9-76c8-dbfb-0cd4-ce16cbad28c1" lang="xml"><body>&lt;parts-catalog&gt;
  &lt;part&gt;
    &lt;name&gt;Engine Block&lt;/name&gt;
    &lt;price&gt;1500.0&lt;/price&gt;
  &lt;/part&gt;
  &lt;part&gt;
    &lt;name&gt;Brake Pad&lt;/name&gt;
    &lt;price&gt;50.0&lt;/price&gt;
  &lt;/part&gt;
&lt;/parts-catalog&gt;</body></sourcecode>


<sourcecode id="_9ead7be8-1865-6c3a-6ba9-3402555c7652" lang="yaml"><body>parts:
  - name: Engine Block
    price: 1500.0
  - name: Brake Pad
    price: 50.0</body></sourcecode>

</example>

<ul id="_41fe14d0-c6fb-77c5-4c93-7ee9a15c6854">
<name>Key points</name>
<li><p id="_da33ae91-91a3-3d69-c1b2-b0187cdeddd4">Collections can support multiple serialization formats simultaneously</p>
</li>
<li><p id="_89d2ba59-b270-25a6-a68d-b0fd49c18981">Each format can have its own mapping rules</p>
</li>
<li><p id="_e967a144-3121-b95c-be60-f395681b6a13">Root elements and naming can be customized per format</p>
</li>
</ul>
</clause>

<clause id="_step_3_collection_operations" obligation="normative">
<title>Step 3: Collection operations</title>
<ul id="_eda32966-c393-0854-6825-909d2b3b880f">
<name>Learning outcomes</name>
<li><p id="_5d1e70d3-f1e7-4bd9-8c6c-ef82831798f6">Implement filtering methods</p>
</li>
<li><p id="_5a2f8cea-aba8-8558-fe30-0c54bcb7ce22">Add aggregation calculations</p>
</li>
<li><p id="_f9960e38-1e88-3d9f-04fa-a6df067482e5">Chain collection operations</p>
</li>
</ul>

<example id="_fd01f69b-325b-c628-db01-348ba25f39c7"><p id="_cdf87c6e-c2f0-3f7a-e602-e6cbddb39952">Add operation methods to the collection:</p>

<sourcecode id="_19be237d-25e8-560e-9c24-b8bd214bebeb" lang="ruby"><body>class PartsCollection &lt; Lutaml::Model::Collection
  instances :parts, CarPart

  # ... existing serialization code ...

  def expensive_parts(threshold = 1000.0)
    self.class.new(
      parts.select { |part| part.price &gt; threshold }
    )
  end

  def total_value
    parts.sum(&amp;:price)
  end

  def price_stats
    {
      total: total_value,
      average: total_value / count,
      max: parts.map(&amp;:price).max,
      min: parts.map(&amp;:price).min
    }
  end
end

# Usage
parts = PartsCollection.new([
  CarPart.new(name: "Engine Block", price: 1500.0),
  CarPart.new(name: "Brake Pad", price: 50.0),
  CarPart.new(name: "Transmission", price: 1200.0)
])

expensive = parts.expensive_parts
puts parts.price_stats</body></sourcecode>

</example>

<ul id="_c911a783-0191-32cb-aec6-3b6cba650756">
<name>Key points</name>
<li><p id="_4612f2a0-4f79-f6ca-5cf6-a00c8aa89b2f">Collection operations return new collection instances</p>
</li>
<li><p id="_4f922566-b481-dd94-1565-c110aa83a272">Operations can be chained</p>
</li>
<li><p id="_aa13a8e2-7bcb-3bbc-6797-a7b532959503">Statistical operations are easily implemented using Enumerable methods</p>
</li>
</ul>
</clause>

<clause id="_step_4_nested_collections" obligation="normative">
<title>Step 4: Nested collections</title>
<ul id="_8589d79f-ba0c-4bae-f0b6-3d3541488216">
<name>Learning outcomes</name>
<li><p id="_240e3049-7212-4fd1-6c0b-7c3cef94dc68">Create hierarchical data structures</p>
</li>
<li><p id="_52e5b097-bde7-6e0f-26f1-a023470ea0c3">Configure nested serialization</p>
</li>
<li><p id="_649c4aff-4f4b-7c58-5ba3-265b14aa498f">Handle complex data relationships</p>
</li>
</ul>

<example id="_06b6acc8-24fd-b691-1ec9-411ffcccda73"><p id="_2c7432bb-0517-a0ff-b003-6211b8549421">First, add a category model:</p>

<sourcecode id="_5fe98606-a2d0-ba8b-bd88-d2451ee46a19" lang="ruby"><body>class Category &lt; Lutaml::Model::Serializable
  attribute :name, :string
  attribute :description, :string
end

class PartCategory &lt; Lutaml::Model::Serializable
  attribute :category, Category
  attribute :parts, PartsCollection
end

class CategorizedPartsCollection &lt; Lutaml::Model::Collection
  instances :categories, PartCategory

  xml do
    root "parts-catalog"
    map_element "category", to: :categories do
      map_element "name", to: "category.name"
      map_element "parts", to: :parts
    end
  end
end</body></sourcecode>


<p id="_cefd12c4-f567-6c9f-c545-e0151771e3c3">Usage:</p>

<sourcecode id="_b67ad0a8-f79a-1fad-b6f5-b703f4917c63" lang="ruby"><body>engine_parts = PartsCollection.new([
  CarPart.new(name: "Engine Block", price: 1500.0),
  CarPart.new(name: "Piston", price: 100.0)
])

brake_parts = PartsCollection.new([
  CarPart.new(name: "Brake Pad", price: 50.0),
  CarPart.new(name: "Rotor", price: 75.0)
])

catalog = CategorizedPartsCollection.new([
  PartCategory.new(
    category: Category.new(name: "Engine"),
    parts: engine_parts
  ),
  PartCategory.new(
    category: Category.new(name: "Brakes"),
    parts: brake_parts
  )
])</body></sourcecode>


<p id="_617e4265-c599-56b3-5947-85faa07cc460">This produces:</p>

<sourcecode id="_33abbb22-c33d-cc12-d690-240ba52a6ca8" lang="xml"><body>&lt;parts-catalog&gt;
  &lt;category&gt;
    &lt;name&gt;Engine&lt;/name&gt;
    &lt;parts&gt;
      &lt;part&gt;
        &lt;name&gt;Engine Block&lt;/name&gt;
        &lt;price&gt;1500.0&lt;/price&gt;
      &lt;/part&gt;
      &lt;part&gt;
        &lt;name&gt;Piston&lt;/name&gt;
        &lt;price&gt;100.0&lt;/price&gt;
      &lt;/part&gt;
    &lt;/parts&gt;
  &lt;/category&gt;
  &lt;category&gt;
    &lt;name&gt;Brakes&lt;/name&gt;
    &lt;parts&gt;
      &lt;part&gt;
        &lt;name&gt;Brake Pad&lt;/name&gt;
        &lt;price&gt;50.0&lt;/price&gt;
      &lt;/part&gt;
      &lt;part&gt;
        &lt;name&gt;Rotor&lt;/name&gt;
        &lt;price&gt;75.0&lt;/price&gt;
      &lt;/part&gt;
    &lt;/parts&gt;
  &lt;/category&gt;
&lt;/parts-catalog&gt;</body></sourcecode>

</example>

<ul id="_c78f57ad-55bc-9c0c-dc4d-4cc938044283">
<name>Key points</name>
<li><p id="_3c5393a9-7bcd-25cb-4d92-010b6cfd0ed6">Collections can contain other collections</p>
</li>
<li><p id="_e4b76c44-83cc-6ff1-9362-9176ecbd1121">Serialization rules cascade through the hierarchy</p>
</li>
<li><p id="_0380d428-82c8-830a-a23d-c9f30183b3e4">Complex relationships can be modeled naturally</p>
</li>
</ul>
</clause>

<clause id="_step_5_keyed_collections" obligation="normative">
<title>Step 5: Keyed collections</title>
<ul id="_82c6ad65-57ba-4209-97e1-28819280dec3">
<name>Learning outcomes</name>
<li><p id="_939c380d-bdd4-b304-6f0c-5f2f44ba9e8c">Implement unique identifiers</p>
</li>
<li><p id="_efcbb126-a28b-7a65-d297-d4a590284355">Convert to keyed collection</p>
</li>
<li><p id="_5dd4d93c-3611-8d3b-b4ef-dc8d9360c637">Add efficient lookup methods</p>
</li>
</ul>

<example id="_642ab30a-ebf4-21fb-dac6-af1190ddd0dc"><p id="_7f3236b3-9fae-b1c1-aa94-969af8ea67fb">Enhance the CarPart model with an identifier:</p>

<sourcecode id="_c2b01fda-d6cc-09d6-5be1-7f77d3fdb9ee" lang="ruby"><body>class CarPart &lt; Lutaml::Model::Serializable
  attribute :id, :string
  attribute :name, :string
  attribute :price, :float
end

class KeyedPartsCollection &lt; Lutaml::Model::Collection
  instances :parts, CarPart

  key_value do
    map_key to: :id
    map_instances to: :parts
  end

  xml do
    root "parts-catalog"
    map_element "part", to: :parts do
      map_attribute "id", to: :id
    end
  end

  def find_part(id)
    parts.find { |part| part.id == id }
  end

  def find_parts_by_ids(ids)
    self.class.new(
      parts.select { |part| ids.include?(part.id) }
    )
  end
end</body></sourcecode>


<p id="_d4b80c35-4740-5fbd-7693-4d99c25c6c94">Usage:</p>

<sourcecode id="_b4ce25b7-304a-bedc-3a6a-367779c0cdf5" lang="ruby"><body>parts = KeyedPartsCollection.new([
  CarPart.new(id: "ENG001", name: "Engine Block", price: 1500.0),
  CarPart.new(id: "BRK002", name: "Brake Pad", price: 50.0)
])

# YAML representation</body></sourcecode>


<p id="_2b049c97-6811-9632-9307-e5ba1e8532c6">Produces:</p>

<sourcecode id="_4b62a82f-e344-c72d-c3fe-b29ca9e32215" lang="yaml"><body>ENG001:
  name: Engine Block
  price: 1500.0
BRK002:
  name: Brake Pad
  price: 50.0</body></sourcecode>


<p id="_ddf928d0-6d90-ea0b-f5c3-c71e782f0ce1">XML representation:</p>

<sourcecode id="_9ae36cf2-4e89-1b7b-f4c1-f98b460a645e" lang="xml"><body>&lt;parts-catalog&gt;
  &lt;part id="ENG001"&gt;
    &lt;name&gt;Engine Block&lt;/name&gt;
    &lt;price&gt;1500.0&lt;/price&gt;
  &lt;/part&gt;
  &lt;part id="BRK002"&gt;
    &lt;name&gt;Brake Pad&lt;/name&gt;
    &lt;price&gt;50.0&lt;/price&gt;
  &lt;/part&gt;
&lt;/parts-catalog&gt;</body></sourcecode>


<p id="_9edbe345-d413-b9df-858a-bf596fac3b83">Example lookups:</p>

<sourcecode id="_9285a9ef-d767-74ee-3a64-5fb6104b1704" lang="ruby"><body># Find a single part
engine = parts.find_part("ENG001")

# Find multiple parts
brake_engine = parts.find_parts_by_ids(["BRK002", "ENG001"])</body></sourcecode>

</example>

<ul id="_ea9ea142-e5e6-ef80-d2e3-94c85c9c7d70">
<name>Key points</name>
<li><p id="_f4d2fcb8-b88b-f67d-6c1e-5cefba85622b">Keys provide efficient lookup capabilities</p>
</li>
<li><p id="_10d8f55d-a6ac-cab4-32f8-6299d02901d2">Different serialization formats can represent keys differently</p>
</li>
<li><p id="_6b1f75e7-ecc1-f5a2-57a0-5e14edd0e313">Keyed collections maintain referential integrity</p>
</li>
</ul>
</clause>

<clause id="_summary" obligation="normative">
<title>Summary</title>
<p id="_26ab43be-b31f-6c56-ff87-8181a01e6712">This tutorial demonstrated:</p>

<ul id="_db236d20-46e7-0f1b-c068-4620e7fb2231"><li><p id="_2b2c1f66-7f1c-82ca-9ee9-48e298172052">Basic collection creation and usage</p>
</li>
<li><p id="_12ce5fc4-139b-4e4a-45fa-765258461223">Multiple serialization format support</p>
</li>
<li><p id="_5fa524cd-9d51-4b0f-ec42-9afaca037cec">Collection operations and aggregation</p>
</li>
<li><p id="_9f60fe5e-d6d9-f070-e3d5-e1798af57335">Nested collection hierarchies</p>
</li>
<li><p id="_03a7f212-1fe3-e652-2ae7-3ee9aa16012e">Keyed collection lookup patterns</p>
</li>
</ul>

<p id="_7038b317-6cc1-89e5-127d-8d8c1f2cf82e">The progression from simple to complex features shows how LutaML collections can be used to build sophisticated data management systems.</p>
</clause>
</annex><bibliography><references id="_normative_references" normative="true" obligation="informative">
<title>Normative references</title><p id="_cf4fafa1-7328-7d25-f309-bb194339feef">There are no normative references in this document.</p>

</references></bibliography>
</metanorma>
