lutaml-model 0.8.9 → 0.8.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-repos.json +1 -0
  3. data/.rubocop_todo.yml +52 -22
  4. data/README.adoc +43 -0
  5. data/docs/_guides/jsonld-serialization.adoc +3 -1
  6. data/docs/_guides/rdf-serialization.adoc +94 -8
  7. data/docs/_guides/turtle-serialization.adoc +17 -4
  8. data/lib/lutaml/jsonld/transform.rb +70 -24
  9. data/lib/lutaml/key_value/transform.rb +5 -5
  10. data/lib/lutaml/key_value/transformation/collection_serializer.rb +25 -11
  11. data/lib/lutaml/key_value/transformation/value_serializer.rb +7 -7
  12. data/lib/lutaml/key_value/transformation.rb +27 -17
  13. data/lib/lutaml/model/adapter_resolver.rb +4 -6
  14. data/lib/lutaml/model/attribute.rb +26 -23
  15. data/lib/lutaml/model/cached_type_resolver.rb +10 -9
  16. data/lib/lutaml/model/cli.rb +1 -1
  17. data/lib/lutaml/model/collection.rb +4 -4
  18. data/lib/lutaml/model/comparable_model.rb +11 -11
  19. data/lib/lutaml/model/config.rb +1 -1
  20. data/lib/lutaml/model/consolidation/dispatcher.rb +1 -1
  21. data/lib/lutaml/model/consolidation/pattern_chunker.rb +3 -3
  22. data/lib/lutaml/model/format_registry.rb +6 -4
  23. data/lib/lutaml/model/global_context.rb +2 -2
  24. data/lib/lutaml/model/global_register.rb +1 -1
  25. data/lib/lutaml/model/instrumentation.rb +1 -1
  26. data/lib/lutaml/model/mapping/mapping_rule.rb +3 -3
  27. data/lib/lutaml/model/mapping/model_mapping.rb +1 -1
  28. data/lib/lutaml/model/mapping/model_mapping_rule.rb +1 -1
  29. data/lib/lutaml/model/register.rb +3 -3
  30. data/lib/lutaml/model/render_policy.rb +11 -17
  31. data/lib/lutaml/model/runtime_compatibility.rb +0 -1
  32. data/lib/lutaml/model/schema/xml_compiler/group.rb +1 -1
  33. data/lib/lutaml/model/schema/xml_compiler/registry_generator.rb +1 -1
  34. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +0 -2
  35. data/lib/lutaml/model/schema/xml_compiler.rb +14 -14
  36. data/lib/lutaml/model/serialize/attribute_definition.rb +1 -1
  37. data/lib/lutaml/model/serialize/deserialization_context.rb +50 -0
  38. data/lib/lutaml/model/serialize/format_conversion.rb +2 -2
  39. data/lib/lutaml/model/serialize/initialization.rb +44 -7
  40. data/lib/lutaml/model/serialize/model_import.rb +1 -1
  41. data/lib/lutaml/model/serialize.rb +8 -1
  42. data/lib/lutaml/model/services/rule_value_extractor.rb +2 -1
  43. data/lib/lutaml/model/store.rb +77 -24
  44. data/lib/lutaml/model/transformation_registry.rb +1 -1
  45. data/lib/lutaml/model/type_context.rb +7 -1
  46. data/lib/lutaml/model/type_resolver.rb +1 -6
  47. data/lib/lutaml/model/utils.rb +19 -6
  48. data/lib/lutaml/model/validation_framework.rb +1 -1
  49. data/lib/lutaml/model/value_transformer.rb +2 -2
  50. data/lib/lutaml/model/version.rb +1 -1
  51. data/lib/lutaml/rdf/mapping.rb +19 -13
  52. data/lib/lutaml/rdf/mapping_rule.rb +19 -2
  53. data/lib/lutaml/rdf/member_rule.rb +19 -2
  54. data/lib/lutaml/rdf/transform.rb +20 -11
  55. data/lib/lutaml/turtle/transform.rb +125 -53
  56. data/lib/lutaml/xml/adapter/adapter_helpers.rb +1 -1
  57. data/lib/lutaml/xml/adapter/base_adapter.rb +10 -14
  58. data/lib/lutaml/xml/adapter/namespace_uri_collector.rb +3 -3
  59. data/lib/lutaml/xml/adapter/plan_based_builder.rb +14 -14
  60. data/lib/lutaml/xml/adapter/xml_serializer.rb +3 -3
  61. data/lib/lutaml/xml/configurable.rb +2 -1
  62. data/lib/lutaml/xml/data_model.rb +2 -2
  63. data/lib/lutaml/xml/decisions/decision_context.rb +3 -3
  64. data/lib/lutaml/xml/decisions/rules/element_form_default_unqualified_rule.rb +1 -1
  65. data/lib/lutaml/xml/decisions/rules/element_form_option_rule.rb +1 -1
  66. data/lib/lutaml/xml/decisions/rules/used_prefix_rule.rb +1 -1
  67. data/lib/lutaml/xml/declaration_plan.rb +2 -2
  68. data/lib/lutaml/xml/declaration_planner.rb +12 -13
  69. data/lib/lutaml/xml/document.rb +13 -13
  70. data/lib/lutaml/xml/format_chooser.rb +3 -3
  71. data/lib/lutaml/xml/hoisting_algorithm.rb +1 -1
  72. data/lib/lutaml/xml/mapping.rb +2 -2
  73. data/lib/lutaml/xml/mapping_rule.rb +16 -3
  74. data/lib/lutaml/xml/model_transform.rb +17 -19
  75. data/lib/lutaml/xml/namespace_collector.rb +10 -10
  76. data/lib/lutaml/xml/namespace_declaration.rb +2 -2
  77. data/lib/lutaml/xml/namespace_declaration_data.rb +5 -8
  78. data/lib/lutaml/xml/namespace_scope_config.rb +3 -2
  79. data/lib/lutaml/xml/namespace_type_resolver.rb +4 -4
  80. data/lib/lutaml/xml/nokogiri/element.rb +2 -2
  81. data/lib/lutaml/xml/polymorphic_value_handler.rb +1 -1
  82. data/lib/lutaml/xml/schema/xsd/base.rb +7 -7
  83. data/lib/lutaml/xml/schema/xsd/choice.rb +2 -2
  84. data/lib/lutaml/xml/schema/xsd/complex_type.rb +5 -5
  85. data/lib/lutaml/xml/schema/xsd/errors/message_builder.rb +3 -3
  86. data/lib/lutaml/xml/schema/xsd/group.rb +2 -2
  87. data/lib/lutaml/xml/schema/xsd/sequence.rb +2 -2
  88. data/lib/lutaml/xml/schema/xsd_schema.rb +5 -5
  89. data/lib/lutaml/xml/serialization/format_conversion.rb +4 -3
  90. data/lib/lutaml/xml/transformation/element_builder.rb +4 -2
  91. data/lib/lutaml/xml/transformation/rule_applier.rb +2 -2
  92. data/lib/lutaml/xml/transformation/value_serializer.rb +4 -6
  93. data/lib/lutaml/xml/transformation.rb +4 -4
  94. data/lib/lutaml/xml/type/configurable.rb +0 -4
  95. data/lib/lutaml/xml/xml_element.rb +21 -13
  96. data/lutaml-model.gemspec +1 -1
  97. data/spec/lutaml/jsonld/transform_spec.rb +239 -0
  98. data/spec/lutaml/model/cached_type_resolver_spec.rb +3 -3
  99. data/spec/lutaml/model/optimization_spec.rb +228 -0
  100. data/spec/lutaml/model/store_spec.rb +195 -0
  101. data/spec/lutaml/rdf/mapping_rule_spec.rb +97 -0
  102. data/spec/lutaml/rdf/mapping_spec.rb +74 -4
  103. data/spec/lutaml/rdf/member_rule_spec.rb +41 -0
  104. data/spec/lutaml/rdf/rdf_transform_spec.rb +95 -29
  105. data/spec/lutaml/turtle/mapping_spec.rb +2 -2
  106. data/spec/lutaml/turtle/transform_spec.rb +315 -0
  107. data/spec/lutaml/xml/data_model_spec.rb +10 -28
  108. metadata +7 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 002a96224c1367b179f30fe6c2f984b7ae80fa7a6c222d8866b10e26e8ee8c4c
