Namespace practices
Use XmlNamespace classes for structured projects
Do:
# Define namespace once
class ProjectNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/project/v1'
prefix_default 'proj'
element_form_default :qualified
version '1.0'
documentation "Project schema"
end
# Reuse across many models
class Model1 < Lutaml::Model::Serializable
xml do
element 'model1'
namespace ProjectNamespace
end
end
class Model2 < Lutaml::Model::Serializable
xml do
element 'model2'
namespace ProjectNamespace
end
endDon’t:
# Repeat namespace details in every model
class Model1 < Lutaml::Model::Serializable
xml do
root 'model1'
namespace 'https://example.com/project/v1', 'proj'
end
end
class Model2 < Lutaml::Model::Serializable
xml do
root 'model2'
namespace 'https://example.com/project/v1', 'proj'
end
endFollow W3C qualification conventions
Do:
class MyNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/schema'
prefix_default 'pfx'
element_form_default :qualified # Typical for complex schemas
attribute_form_default :unqualified # W3C convention
endDon’t:
# Avoid qualifying attributes unless absolutely necessary
class MyNamespace < Lutaml::Model::XmlNamespace
attribute_form_default :qualified # Unusual, avoid unless required
endUse meaningful namespace URIs
Do:
class ContactNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/schemas/contact/v1' # Versioned, descriptive
schema_location 'https://example.com/schemas/contact/v1/contact.xsd'
endDon’t:
class ContactNamespace < Lutaml::Model::XmlNamespace
uri 'http://example.com' # Too generic
uri 'urn:x-example:contact' # Non-standard format without good reason
endUse Type-level namespaces for reusable types
Do:
# Define namespace for Dublin Core
class DublinCoreNamespace < Lutaml::Model::XmlNamespace
uri 'http://purl.org/dc/elements/1.1/'
prefix_default 'dc'
end
# Define reusable DC types with namespace
class DcTitleType < Lutaml::Model::Type::String
xml_namespace DublinCoreNamespace
xsd_type 'titleType'
end
class DcCreatorType < Lutaml::Model::Type::String
xml_namespace DublinCoreNamespace
xsd_type 'creatorType'
end
# Use across multiple models - namespace applied automatically
class Document < Lutaml::Model::Serializable
attribute :title, DcTitleType
attribute :creator, DcCreatorType
xml do
root 'document'
map_element 'title', to: :title # dc:title automatically
map_element 'creator', to: :creator # dc:creator automatically
end
end
class BookMetadata < Lutaml::Model::Serializable
attribute :title, DcTitleType
attribute :creator, DcCreatorType
xml do
root 'book'
map_element 'title', to: :title # dc:title automatically
map_element 'creator', to: :creator # dc:creator automatically
end
endDon’t:
# Repeat namespace on every element mapping
class Document < Lutaml::Model::Serializable
attribute :title, :string
attribute :creator, :string
xml do
root 'document'
# Repetitive and error-prone
map_element 'title', to: :title,
namespace: 'http://purl.org/dc/elements/1.1/', prefix: 'dc'
map_element 'creator', to: :creator,
namespace: 'http://purl.org/dc/elements/1.1/', prefix: 'dc'
end
endUse namespace_scope for multi-namespace documents
Do:
# Consolidate frequently-used namespaces at root
class Vcard < Lutaml::Model::Serializable
xml do
root "vCard"
namespace VcardNamespace
namespace_scope [VcardNamespace, DcNamespace, DctermsNamespace]
map_element "version", to: :version
map_element "title", to: :title
map_element "created", to: :created
end
end
# Result: Clean XML with all namespaces declared at root
# <vcard:vCard xmlns:vcard="..." xmlns:dc="..." xmlns:dcterms="...">
# <vcard:version>4.0</vcard:version>
# <dc:title>Contact: Dr. John Doe</dc:title>
# <dcterms:created>2024-06-01T12:00:00Z</dcterms:created>
# </vcard:vCard>Don’t:
# Without namespace_scope for multi-namespace documents
class Vcard < Lutaml::Model::Serializable
xml do
root "vCard"
namespace VcardNamespace
# No namespace_scope
map_element "version", to: :version
map_element "title", to: :title
map_element "created", to: :created
end
end
# Result: Repetitive namespace declarations
# <vcard:vCard xmlns:vcard="...">
# <vcard:version>4.0</vcard:version>
# <dc:title xmlns:dc="...">Contact: Dr. John Doe</dc:title>
# <dcterms:created xmlns:dcterms="...">2024-06-01T12:00:00Z</dcterms:created>
# </vcard:vCard>When NOT to use namespace_scope
Single namespace documents:
# Don't use namespace_scope for single namespace
class Ceramic < Lutaml::Model::Serializable
xml do
element 'ceramic'
namespace CeramicNamespace
# namespace_scope [CeramicNamespace] # NOT NEEDED - only one namespace
map_element 'type', to: :type
map_element 'glaze', to: :glaze
end
endRarely used namespaces:
# Keep rarely-used namespaces local for clarity
class Document < Lutaml::Model::Serializable
xml do
root 'document'
namespace DocumentNamespace
namespace_scope [DocumentNamespace] # Only frequently used
map_element 'title', to: :title
map_element 'content', to: :content
# Special metadata element used rarely
map_element 'metadata', to: :metadata # MetadataNamespace declared locally
end
endElement declaration practices
Use element() for new code
Do:
class Model < Lutaml::Model::Serializable
xml do
element 'model' # Clear, modern API
map_element 'attr', to: :attr
end
endAcceptable:
class Model < Lutaml::Model::Serializable
xml do
root 'model' # Backward compatible, still works
map_element 'attr', to: :attr
end
endOmit element declaration for type-only models
Do:
class Address < Lutaml::Model::Serializable
attribute :street, :string
attribute :city, :string
xml do
# No element() - embedded type only
sequence do
map_element 'street', to: :street
map_element 'city', to: :city
end
end
endDon’t:
class Address < Lutaml::Model::Serializable
xml do
no_root # DEPRECATED
map_element 'street', to: :street
end
endMixed content practices
Use explicit mixed_content() for clarity
Do:
class RichText < Lutaml::Model::Serializable
xml do
element 'text'
mixed_content # Explicit and clear
map_element 'b', to: :bold
end
endAcceptable:
class RichText < Lutaml::Model::Serializable
xml do
root 'text', mixed: true # Also works
map_element 'b', to: :bold
end
endSequence practices
Use sequence when order matters
Do:
class Person < Lutaml::Model::Serializable
xml do
element 'person'
sequence do # Enforce strict order
map_element 'firstName', to: :first_name
map_element 'lastName', to: :last_name
map_element 'email', to: :email
end
end
endDon’t:
# Without sequence when order is important per XSD
class Person < Lutaml::Model::Serializable
xml do
element 'person'
# Elements can appear in any order - may not match XSD
map_element 'firstName', to: :first_name
map_element 'lastName', to: :last_name
end
endXSD generation practices
Add documentation for generated schemas
Do:
class ContactNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/contact/v1'
version '1.0'
documentation "Contact information schema for Example Corp"
end
class Contact < Lutaml::Model::Serializable
xml do
element 'contact'
namespace ContactNamespace
documentation "Represents a contact record"
type_name 'ContactRecordType'
end
endDon’t:
# Missing documentation makes generated XSD less useful
class Contact < Lutaml::Model::Serializable
xml do
element 'contact'
end
endUse explicit xsd_type for special cases
Do:
class Product < Lutaml::Model::Serializable
attribute :product_id, :string, xsd_type: 'xs:ID'
attribute :parent_ref, :string, xsd_type: 'xs:IDREF'
xml do
element 'product'
map_attribute 'id', to: :product_id
map_attribute 'parentRef', to: :parent_ref
end
endDon’t:
# Rely on default inference for ID/IDREF relationships
class Product < Lutaml::Model::Serializable
attribute :product_id, :string # Won't generate xs:ID
attribute :parent_ref, :string # Won't generate xs:IDREF
endQualification practices
Be consistent within a schema
Do:
class MyNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/schema'
element_form_default :qualified # Set once, applies to all
end
# All models using this namespace follow the same rules
class Model1 < Lutaml::Model::Serializable
xml do
element 'model1'
namespace MyNamespace
end
end
class Model2 < Lutaml::Model::Serializable
xml do
element 'model2'
namespace MyNamespace
end
endDon’t:
# Inconsistent qualification creates confusion
class Model1 < Lutaml::Model::Serializable
xml do
element 'model1'
namespace 'https://example.com/schema', 'pfx'
map_element 'attr', to: :attr, form: :qualified
end
end
class Model2 < Lutaml::Model::Serializable
xml do
element 'model2'
namespace 'https://example.com/schema', 'pfx'
map_element 'attr', to: :attr, form: :unqualified
end
end Use form: sparingly
Do:
# Set namespace-level defaults, override only when needed
class MyNamespace < Lutaml::Model::XmlNamespace
element_form_default :qualified
end
class Model < Lutaml::Model::Serializable
xml do
namespace MyNamespace
map_element 'special', to: :special, form: :unqualified # Exception
map_element 'normal', to: :normal # Uses default
end
endDon’t:
# Overriding form on every element defeats namespace defaults
class Model < Lutaml::Model::Serializable
xml do
namespace MyNamespace
map_element 'attr1', to: :attr1, form: :qualified
map_element 'attr2', to: :attr2, form: :qualified
map_element 'attr3', to: :attr3, form: :qualified
# Just set element_form_default :qualified instead!
end
endEncoding practices
Set encoding explicitly for non-UTF-8
Do:
# Per-instance for consistent encoding
instance = JapaneseCeramic.new(glaze_type: "志野釉")
instance.encoding = "Shift_JIS"
xml = instance.to_xml
# Or per-export when needed
xml = instance.to_xml(encoding: "Shift_JIS")Don’t:
# Rely on defaults for non-UTF-8 data
instance = JapaneseCeramic.new(glaze_type: "志野釉")
xml = instance.to_xml # May produce corrupted output with some adaptersTesting practices
Test round-trip serialization
Do:
RSpec.describe Contact do
it "round-trips correctly" do
original = Contact.new(name: "John", email: "john@example.com")
xml = original.to_xml
parsed = Contact.from_xml(xml)
expect(parsed).to eq(original)
expect(parsed.name).to eq("John")
expect(parsed.email).to eq("john@example.com")
end
endPerformance practices
Choose appropriate adapter
XML adapters:
-
Ox: Fastest, use for performance-critical applications
-
Nokogiri: Most compatible, good balance
-
Oga: Pure Ruby, works with Opal, use when no native extensions allowed
Configure once:
Lutaml::Model::Config.configure do |config|
config.xml_adapter_type = :ox # or :nokogiri, :oga
endAvoid excessive nesting
Do:
# Flat structure when possible
class Address < Lutaml::Model::Serializable
attribute :street, :string
attribute :city, :string
attribute :postal_code, :string
endAvoid when unnecessary:
# Unnecessary nesting
class Street < Lutaml::Model::Serializable
attribute :name, :string
end
class City < Lutaml::Model::Serializable
attribute :name, :string
end
class Address < Lutaml::Model::Serializable
attribute :street, Street
attribute :city, City
# Just use :string attributes directly
endOrganization practices
Group related namespace classes
Do:
# lib/my_project/namespaces.rb
module MyProject
module Namespaces
class Contact < Lutaml::Model::XmlNamespace
uri 'https://example.com/contact/v1'
prefix_default 'contact'
end
class Address < Lutaml::Model::XmlNamespace
uri 'https://example.com/address/v1'
prefix_default 'addr'
end
end
endDocumentation practices
Document complex mappings
Do:
class ComplexModel < Lutaml::Model::Serializable
# This attribute maps to either 'type' or 'kind' element
# depending on schema version
attribute :type_info, :string
xml do
element 'model'
# Map multiple names for backward compatibility
map_element ['type', 'kind'], to: :type_info
end
endError handling practices
Schema versioning practices
Include version in namespace URI
Do:
class ContactNamespace < Lutaml::Model::XmlNamespace
uri 'https://example.com/schemas/contact/v1' # Version in URI
version '1.0' # Also track in metadata
endSupport multiple versions
Do:
class ContactNamespaceV1 < Lutaml::Model::XmlNamespace
uri 'https://example.com/schemas/contact/v1'
version '1.0'
end
class ContactNamespaceV2 < Lutaml::Model::XmlNamespace
uri 'https://example.com/schemas/contact/v2'
version '2.0'
end
# Models can explicitly choose version
class LegacyContact < Lutaml::Model::Serializable
xml do
element 'contact'
namespace ContactNamespaceV1
end
end
class ModernContact < Lutaml::Model::Serializable
xml do
element 'contact'
namespace ContactNamespaceV2
end
endCode style practices
Follow consistent indentation
Do:
class Model < Lutaml::Model::Serializable
attribute :name, :string
xml do
element 'model'
namespace MyNamespace
sequence do
map_element 'name', to: :name
end
end
endGroup related mappings
Do:
class Model < Lutaml::Model::Serializable
xml do
element 'model'
namespace MyNamespace
# Attributes first
map_attribute 'id', to: :id
map_attribute 'version', to: :version
# Then elements in logical order
sequence do
map_element 'name', to: :name
map_element 'description', to: :description
end
end
endSummary checklist
When writing XML mappings:
-
☑ Use XmlNamespace classes for projects with multiple models
-
☑ Use
element()for new code (orroot()for backward compat) -
☑ Omit element declaration for type-only models (no
no_root) -
☑ Set
element_form_default :qualifiedfor complex schemas -
☑ Keep
attribute_form_default :unqualified(W3C convention) -
☑ Use
sequencewhen element order is significant -
☑ Add
documentationfor XSD generation -
☑ Specify
xsd_typefor ID/IDREF and custom XSD types -
☑ Use Type-level namespaces for reusable types across models
-
☑ Test round-trip serialization (both
to_xmlandfrom_xml) -
☑ Validate parsed data with
validateorvalidate! -
☑ Set encoding explicitly for non-UTF-8 data
-
☑ Choose appropriate XML adapter for your needs
-
☑ Follow namespace resolution priority (explicit > type > inherit > form default)