Lutaml::Model supports serialization to and from W3C RDF Turtle format using SKOS, Dublin Core Terms, and other W3C vocabularies.
Setup
Add the rdf-turtle gem to your Gemfile:
gem "rdf-turtle", "~> 3.3"Turtle is a non-key-value format adapter with its own mapping DSL based on RDF triples (subject–predicate–object). It is registered as a first-class format in the FormatRegistry with dedicated Mapping, Transform, and Adapter classes.
Mapping DSL
Use the turtle do block in your model to define Turtle mappings:
class Concept < Lutaml::Model::Serializable
attribute :name, :string
attribute :description, :string
attribute :code, :string
turtle do
namespace Lutaml::Rdf::Namespaces::SkosNamespace,
Lutaml::Rdf::Namespaces::DctermsNamespace
subject { |m| "http://example.org/concept/#{m.code}" }
type "skos:Concept"
predicate :prefLabel,
namespace: Lutaml::Rdf::Namespaces::SkosNamespace,
to: :name,
lang_tagged: true
predicate :definition,
namespace: Lutaml::Rdf::Namespaces::SkosNamespace,
to: :description
predicate :notation,
namespace: Lutaml::Rdf::Namespaces::SkosNamespace,
to: :code
end
endnamespace
Declares the @prefix lines in the output. Accepts one or more Lutaml::Rdf::Namespace subclass references. Internally builds a NamespaceSet for O(1) prefix lookup and IRI resolution.
subject
Required for top-level models with predicates and no members. A block that generates the subject URI from the model instance. Raises Lutaml::Turtle::MissingSubjectError if not defined when needed.
Member models (serialized via a container’s members declaration) can omit the subject block — blank nodes are used automatically.
type
Sets the RDF type (rdf:type) triple. Accepts a single compact IRI or an array:
type "skos:Concept"
# or multiple types:
type ["skos:Concept", "dcterms:Agent"]Compact IRIs are resolved to full URIs via the declared namespaces. Full URIs (e.g., "http://example.org/MyType") are used as-is. Each type produces a separate a triple in the output.
predicate
Each predicate creates a Lutaml::Rdf::MappingRule value object:
-
name— the local name in the namespace (e.g.,:prefLabel) -
namespace:— theLutaml::Rdf::Namespacesubclass (required, validated) -
to:— the model attribute to read/write (required) -
lang_tagged:(default:false) — if true, appends@langsuffix from the value’slanguage_codeorlanguagemethod. Mutually exclusive withuri_reference. -
uri_reference:(default:false) — if true, serializes values as URI objects rather than string literals. Compact IRIs are resolved via the declared namespaces. Mutually exclusive withlang_tagged.
Validation errors:
-
namespacemust be aRdf::Namespacesubclass — raisesArgumentError -
to:is required — raisesArgumentError
Serialization
concept = Concept.new(name: "test", description: "A description", code: "2119")
turtle = concept.to_turtleProduces:
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
<http://example.org/concept/2119> a skos:Concept;
skos:definition "A description";
skos:notation "2119";
skos:prefLabel "test" .The output uses the RDF::Turtle::Writer from the rdf-turtle gem, which automatically compacts URIs using declared prefixes and uses native Turtle syntax for typed literals.
Special Characters
String values containing quotes, newlines, tabs, or backslashes are automatically escaped. The RDF::Turtle::Writer uses triple-quoted strings ("""…""") for multi-line literals.
Nil Values
Predicates for attributes with nil values are omitted from the output. If all predicates produce no data, the result is an empty string.
Deserialization
concept = Concept.from_turtle(turtle_string)
puts concept.code # => "2119"
puts concept.description # => "A description"The from_turtle method:
-
Parses the Turtle string into an
RDF::GraphviaRDF::Turtle::Reader -
Finds subjects by
rdf:typematching the declared type -
Maps predicates back to model attributes using the declared predicate rules
-
Converts RDF typed literals back to Ruby types (integers, booleans, etc.)
Language-tagged literals are extracted without the language tag.
Round-trip
Model data round-trips through Turtle serialization:
restored = Concept.from_turtle(concept.to_turtle)
restored.code == concept.code # => true
restored.description == concept.description # => trueError Handling
-
Lutaml::Turtle::MissingSubjectError— raised when serializing a model without asubjectblock defined -
RDF::ReaderError— raised by the RDF parser for malformed Turtle input -
Both are wrapped in
Lutaml::Model::InvalidFormatErrorby the format pipeline
Architecture
The Turtle format is composed of:
-
Lutaml::Turtle::Adapter— parses Turtle strings toRDF::Graphand serializes graphs back to Turtle -
Lutaml::Turtle::Mapping— empty subclass ofRdf::Mapping; inheritsnamespace,subject,type,predicate,membersDSL methods -
Lutaml::Turtle::Transform— inherits fromRdf::Transform; bidirectional transform between model instances and RDF graphs viamodel_to_data/data_to_model
Unified rdf DSL
If you need both JSON-LD and Turtle output from the same model, use the unified rdf DSL instead of separate jsonld and turtle blocks:
class Concept < Lutaml::Model::Serializable
attribute :name, :string
attribute :code, :string
rdf do
namespace Lutaml::Rdf::Namespaces::SkosNamespace
subject { |m| "http://example.org/#{m.code}" }
type "skos:Concept"
predicate :prefLabel, namespace: SkosNamespace, to: :name
predicate :notation, namespace: SkosNamespace, to: :code
end
endThe rdf block also supports members for graph-level serialization, where a container model emits all member resources as separate subjects in the same Turtle document.
See Unified RDF Serialization for the complete guide.