4
- data.tar.gz: 2677ff0aca481ae66bf7be4f47cb132417b8d59ddbbf5d266129e9e6c6017e57
3
+ metadata.gz: 992b8e51bee9c168f1dbb4cac0fcfd75f593f1c40cc29abce7e2163088e6fc48
4
+ data.tar.gz: 8e3524b5e4336e8cb289c3f31b513727bb2bbb23d3429fcd770425b6f24de489
5
5
  SHA512:
6
- metadata.gz: c28f9a50a50f742dc106fb34af4e620b8f12f036b5256e6e5b36bec7ed3ae0634294e078edeb48cef3983e82b266abdb214baa7dceab9e7bd865ad8858162bb5
7
- data.tar.gz: 6ef9ffdf02e9e7497a717d082c77c4fce9de3350f1039c3073135fc5d1107ebd50c0024419c33839ff895c70f97aa5907c67455a19f29d4a56e3ca9815c08c79
6
+ metadata.gz: 61c349cf6a1476a0c8668ea19b45e2989275a00d97ab718613eb80cfdc86a2e71ba0cb344c69a6877c5447a57d715f080470c3b28dce0c046652502182b29408
7
+ data.tar.gz: b85e8858876efa7b98acec6df1ff36e2ebbbf772c8b8cf035a459374159d2c5d704c13cbcecc99a69561438884d05bc4f52d13e12d6cd1b3f63611761344bd04
@@ -29,6 +29,7 @@
29
29
  "relaton/loc_mods",
