Model definition
General
A LutaML model is used to represent a class of information, of which a model instance is a set of information representing a coherent concept.
There are two ways to define an information model in Lutaml::Model:
-
Inheriting from the
Lutaml::Model::Serializableclass -
Including the
Lutaml::Model::Serializemodule
Definition
Through inheritance
The simplest way to define a model is to create a class that inherits from Lutaml::Model::Serializable.
The attribute class method is used to define attributes.
require 'lutaml/model'
class Kiln < Lutaml::Model::Serializable
attribute :brand, :string
attribute :capacity, :integer
attribute :temperature, :integer
endThrough inclusion
If the model class already has a super class that it inherits from, the model can be extended using the Lutaml::Model::Serialize module.
require 'lutaml/model'
class Kiln < SomeSuperClass
include Lutaml::Model::Serialize
attribute :brand, :string
attribute :capacity, :integer
attribute :temperature, :integer
endInheritance
A model can inherit from another model to inherit all attributes and methods of the parent model, allowing for code reusability and a clear model hierarchy.
Syntax:
class Superclass < Lutaml::Model::Serializable
# attribute ...
# serialization blocks
end
class Subclass < Superclass
# attributes are additive
# serialization blocks are replaced
endAn inherited model has the following characteristics:
-
All attributes are inherited from the parent model.
-
Additional calls to
attributein the child model are additive, unless the attribute name is the same as an attribute in the parent model. -
Serialization blocks, such as
xmlandkey_valueare replaced when defined.-
In order to selectively import serialization mapping rules from the parent model, the
import_model_mappingsmethod can be used (see import_model_mappings).
-
Comparison and diff
A Serialize / Serializable object can be compared with another object of the same class using the == operator. This is implemented through the ComparableModel module, which provides powerful comparison and diff functionality.
Basic comparison
Two objects are considered equal if they have the same class and all their attributes are equal. This behavior differs from the typical Ruby behavior, where two objects are considered equal only if they have the same object ID.
Two Serialize objects will have the same hash value if they have the same class and all their attributes are equal. |
> a = Kiln.new(brand: 'Kiln 1', capacity: 100, temperature: 1050)
> b = Kiln.new(brand: 'Kiln 1', capacity: 100, temperature: 1050)
> a == b
> # true
> a.hash == b.hash
> # trueDeep comparison
The comparison works recursively for nested objects and handles complex data structures:
class Glaze < Lutaml::Model::Serializable
attribute :color, :string
attribute :temperature, :integer
attribute :food_safe, :boolean
end
class Ceramic < Lutaml::Model::Serializable
attribute :type, :string
attribute :glaze, Glaze
end
# Nested object comparison
glaze1 = Glaze.new(color: "Blue", temperature: 1200, food_safe: true)
glaze2 = Glaze.new(color: "Blue", temperature: 1200, food_safe: true)
ceramic1 = Ceramic.new(type: "Bowl", glaze: glaze1)
ceramic2 = Ceramic.new(type: "Bowl", glaze: glaze2)
ceramic1 == ceramic2 # true - deep comparison of nested objectsCircular reference handling
The comparison safely handles circular references without infinite loops:
class RecursiveNode < Lutaml::Model::Serializable
attribute :name, :string
attribute :next_node, RecursiveNode
end
node1 = RecursiveNode.new(name: "A")
node2 = RecursiveNode.new(name: "B", next_node: node1)
node1.next_node = node2 # Creates circular reference
# Comparison still works without infinite loops
node1_copy = RecursiveNode.new(name: "A")
node2_copy = RecursiveNode.new(name: "B", next_node: node1_copy)
node1_copy.next_node = node2_copy
node1 == node1_copy # trueDiff generation
The ComparableModel module provides powerful diff functionality to visualize differences between objects and calculate similarity scores.
Understanding diff and score
Diff (Difference): A visual representation showing the differences between two objects:
-
Removed values are marked with
-(typically red) -
Added values are marked with
+(typically green) -
Hierarchical structure shows nested objects and their relationships
Score: A numerical value between 0 and 1 representing how different the objects are:
-
0= Objects are identical (no differences) -
1= Objects are completely different -
0.5= Objects are 50% different -
Convert to similarity percentage:
(1 - score) * 100
The diff_with_score method
The diff_with_score method returns two values as an array:
diff_score, diff_tree = Lutaml::Model::Serialize.diff_with_score(obj1, obj2, options)
# ↑ ↑
# Float String
# (0.0-1.0) (Visual representation)-
diff_score(Float): The numerical difference score between 0.0 and 1.0 -
diff_tree(String): The formatted visual diff showing all differences
Basic example
# Create two different objects
ceramic1 = Ceramic.new(
type: "Bowl",
glaze: Glaze.new(color: "Blue", temperature: 1200, food_safe: true)
)
ceramic2 = Ceramic.new(
type: "Bowl",
glaze: Glaze.new(color: "Red", temperature: 1000, food_safe: false)
)
# Generate diff with similarity score
diff_score, diff_tree = Lutaml::Model::Serialize.diff_with_score(ceramic1, ceramic2)
puts "Difference Score: #{diff_score}"
puts "Similarity: #{((1 - diff_score) * 100).round(2)}%"
puts "Visual Diff:"
puts diff_treeOutput:
Difference Score: 0.25
Similarity: 75.0%
Visual Diff:
└── Ceramic
└── glaze (Glaze):
├── color (Lutaml::Model::Type::String):
│ ├── - (String) "Blue"
│ └── + (String) "Red"
├── temperature (Lutaml::Model::Type::Integer):
│ ├── - (Integer) 1200
│ └── + (Integer) 1000
└── food_safe (Lutaml::Model::Type::Boolean):
├── - (TrueClass) true
└── + (FalseClass) falseUnderstanding the return values structure
The method returns an array with exactly two elements:
result = Lutaml::Model::Serialize.diff_with_score(obj1, obj2)
# result[0] => diff_score (Float between 0.0 and 1.0)
# result[1] => diff_tree (String with visual representation)
# Or using array destructuring:
score, tree = Lutaml::Model::Serialize.diff_with_score(obj1, obj2)
puts "Objects are #{(score * 100).round(1)}% different"
puts treeDiff options
The diff functionality supports various options:
# Show unchanged attributes as well
diff_score, diff_tree = Lutaml::Model::Serialize.diff_with_score(
ceramic1,
ceramic2,
show_unchanged: true, # Show attributes that are the same
highlight_diff: false, # Don't highlight only differences
use_colors: true # Use color coding (red/green)
)
# With indentation
diff_score, diff_tree = Lutaml::Model::Serialize.diff_with_score(
ceramic1,
ceramic2,
indent: " "
)Collection handling
The diff functionality handles collections (arrays) intelligently:
class CeramicCollection < Lutaml::Model::Serializable
attribute :name, :string
attribute :items, Ceramic, collection: true
end
collection1 = CeramicCollection.new(
name: "Blue Collection",
items: [
Ceramic.new(type: "Bowl", glaze: glaze1),
Ceramic.new(type: "Plate", glaze: glaze1)
]
)
collection2 = CeramicCollection.new(
name: "Mixed Collection",
items: [
Ceramic.new(type: "Bowl", glaze: glaze1), # Same as first
Ceramic.new(type: "Cup", glaze: glaze2) # Different
]
)
diff_score, diff_tree = Lutaml::Model::Serialize.diff_with_score(collection1, collection2)
# Shows detailed diff of collection items with indexesUse cases
The comparison and diff functionality is particularly useful for:
-
Testing: Verify model equality in specs
-
Data Migration: Compare objects before/after transformation
-
Auditing: Track changes to model instances
-
Data Validation: Identify differences in data imports
-
Debugging: Visualize object differences during development
-
API Testing: Compare expected vs actual responses