lutaml-model 0.8.10 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/dependent-repos.json +1 -0
- data/.rubocop_todo.yml +56 -6
- data/README.adoc +43 -0
- data/docs/_guides/jsonld-serialization.adoc +3 -1
- data/docs/_guides/rdf-serialization.adoc +94 -8
- data/docs/_guides/turtle-serialization.adoc +17 -4
- data/lib/lutaml/jsonld/transform.rb +70 -24
- data/lib/lutaml/model/store.rb +51 -4
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/rdf/mapping.rb +19 -13
- data/lib/lutaml/rdf/mapping_rule.rb +19 -2
- data/lib/lutaml/rdf/member_rule.rb +19 -2
- data/lib/lutaml/rdf/transform.rb +20 -11
- data/lib/lutaml/turtle/transform.rb +125 -53
- data/spec/lutaml/jsonld/transform_spec.rb +239 -0
- data/spec/lutaml/model/store_spec.rb +156 -2
- data/spec/lutaml/rdf/mapping_rule_spec.rb +97 -0
- data/spec/lutaml/rdf/mapping_spec.rb +74 -4
- data/spec/lutaml/rdf/member_rule_spec.rb +41 -0
- data/spec/lutaml/rdf/rdf_transform_spec.rb +95 -29
- data/spec/lutaml/turtle/mapping_spec.rb +2 -2
- data/spec/lutaml/turtle/transform_spec.rb +315 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 992b8e51bee9c168f1dbb4cac0fcfd75f593f1c40cc29abce7e2163088e6fc48
|
|
4
|
+
data.tar.gz: 8e3524b5e4336e8cb289c3f31b513727bb2bbb23d3429fcd770425b6f24de489
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 61c349cf6a1476a0c8668ea19b45e2989275a00d97ab718613eb80cfdc86a2e71ba0cb344c69a6877c5447a57d715f080470c3b28dce0c046652502182b29408
|
|
7
|
+
data.tar.gz: b85e8858876efa7b98acec6df1ff36e2ebbbf772c8b8cf035a459374159d2c5d704c13cbcecc99a69561438884d05bc4f52d13e12d6cd1b3f63611761344bd04
|
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-
|
|
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,13 +11,52 @@ Gemspec/RequiredRubyVersion:
|
|
|
11
11
|
Exclude:
|
|
12
12
|
- 'lutaml-model.gemspec'
|
|
13
13
|
|
|
14
|
-
# Offense count:
|
|
14
|
+
# Offense count: 2
|
|
15
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
16
|
+
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
|
17
|
+
# SupportedStyles: with_first_argument, with_fixed_indentation
|
|
18
|
+
Layout/ArgumentAlignment:
|
|
19
|
+
Exclude:
|
|
20
|
+
- 'lib/lutaml/turtle/transform.rb'
|
|
21
|
+
- 'spec/lutaml/model/store_spec.rb'
|
|
22
|
+
|
|
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
|
|
15
46
|
# This cop supports safe autocorrection (--autocorrect).
|
|
16
47
|
# Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
|
|
17
48
|
# URISchemes: http, https
|
|
18
49
|
Layout/LineLength:
|
|
19
50
|
Enabled: false
|
|
20
51
|
|
|
52
|
+
# Offense count: 2
|
|
53
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
54
|
+
# Configuration parameters: AllowInHeredoc.
|
|
55
|
+
Layout/TrailingWhitespace:
|
|
56
|
+
Exclude:
|
|
57
|
+
- 'lib/lutaml/turtle/transform.rb'
|
|
58
|
+
- 'spec/lutaml/model/store_spec.rb'
|
|
59
|
+
|
|
21
60
|
# Offense count: 21
|
|
22
61
|
# Configuration parameters: AllowedMethods.
|
|
23
62
|
# AllowedMethods: enums
|
|
@@ -130,12 +169,12 @@ Metrics/BlockLength:
|
|
|
130
169
|
Metrics/BlockNesting:
|
|
131
170
|
Max: 6
|
|
132
171
|
|
|
133
|
-
# Offense count:
|
|
172
|
+
# Offense count: 308
|
|
134
173
|
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
|
|
135
174
|
Metrics/CyclomaticComplexity:
|
|
136
175
|
Enabled: false
|
|
137
176
|
|
|
138
|
-
# Offense count:
|
|
177
|
+
# Offense count: 553
|
|
139
178
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
|
140
179
|
Metrics/MethodLength:
|
|
141
180
|
Max: 514
|
|
@@ -249,7 +288,7 @@ RSpec/DescribeMethod:
|
|
|
249
288
|
- 'spec/lutaml/xml/schema/xsd/schema_helper_methods_spec.rb'
|
|
250
289
|
- 'spec/lutaml/xml/serializable_namespace_spec.rb'
|
|
251
290
|
|
|
252
|
-
# Offense count:
|
|
291
|
+
# Offense count: 1285
|
|
253
292
|
# Configuration parameters: CountAsOne.
|
|
254
293
|
RSpec/ExampleLength:
|
|
255
294
|
Max: 68
|
|
@@ -324,7 +363,7 @@ RSpec/MultipleDescribes:
|
|
|
324
363
|
- 'spec/lutaml/xml/namespace_resolution_strategy_spec.rb'
|
|
325
364
|
- 'spec/lutaml/xml/xml_space_type_spec.rb'
|
|
326
365
|
|
|
327
|
-
# Offense count:
|
|
366
|
+
# Offense count: 1507
|
|
328
367
|
RSpec/MultipleExpectations:
|
|
329
368
|
Max: 21
|
|
330
369
|
|
|
@@ -398,6 +437,17 @@ Security/MarshalLoad:
|
|
|
398
437
|
Exclude:
|
|
399
438
|
- 'scripts-xmi-profile/profile_xmi.rb'
|
|
400
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
|
+
|
|
401
451
|
# Offense count: 2
|
|
402
452
|
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
403
453
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
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`).
|
|
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.
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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"] =
|
|
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] =
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
data/lib/lutaml/model/store.rb
CHANGED
|
@@ -5,9 +5,15 @@ require "weakref"
|
|
|
5
5
|
module Lutaml
|
|
6
6
|
module Model
|
|
7
7
|
class Store
|
|
8
|
-
# Compact dead WeakRef shells
|
|
8
|
+
# Compact dead WeakRef shells once a class bucket grows past this size.
|
|
9
9
|
COMPACTION_THRESHOLD = 1000
|
|
10
10
|
|
|
11
|
+
# Once the threshold is exceeded, only compact every Nth subsequent
|
|
12
|
+
# register call. Amortises the O(N) reject! over N inserts so
|
|
13
|
+
# register stays O(1) per call rather than O(N) per call (O(N^2)
|
|
14
|
+
# cumulatively for the class).
|
|
15
|
+
COMPACTION_INTERVAL = 1000
|
|
16
|
+
|
|
11
17
|
class << self
|
|
12
18
|
def instance
|
|
13
19
|
@instance ||= new
|
|
@@ -39,14 +45,17 @@ module Lutaml
|
|
|
39
45
|
# Nested index: { model_key => { reference_key => { value => WeakRef(object) } } }
|
|
40
46
|
# Grouped by model_key so register only iterates this class's own indices.
|
|
41
47
|
@index = {}
|
|
48
|
+
@inserts_since_compaction = ::Hash.new(0)
|
|
49
|
+
@compaction_count = 0
|
|
42
50
|
end
|
|
43
51
|
|
|
44
52
|
def register(object)
|
|
45
53
|
model_key = object.class.to_s
|
|
46
54
|
refs = @store[model_key]
|
|
47
55
|
refs << WeakRef.new(object)
|
|
56
|
+
@inserts_since_compaction[model_key] += 1
|
|
48
57
|
|
|
49
|
-
compact_if_needed(refs)
|
|
58
|
+
compact_if_needed(refs, model_key)
|
|
50
59
|
|
|
51
60
|
update_existing_indices(object, model_key)
|
|
52
61
|
end
|
|
@@ -63,12 +72,16 @@ module Lutaml
|
|
|
63
72
|
entry = model_indices[reference_key][reference_value]
|
|
64
73
|
return nil unless entry
|
|
65
74
|
|
|
66
|
-
dereference(entry)
|
|
75
|
+
obj = dereference(entry)
|
|
76
|
+
model_indices[reference_key].delete(reference_value) unless obj
|
|
77
|
+
obj
|
|
67
78
|
end
|
|
68
79
|
|
|
69
80
|
def clear
|
|
70
81
|
@store = ::Hash.new { |hash, key| hash[key] = [] }
|
|
71
82
|
@index = {}
|
|
83
|
+
@inserts_since_compaction = ::Hash.new(0)
|
|
84
|
+
@compaction_count = 0
|
|
72
85
|
end
|
|
73
86
|
|
|
74
87
|
def store
|
|
@@ -81,6 +94,22 @@ module Lutaml
|
|
|
81
94
|
end
|
|
82
95
|
end
|
|
83
96
|
|
|
97
|
+
def refs_for(model_key)
|
|
98
|
+
@store[model_key]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def inserts_since_compaction
|
|
102
|
+
@inserts_since_compaction
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def compaction_count
|
|
106
|
+
@compaction_count
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def index_entry_count(model_key)
|
|
110
|
+
@index[model_key]&.sum { |_reference_key, entries| entries.size } || 0
|
|
111
|
+
end
|
|
112
|
+
|
|
84
113
|
private
|
|
85
114
|
|
|
86
115
|
def ensure_model_index(model_key)
|
|
@@ -120,14 +149,32 @@ module Lutaml
|
|
|
120
149
|
nil
|
|
121
150
|
end
|
|
122
151
|
|
|
123
|
-
def compact_if_needed(refs)
|
|
152
|
+
def compact_if_needed(refs, model_key)
|
|
124
153
|
return unless refs.size > COMPACTION_THRESHOLD
|
|
154
|
+
return unless @inserts_since_compaction[model_key] >= COMPACTION_INTERVAL
|
|
125
155
|
|
|
156
|
+
@inserts_since_compaction[model_key] = 0
|
|
157
|
+
@compaction_count += 1
|
|
126
158
|
refs.reject! do |ref|
|
|
127
159
|
!ref.weakref_alive?
|
|
128
160
|
rescue WeakRef::RefError
|
|
129
161
|
true
|
|
130
162
|
end
|
|
163
|
+
prune_index(model_key)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def prune_index(model_key)
|
|
167
|
+
model_indices = @index[model_key]
|
|
168
|
+
return unless model_indices
|
|
169
|
+
|
|
170
|
+
model_indices.delete_if do |_reference_key, entries|
|
|
171
|
+
entries.delete_if do |_value, ref|
|
|
172
|
+
!ref.weakref_alive?
|
|
173
|
+
rescue WeakRef::RefError
|
|
174
|
+
true
|
|
175
|
+
end
|
|
176
|
+
entries.empty?
|
|
177
|
+
end
|
|
131
178
|
end
|
|
132
179
|
end
|
|
133
180
|
end
|