30
30
  "relaton/w3c_api",
31
31
  "ukiryu/ukiryu",
32
+ "unitsml/unitsdb-ruby",
32
33
  "unitsml/unitsml-ruby"
33
34
  ]
34
35
  }
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-05-14 09:40:04 UTC using RuboCop version 1.86.0.
3
+ # on 2026-05-20 09:01:31 UTC using RuboCop version 1.86.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -11,27 +11,51 @@ Gemspec/RequiredRubyVersion:
11
11
  Exclude:
12
12
  - 'lutaml-model.gemspec'
13
13
 
14
- # Offense count: 1
14
+ # Offense count: 2
15
15
  # This cop supports safe autocorrection (--autocorrect).
16
16
  # Configuration parameters: EnforcedStyle, IndentationWidth.
17
17
  # SupportedStyles: with_first_argument, with_fixed_indentation
18
18
  Layout/ArgumentAlignment:
19
19
  Exclude:
20
- - 'lib/lutaml/model/store.rb'
20
+ - 'lib/lutaml/turtle/transform.rb'
21
+ - 'spec/lutaml/model/store_spec.rb'
21
22
 
22
- # Offense count: 2982
23
+ # Offense count: 3
24
+ # This cop supports safe autocorrection (--autocorrect).
25
+ # Configuration parameters: EnforcedStyleAlignWith.
26
+ # SupportedStylesAlignWith: either, start_of_block, start_of_line
27
+ Layout/BlockAlignment:
28
+ Exclude:
29
+ - 'spec/lutaml/model/store_spec.rb'
30
+
31
+ # Offense count: 3
32
+ # This cop supports safe autocorrection (--autocorrect).
33
+ Layout/BlockEndNewline:
34
+ Exclude:
35
+ - 'spec/lutaml/model/store_spec.rb'
36
+
37
+ # Offense count: 6
38
+ # This cop supports safe autocorrection (--autocorrect).
39
+ # Configuration parameters: Width, EnforcedStyleAlignWith, AllowedPatterns.
40
+ # SupportedStylesAlignWith: start_of_line, relative_to_receiver
41
+ Layout/IndentationWidth:
42
+ Exclude:
43
+ - 'spec/lutaml/model/store_spec.rb'
44
+
45
+ # Offense count: 3024
23
46
  # This cop supports safe autocorrection (--autocorrect).
24
47
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
25
48
  # URISchemes: http, https
26
49
  Layout/LineLength:
27
50
  Enabled: false
28
51
 
29
- # Offense count: 1
52
+ # Offense count: 2
30
53
  # This cop supports safe autocorrection (--autocorrect).
31
54
  # Configuration parameters: AllowInHeredoc.
32
55
  Layout/TrailingWhitespace:
33
56
  Exclude:
34
- - 'lib/lutaml/model/store.rb'
57
+ - 'lib/lutaml/turtle/transform.rb'
58
+ - 'spec/lutaml/model/store_spec.rb'
35
59
 
36
60
  # Offense count: 21
37
61
  # Configuration parameters: AllowedMethods.
@@ -129,28 +153,28 @@ Lint/UselessConstantScoping:
129
153
  Exclude:
130
154
  - 'lib/lutaml/xml/mapping_rule.rb'
131
155
 
132
- # Offense count: 342
156
+ # Offense count: 345
133
157
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
134
158
  Metrics/AbcSize:
135
159
  Enabled: false
136
160
 
137
- # Offense count: 39
161
+ # Offense count: 40
138
162
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
139
163
  # AllowedMethods: refine
140
164
  Metrics/BlockLength:
141
- Max: 127
165
+ Max: 133
142
166
 
143
167
  # Offense count: 16
144
168
  # Configuration parameters: CountBlocks, CountModifierForms.
145
169
  Metrics/BlockNesting:
146
170
  Max: 6
147
171
 
148
- # Offense count: 302
172
+ # Offense count: 308
149
173
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
150
174
  Metrics/CyclomaticComplexity:
151
175
  Enabled: false
152
176
 
153
- # Offense count: 554
177
+ # Offense count: 553
154
178
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
155
179
  Metrics/MethodLength:
156
180
  Max: 514
@@ -161,7 +185,7 @@ Metrics/ParameterLists:
161
185
  Max: 24
162
186
  MaxOptionalParameters: 5
163
187
 
164
- # Offense count: 253
188
+ # Offense count: 262
165
189
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
166
190
  Metrics/PerceivedComplexity:
167
191
  Enabled: false
@@ -253,7 +277,7 @@ RSpec/BeforeAfterAll:
253
277
  RSpec/ContextWording:
254
278
  Enabled: false
255
279
 
256
- # Offense count: 95
280
+ # Offense count: 96
257
281
  # Configuration parameters: IgnoredMetadata.
258
282
  RSpec/DescribeClass:
259
283
  Enabled: false
@@ -264,7 +288,7 @@ RSpec/DescribeMethod:
264
288
  - 'spec/lutaml/xml/schema/xsd/schema_helper_methods_spec.rb'
265
289
  - 'spec/lutaml/xml/serializable_namespace_spec.rb'
266
290
 
267
- # Offense count: 1246
291
+ # Offense count: 1285
268
292
  # Configuration parameters: CountAsOne.
269
293
  RSpec/ExampleLength:
270
294
  Max: 68
@@ -339,7 +363,7 @@ RSpec/MultipleDescribes:
339
363
  - 'spec/lutaml/xml/namespace_resolution_strategy_spec.rb'
340
364
  - 'spec/lutaml/xml/xml_space_type_spec.rb'
341
365
 
342
- # Offense count: 1482
366
+ # Offense count: 1507
343
367
  RSpec/MultipleExpectations:
344
368
  Max: 21
345
369
 
@@ -393,7 +417,7 @@ RSpec/RepeatedExampleGroupDescription:
393
417
  RSpec/SpecFilePathFormat:
394
418
  Enabled: false
395
419
 
396
- # Offense count: 32
420
+ # Offense count: 33
397
421
  # Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
398
422
  RSpec/VerifiedDoubles:
399
423
  Exclude:
@@ -401,6 +425,7 @@ RSpec/VerifiedDoubles:
401
425
  - 'spec/lutaml/key_value/transformation/rule_compiler_spec.rb'
402
426
  - 'spec/lutaml/key_value/transformation/value_serializer_spec.rb'
403
427
  - 'spec/lutaml/model/compiled_rule_spec.rb'
428
+ - 'spec/lutaml/model/optimization_spec.rb'
404
429
  - 'spec/lutaml/model/schema/renderer_spec.rb'
405
430
  - 'spec/lutaml/model/transformation_builder_spec.rb'
406
431
  - 'spec/lutaml/model/transformation_spec.rb'
@@ -412,6 +437,17 @@ Security/MarshalLoad:
412
437
  Exclude:
413
438
  - 'scripts-xmi-profile/profile_xmi.rb'
414
439
 
440
+ # Offense count: 5
441
+ # This cop supports safe autocorrection (--autocorrect).
442
+ # Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods.
443
+ # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces
444
+ # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object
445
+ # FunctionalMethods: let, let!, subject, watch
446
+ # AllowedMethods: lambda, proc, it
447
+ Style/BlockDelimiters:
448
+ Exclude:
449
+ - 'spec/lutaml/model/store_spec.rb'
450
+
415
451
  # Offense count: 2
416
452
  # This cop supports unsafe autocorrection (--autocorrect-all).
417
453
  # Configuration parameters: AllowedMethods, AllowedPatterns.
@@ -457,12 +493,6 @@ Style/MixinUsage:
457
493
  - 'bench/bench_unitsml.rb'
458
494
  - 'bench/bench_xmi.rb'
459
495
 
460
- # Offense count: 1
461
- # This cop supports safe autocorrection (--autocorrect).
462
- Style/MultilineIfModifier:
463
- Exclude:
464
- - 'lib/lutaml/model/store.rb'
465
-
466
496
  # Offense count: 12
467
497
  # Configuration parameters: AllowedClasses.
468
498
  Style/OneClassPerFile:
data/README.adoc CHANGED
@@ -5507,6 +5507,49 @@ vocab.to_turtle
5507
5507
  vocab.to_jsonld
5508
5508
  ----
5509
5509
 
5510
+ ==== Multiple RDF types
5511
+
5512
+ `type` accepts a single compact IRI or an array:
5513
+
5514
+ [source,ruby]
5515
+ ----
5516
+ type "skos:Concept"
5517
+ # or multiple types:
5518
+ type ["skos:Concept", "dcterms:Agent"]
5519
+ ----
5520
+
5521
+ In Turtle, each type produces a separate `a` triple. In JSON-LD, a single type
5522
+ produces `"@type": "skos:Concept"` while multiple types produce an array.
5523
+
5524
+ ==== URI reference predicates
5525
+
5526
+ Predicates declared with `uri_reference: true` serialize values as URI objects
5527
+ rather than string literals. In Turtle, values are emitted without quotes. In
5528
+ JSON-LD, values are wrapped in `{"@id": ...}` and the context term includes
5529
+ `"@type": "@id"`.
5530
+
5531
+ [source,ruby]
5532
+ ----
5533
+ predicate :related, namespace: SkosNamespace, to: :related, uri_reference: true
5534
+ ----
5535
+
5536
+ Round-trip fidelity is preserved: compact IRI forms (e.g. `"skos:other"`) are
5537
+ maintained through serialization and deserialization.
5538
+
5539
+ ==== Linked member predicates
5540
+
5541
+ `members` accepts `predicate_name:` and `namespace:` to generate linking triples
5542
+ from the container to each member:
5543
+
5544
+ [source,ruby]
5545
+ ----
5546
+ members :children, predicate_name: :member, namespace: SkosNamespace
5547
+ ----
5548
+
5549
+ In Turtle, this produces `skos:member <child-uri>` triples on the container
5550
+ subject. In JSON-LD, the context includes a `member` term with `"@type": "@id"`
5551
+ and the container resource contains `{"@id": "..."}` references.
5552
+
5510
5553
  See link:docs/_guides/rdf-serialization.adoc[Unified RDF Serialization] for the
5511
5554
  complete guide including language-tagged values, graph serialization, and
5512
5555
  architecture details.
@@ -160,7 +160,9 @@ end
160
160
  ----
161
161
 
162
162
  See link:../_guides/rdf-serialization.adoc[Unified RDF Serialization] for the
163
- complete guide including graph-level serialization with `members`.
163
+ complete guide including graph-level serialization with `members`, URI reference
164
+ predicates (`uri_reference: true`), multiple RDF types, and linking predicates
165
+ on member collections.
164
166
 
165
167
  == Multi-format models
166
168
 
@@ -94,15 +94,22 @@ subject { |m| "http://example.org/concept/#{m.code}" }
94
94
 
95
95
  ==== type
96
96
 
97
- Sets the RDF type (`rdf:type`). Compact IRIs are resolved via declared
98
- namespaces:
97
+ Sets the RDF type (`rdf:type`). Accepts a single compact IRI or an array of
98
+ compact IRIs. Compact IRIs are resolved via declared namespaces:
99
99
 
100
100
  [source,ruby]
101
101
  ----
102
102
  type "skos:Concept"
103
103
  # resolves to <http://www.w3.org/2004/02/skos/core#Concept>
104
+
105
+ # multiple types:
106
+ type ["skos:Concept", "dcterms:Agent"]
104
107
  ----
105
108
 
109
+ In Turtle, each type produces a separate `a` triple. In JSON-LD, a single type
110
+ produces `"@type": "skos:Concept"` while multiple types produce an array
111
+ `"@type": ["skos:Concept", "dcterms:Agent"]`.
112
+
106
113
  ==== predicate
107
114
 
108
115
  Each `predicate` creates a mapping between an RDF predicate and a model
@@ -119,13 +126,38 @@ Parameters:
119
126
  * `namespace:` — the `Lutaml::Rdf::Namespace` subclass (required)
120
127
  * `to:` — the model attribute to read (required)
121
128
  * `lang_tagged:` (default: `false`) — if true, values are serialized with
122
- language tags (see <<language-tagged-values>>)
129
+ language tags (see <<language-tagged-values>>). Mutually exclusive with
130
+ `uri_reference`.
131
+ * `uri_reference:` (default: `false`) — if true, values are serialized as URI
132
+ references rather than string literals (see <<uri-reference-predicates>>).
133
+ Mutually exclusive with `lang_tagged`.
123
134
 
124
135
  ==== members
125
136
 
126
137
  Declares that a container model contains member resources that should be
127
138
  serialized as separate subjects in the output graph (see <<graph-serialization>>).
128
139
 
140
+ Optional linking predicate parameters generate relationship triples from the
141
+ container to each member:
142
+
143
+ [source,ruby]
144
+ ----
145
+ members :concepts, predicate_name: :member, namespace: SkosNamespace
146
+ ----
147
+
148
+ When `predicate_name` and `namespace` are provided:
149
+
150
+ * **Turtle**: produces `skos:member <child-uri>` triples on the container subject
151
+ * **JSON-LD**: adds a `member` term to `@context` with `"@type": "@id"` and
152
+ `{"@id": "..."}` references in the container resource
153
+
154
+ Parameters:
155
+
156
+ * `attr_name` — the model attribute holding the member collection (required)
157
+ * `predicate_name:` — the local name for the linking predicate (optional)
158
+ * `namespace:` — the `Lutaml::Rdf::Namespace` subclass for the linking predicate
159
+ (required when `predicate_name` is given)
160
+
129
161
  == Serialization
130
162
 
131
163
  === Turtle
@@ -207,6 +239,48 @@ class LocalizedLiteral < Lutaml::Model::Serializable
207
239
  end
208
240
  ----
209
241
 
242
+ == [[uri-reference-predicates]]URI Reference Predicates
243
+
244
+ Predicates declared with `uri_reference: true` serialize attribute values as
245
+ URI references rather than string literals:
246
+
247
+ [source,ruby]
248
+ ----
249
+ predicate :related, namespace: SkosNamespace, to: :related, uri_reference: true
250
+ ----
251
+
252
+ **Turtle:**
253
+
254
+ Values are emitted as URI objects without quotes. Compact IRIs (e.g.
255
+ `"skos:other"`) are resolved to full URIs using the declared namespaces. Full
256
+ URIs (e.g. `"http://example.org/foo"`) are used as-is.
257
+
258
+ [source,turtle]
259
+ ----
260
+ <http://example.org/concept/1> skos:related skos:other .
261
+ ----
262
+
263
+ **JSON-LD:**
264
+
265
+ Values are wrapped in `{"@id": ...}` objects. The auto-generated `@context`
266
+ includes a term definition with `"@type": "@id"`:
267
+
268
+ [source,json]
269
+ ----
270
+ {
271
+ "@context": {
272
+ "related": { "@id": "http://www.w3.org/2004/02/skos/core#related", "@type": "@id" }
273
+ },
274
+ "related": [ { "@id": "skos:other" } ]
275
+ }
276
+ ----
277
+
278
+ Round-trip fidelity: compact IRI forms are preserved through serialization and
279
+ deserialization. Values round-trip as `Concept.from_turtle(concept.to_turtle)`
280
+ preserving the original `"skos:other"` compact form.
281
+
282
+ The `uri_reference` option is mutually exclusive with `lang_tagged`.
283
+
210
284
  == [[graph-serialization]]Graph Serialization
211
285
 
212
286
  When a container model holds a collection of member resources, use `members` to
@@ -227,11 +301,16 @@ class Vocabulary < Lutaml::Model::Serializable
227
301
  type "skos:ConceptScheme"
228
302
  predicate :prefLabel, namespace: SkosNamespace, to: :id
229
303
 
230
- members :concepts
304
+ members :concepts,
305
+ predicate_name: :member,
306
+ namespace: SkosNamespace
231
307
  end
232
308
  end
233
309
  ----
234
310
 
311
+ The `predicate_name` and `namespace` parameters on `members` generate linking
312
+ triples from the container to each member.
313
+
235
314
  === Turtle Output with Members
236
315
 
237
316
  [source,ruby]
@@ -247,7 +326,9 @@ Produces a single Turtle document with the container and all member triples:
247
326
  @prefix skos: <http://www.w3.org/2004/02/skos/core#> .
248
327
 
249
328
  <http://example.org/vocab/iso1087> a skos:ConceptScheme;
250
- skos:prefLabel "iso1087" .
329
+ skos:prefLabel "iso1087";
330
+ skos:member <http://example.org/concept/2119>;
331
+ skos:member <http://example.org/concept/2120> .
251
332
 
252
333
  <http://example.org/concept/2119> a skos:Concept;
253
334
  skos:notation "2119";
@@ -272,14 +353,19 @@ Produces a JSON-LD document with `@graph` containing all resources:
272
353
  {
273
354
  "@context": {
274
355
  "skos": "http://www.w3.org/2004/02/skos/core#",
275
- "prefLabel": "skos:prefLabel",
276
- "notation": "skos:notation"
356
+ "prefLabel": { "@id": "skos:prefLabel" },
357
+ "notation": { "@id": "skos:notation" },
358
+ "member": { "@id": "http://www.w3.org/2004/02/skos/core#member", "@type": "@id" }
277
359
  },
278
360
  "@graph": [
279
361
  {
280
362
  "@id": "http://example.org/vocab/iso1087",
281
363
  "@type": "skos:ConceptScheme",
282
- "prefLabel": "iso1087"
364
+ "prefLabel": "iso1087",
365
+ "member": [
366
+ { "@id": "http://example.org/concept/2119" },
367
+ { "@id": "http://example.org/concept/2120" }
368
+ ]
283
369
  },
284
370
  {
285
371
  "@id": "http://example.org/concept/2119",
@@ -73,9 +73,18 @@ Member models (serialized via a container's `members` declaration) can omit the
73
73
 
74
74
  === type
75
75
 
76
- Sets the RDF type (`rdf:type`) triple. Compact IRIs (e.g., `"skos:Concept"`)
77
- are resolved to full URIs via the declared namespaces. Full URIs (e.g.,
78
- `"http://example.org/MyType"`) are used as-is.
76
+ Sets the RDF type (`rdf:type`) triple. Accepts a single compact IRI or an array:
77
+
78
+ [source,ruby]
79
+ ----
80
+ type "skos:Concept"
81
+ # or multiple types:
82
+ type ["skos:Concept", "dcterms:Agent"]
83
+ ----
84
+
85
+ Compact IRIs are resolved to full URIs via the declared namespaces. Full URIs
86
+ (e.g., `"http://example.org/MyType"`) are used as-is. Each type produces a
87
+ separate `a` triple in the output.
79
88
 
80
89
  === predicate
81
90
 
@@ -85,7 +94,11 @@ Each `predicate` creates a `Lutaml::Rdf::MappingRule` value object:
85
94
  * `namespace:` — the `Lutaml::Rdf::Namespace` subclass (required, validated)
86
95
  * `to:` — the model attribute to read/write (required)
87
96
  * `lang_tagged:` (default: `false`) — if true, appends `@lang` suffix from the
88
- value's `language_code` or `language` method
97
+ value's `language_code` or `language` method. Mutually exclusive with
98
+ `uri_reference`.
99
+ * `uri_reference:` (default: `false`) — if true, serializes values as URI
100
+ objects rather than string literals. Compact IRIs are resolved via the
101
+ declared namespaces. Mutually exclusive with `lang_tagged`.
89
102
 
90
103
  Validation errors:
91
104
 
@@ -35,7 +35,7 @@ module Lutaml
35
35
  value = hash[rule.predicate_name]
36
36
  next if value.nil?
37
37
 
38
- attrs[rule.to] = if rule.lang_tagged && value.is_a?(Hash)
38
+ attrs[rule.to] = if rule.kind == :lang_tagged && value.is_a?(Hash)
39
39
  flatten_language_map(value)
40
40
  else
41
41
  value
@@ -61,9 +61,8 @@ module Lutaml
61
61
  end
62
62
 
63
63
  mapping.rdf_members.each do |member_rule|
64
- collection = Array(instance.public_send(member_rule.attr_name))
65
- collection.each do |member|
66
- member_mapping = member.class.mappings[:jsonld]
64
+ each_member(instance, member_rule) do |member|
65
+ member_mapping = member_mapping_for(member, :jsonld)
67
66
  next unless member_mapping
68
67
 
69
68
  resource = build_resource_data(member_mapping, member)
@@ -78,13 +77,12 @@ module Lutaml
78
77
  context_hash = build_context_from_mapping(mapping).to_hash
79
78
 
80
79
  mapping.rdf_members.each do |member_rule|
81
- collection = Array(instance.public_send(member_rule.attr_name))
82
- next if collection.empty?
83
-
84
- member_mapping = collection.first.class.mappings[:jsonld]
85
- next unless member_mapping
80
+ each_member(instance, member_rule) do |member|
81
+ member_mapping = member_mapping_for(member, :jsonld)
82
+ next unless member_mapping
86
83
 
87
- context_hash.merge!(build_context_from_mapping(member_mapping).to_hash)
84
+ context_hash.merge!(build_context_from_mapping(member_mapping).to_hash)
85
+ end
88
86
  end
89
87
 
90
88
  context_hash
@@ -94,17 +92,30 @@ module Lutaml
94
92
  context = Context.new
95
93
  mapping.namespace_set.each { |ns| context.prefix(ns) }
96
94
  mapping.rdf_predicates.each do |pred|
97
- if pred.lang_tagged
98
- context.term(pred.predicate_name,
99
- id: pred.uri,
100
- container: :language)
101
- else
102
- context.term(pred.predicate_name, id: pred.uri)
103
- end
95
+ options = { id: pred.uri }
96
+ term_options = context_term_options(pred)
97
+ context.term(pred.predicate_name, **options, **term_options)
104
98
  end
99
+
100
+ mapping.rdf_members.each do |member_rule|
101
+ next unless member_rule.linked?
102
+
103
+ context.term(member_rule.predicate_name.to_s,
104
+ id: member_rule.linked_predicate_uri,
105
+ type: "@id")
106
+ end
107
+
105
108
  context
106
109
  end
107
110
 
111
+ def context_term_options(rule)
112
+ case rule.kind
113
+ when :uri_reference then { type: "@id" }
114
+ when :lang_tagged then { container: :language }
115
+ else {}
116
+ end
117
+ end
118
+
108
119
  def build_resource_object(mapping, instance)
109
120
  context = build_context_from_mapping(mapping).to_hash
110
121
  data = build_resource_data(mapping, instance)
@@ -114,8 +125,12 @@ module Lutaml
114
125
  def build_resource_data(mapping, instance)
115
126
  result = {}
116
127
 
117
- if mapping.rdf_type
118
- result["@type"] = resolve_type_compact(mapping)
128
+ if mapping.rdf_type.any?
129
+ result["@type"] = if mapping.rdf_type.length == 1
130
+ mapping.rdf_type.first
131
+ else
132
+ mapping.rdf_type
133
+ end
119
134
  end
120
135
 
121
136
  if mapping.rdf_subject
@@ -127,16 +142,47 @@ module Lutaml
127
142
  next if value.nil?
128
143
  next if value.is_a?(String) && value.empty?
129
144
 
130
- result[rule.predicate_name] = if rule.lang_tagged
131
- build_language_map(value)
132
- else
133
- serialize_rdf_value(value)
134
- end
145
+ result[rule.predicate_name] = serialize_value(value, rule)
146
+ end
147
+
148
+ mapping.rdf_members.each do |member_rule|
149
+ next unless member_rule.linked?
150
+
151
+ member_refs = collect_member_references(instance, member_rule)
152
+ next if member_refs.empty?
153
+
154
+ result[member_rule.predicate_name.to_s] = member_refs
135
155
  end
136
156
 
137
157
  result
138
158
  end
139
159
 
160
+ def collect_member_references(instance, member_rule)
161
+ refs = []
162
+ each_member(instance, member_rule) do |member|
163
+ member_mapping = member_mapping_for(member, :jsonld)
164
+ next unless member_mapping
165
+
166
+ refs << { "@id" => resolve_subject_uri(member_mapping, member) }
167
+ end
168
+ refs
169
+ end
170
+
171
+ def serialize_value(value, rule)
172
+ case rule.kind
173
+ when :uri_reference then serialize_uri_reference(value)
174
+ when :lang_tagged then build_language_map(value)
175
+ else serialize_rdf_value(value)
176
+ end
177
+ end
178
+
179
+ def serialize_uri_reference(value)
180
+ case value
181
+ when Array then value.map { |v| { "@id" => v.to_s } }
182
+ else { "@id" => value.to_s }
183
+ end
184
+ end
185
+
140
186
  def build_language_map(values)
141
187
  case values
142
188
  when Array
@@ -33,7 +33,7 @@ module Lutaml
33
33
 
34
34
  # transformation_for returns nil for cyclic dependencies or :building sentinel
35
35
  # Fall back to legacy approach in these cases
36
- if transformation.respond_to?(:transform)
36
+ if transformation.is_a?(Lutaml::KeyValue::Transformation)
37
37
  # Use new Transformation to get KeyValueElement
38
38
  kv_element = transformation.transform(instance, options)
39
39
  # Convert KeyValueElement to hash for backward compatibility with adapters
@@ -68,7 +68,7 @@ module Lutaml
68
68
 
69
69
  def process_mapping_for_instance(instance, hash, format, rule, options)
70
70
  if rule.custom_methods[:to]
71
- return instance.send(rule.custom_methods[:to], instance, hash)
71
+ return instance.public_send(rule.custom_methods[:to], instance, hash)
72
72
  end
73
73
 
74
74
  attribute = attributes[rule.to]
@@ -223,13 +223,13 @@ format)
223
223
  value = extract_value_for_delegate(instance, rule)
224
224
  return if value.nil? && rule.value_map(:to)[:nil] == :omitted
225
225
 
226
- attribute = instance.send(rule.delegate).class.attributes(lutaml_register)[rule.to]
226
+ attribute = instance.public_send(rule.delegate).class.attributes(lutaml_register)[rule.to]
227
227
  hash[rule_from_name(rule)] =
228
228
  attribute.serialize(value, format, lutaml_register)
229
229
  end
230
230
 
231
231
  def extract_value_for_delegate(instance, rule)
232
- instance.send(rule.delegate).send(rule.to)
232
+ instance.public_send(rule.delegate).public_send(rule.to)
233
233
  end
234
234
 
235
235
  def extract_mappings(options, format)
@@ -268,7 +268,7 @@ format)
268
268
  def process_custom_method(rule, instance, value)
269
269
  return unless Lutaml::Model::Utils.present?(value)
270
270
 
271
- model_class.new.send(rule.custom_methods[:from], instance, value)
271
+ model_class.new.public_send(rule.custom_methods[:from], instance, value)
272
272
  end
273
273
 
274
274
  def cast_value(value, attr, format, rule, instance)