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
data/lib/lutaml/rdf/mapping.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Lutaml
|
|
|
10
10
|
super
|
|
11
11
|
@namespace_set = Lutaml::Rdf::NamespaceSet.new
|
|
12
12
|
@rdf_subject = nil
|
|
13
|
-
@rdf_type =
|
|
13
|
+
@rdf_type = []
|
|
14
14
|
@rdf_predicates = []
|
|
15
15
|
@rdf_members = []
|
|
16
16
|
end
|
|
@@ -24,20 +24,26 @@ module Lutaml
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def type(value)
|
|
27
|
-
@rdf_type = value
|
|
27
|
+
@rdf_type = Array(value)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
def predicate(name, namespace:, to:, lang_tagged: false
|
|
30
|
+
def predicate(name, namespace:, to:, lang_tagged: false,
|
|
31
|
+
uri_reference: false)
|
|
31
32
|
@rdf_predicates << Lutaml::Rdf::MappingRule.new(
|
|
32
33
|
name,
|
|
33
34
|
namespace: namespace,
|
|
34
35
|
to: to,
|
|
35
36
|
lang_tagged: lang_tagged,
|
|
37
|
+
uri_reference: uri_reference,
|
|
36
38
|
)
|
|
37
39
|
end
|
|
38
40
|
|
|
39
|
-
def members(attr_name)
|
|
40
|
-
@rdf_members << Lutaml::Rdf::MemberRule.new(
|
|
41
|
+
def members(attr_name, predicate_name: nil, namespace: nil)
|
|
42
|
+
@rdf_members << Lutaml::Rdf::MemberRule.new(
|
|
43
|
+
attr_name,
|
|
44
|
+
predicate_name: predicate_name,
|
|
45
|
+
namespace: namespace,
|
|
46
|
+
)
|
|
41
47
|
end
|
|
42
48
|
|
|
43
49
|
def mappings(_register_id = nil)
|
|
@@ -57,14 +63,14 @@ module Lutaml
|
|
|
57
63
|
end
|
|
58
64
|
|
|
59
65
|
def deep_dup
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
dup
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def initialize_copy(source)
|
|
70
|
+
super
|
|
71
|
+
@rdf_type = source.rdf_type.dup
|
|
72
|
+
@rdf_predicates = source.rdf_predicates.dup
|
|
73
|
+
@rdf_members = source.rdf_members.dup
|
|
68
74
|
end
|
|
69
75
|
end
|
|
70
76
|
end
|
|
@@ -3,14 +3,31 @@
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Rdf
|
|
5
5
|
class MappingRule
|
|
6
|
-
attr_reader :predicate_name, :namespace, :to, :lang_tagged
|
|
6
|
+
attr_reader :predicate_name, :namespace, :to, :lang_tagged, :uri_reference
|
|
7
7
|
|
|
8
|
-
def initialize(predicate_name, namespace:, to:, lang_tagged: false
|
|
8
|
+
def initialize(predicate_name, namespace:, to:, lang_tagged: false,
|
|
9
|
+
uri_reference: false)
|
|
9
10
|
validate!(predicate_name, namespace, to)
|
|
11
|
+
if lang_tagged && uri_reference
|
|
12
|
+
raise ArgumentError,
|
|
13
|
+
"lang_tagged and uri_reference are mutually exclusive"
|
|
14
|
+
end
|
|
15
|
+
|
|
10
16
|
@predicate_name = predicate_name.to_s.freeze
|
|
11
17
|
@namespace = namespace
|
|
12
18
|
@to = to
|
|
13
19
|
@lang_tagged = lang_tagged
|
|
20
|
+
@uri_reference = uri_reference
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def kind
|
|
24
|
+
if uri_reference
|
|
25
|
+
:uri_reference
|
|
26
|
+
elsif lang_tagged
|
|
27
|
+
:lang_tagged
|
|
28
|
+
else
|
|
29
|
+
:plain
|
|
30
|
+
end
|
|
14
31
|
end
|
|
15
32
|
|
|
16
33
|
def uri
|
|
@@ -3,10 +3,27 @@
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Rdf
|
|
5
5
|
class MemberRule
|
|
6
|
-
attr_reader :attr_name
|
|
6
|
+
attr_reader :attr_name, :predicate_name, :namespace
|
|
7
|
+
|
|
8
|
+
def initialize(attr_name, predicate_name: nil, namespace: nil)
|
|
9
|
+
if predicate_name && !namespace
|
|
10
|
+
raise ArgumentError,
|
|
11
|
+
"namespace is required when predicate_name is provided"
|
|
12
|
+
end
|
|
7
13
|
|
|
8
|
-
def initialize(attr_name)
|
|
9
14
|
@attr_name = attr_name.to_sym
|
|
15
|
+
@predicate_name = predicate_name
|
|
16
|
+
@namespace = namespace
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def linked?
|
|
20
|
+
!!@predicate_name
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def linked_predicate_uri
|
|
24
|
+
return nil unless linked?
|
|
25
|
+
|
|
26
|
+
@namespace[@predicate_name]
|
|
10
27
|
end
|
|
11
28
|
end
|
|
12
29
|
end
|
data/lib/lutaml/rdf/transform.rb
CHANGED
|
@@ -3,22 +3,35 @@
|
|
|
3
3
|
module Lutaml
|
|
4
4
|
module Rdf
|
|
5
5
|
class Transform < Lutaml::Model::Transform
|
|
6
|
-
protected
|
|
7
|
-
|
|
8
6
|
def resolve_subject_uri(mapping, instance)
|
|
9
7
|
mapping.rdf_subject&.call(instance)
|
|
10
8
|
end
|
|
11
9
|
|
|
12
|
-
def
|
|
13
|
-
|
|
10
|
+
def resolve_single_type_uri(mapping, type_value)
|
|
11
|
+
mapping.namespace_set.resolve_compact_iri(type_value)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def resolve_type_uris(mapping)
|
|
15
|
+
return [] unless mapping.rdf_type.any?
|
|
16
|
+
|
|
17
|
+
mapping.rdf_type.map { |t| resolve_single_type_uri(mapping, t) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def each_member(instance, member_rule, &)
|
|
21
|
+
collection = Array(instance.public_send(member_rule.attr_name))
|
|
22
|
+
collection.each(&)
|
|
23
|
+
end
|
|
14
24
|
|
|
15
|
-
|
|
25
|
+
def member_mapping_for(member, format)
|
|
26
|
+
member.class.mappings[format]
|
|
16
27
|
end
|
|
17
28
|
|
|
18
|
-
def
|
|
19
|
-
|
|
29
|
+
def extract_language(value)
|
|
30
|
+
value.language_tag if value.is_a?(Lutaml::Rdf::LanguageTagged)
|
|
20
31
|
end
|
|
21
32
|
|
|
33
|
+
protected
|
|
34
|
+
|
|
22
35
|
def build_instance(attrs, options)
|
|
23
36
|
child_register = Lutaml::Model::Register.resolve_for_child(
|
|
24
37
|
model_class, lutaml_register
|
|
@@ -27,10 +40,6 @@ module Lutaml
|
|
|
27
40
|
root_and_parent_assignment(instance, options)
|
|
28
41
|
instance
|
|
29
42
|
end
|
|
30
|
-
|
|
31
|
-
def extract_language(value)
|
|
32
|
-
value.language_tag if value.is_a?(Lutaml::Rdf::LanguageTagged)
|
|
33
|
-
end
|
|
34
43
|
end
|
|
35
44
|
end
|
|
36
45
|
end
|
|
@@ -46,56 +46,101 @@ module Lutaml
|
|
|
46
46
|
def build_graph(mapping, instance)
|
|
47
47
|
graph = RDF::Graph.new
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
49
|
+
has_resource_data =
|
|
50
|
+
mapping.rdf_type.any? ||
|
|
51
|
+
mapping.rdf_predicates.any? ||
|
|
52
|
+
mapping.rdf_members.any?(&:linked?)
|
|
53
|
+
|
|
54
|
+
if has_resource_data
|
|
55
|
+
subject_uri = resolve_subject(mapping, instance)
|
|
56
|
+
build_resource_triples(graph, mapping, instance, subject_uri)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
build_member_subgraphs(graph, mapping, instance)
|
|
60
|
+
|
|
61
|
+
graph
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def resolve_subject(mapping, instance)
|
|
65
|
+
if mapping.rdf_subject
|
|
66
|
+
RDF::URI(resolve_subject_uri(mapping, instance))
|
|
67
|
+
else
|
|
68
|
+
RDF::Node.new
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def build_resource_triples(graph, mapping, instance, subject_uri)
|
|
73
|
+
mapping.rdf_type.each do |type_value|
|
|
74
|
+
type_uri = RDF::URI(resolve_single_type_uri(mapping, type_value))
|
|
75
|
+
graph << RDF::Statement.new(subject_uri, RDF.type, type_uri)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
mapping.rdf_predicates.each do |rule|
|
|
79
|
+
value = instance.public_send(rule.to)
|
|
80
|
+
next if value.nil?
|
|
81
|
+
|
|
82
|
+
Array(value).each do |v|
|
|
83
|
+
object = build_rdf_object(v, rule, mapping.namespace_set)
|
|
84
|
+
graph << RDF::Statement.new(subject_uri, RDF::URI(rule.uri), object)
|
|
61
85
|
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
mapping.rdf_members.each do |member_rule|
|
|
89
|
+
next unless member_rule.linked?
|
|
62
90
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
next
|
|
91
|
+
each_member(instance, member_rule) do |member|
|
|
92
|
+
member_mapping = member_mapping_for(member, :turtle)
|
|
93
|
+
next unless member_mapping
|
|
66
94
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
95
|
+
member_subject = RDF::URI(resolve_subject_uri(member_mapping,
|
|
96
|
+
member))
|
|
97
|
+
graph << RDF::Statement.new(
|
|
98
|
+
subject_uri,
|
|
99
|
+
RDF::URI(member_rule.linked_predicate_uri),
|
|
100
|
+
member_subject,
|
|
101
|
+
)
|
|
72
102
|
end
|
|
73
103
|
end
|
|
104
|
+
end
|
|
74
105
|
|
|
106
|
+
def build_member_subgraphs(graph, mapping, instance)
|
|
75
107
|
mapping.rdf_members.each do |member_rule|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
member_mapping = member.class.mappings[:turtle]
|
|
108
|
+
each_member(instance, member_rule) do |member|
|
|
109
|
+
member_mapping = member_mapping_for(member, :turtle)
|
|
79
110
|
next unless member_mapping
|
|
80
111
|
|
|
81
112
|
graph << build_graph(member_mapping, member)
|
|
82
113
|
end
|
|
83
114
|
end
|
|
84
|
-
|
|
85
|
-
graph
|
|
86
115
|
end
|
|
87
116
|
|
|
88
|
-
def build_rdf_object(value, rule)
|
|
89
|
-
|
|
117
|
+
def build_rdf_object(value, rule, namespace_set)
|
|
118
|
+
case rule.kind
|
|
119
|
+
when :uri_reference
|
|
120
|
+
build_uri_reference_object(value, namespace_set)
|
|
121
|
+
when :lang_tagged
|
|
90
122
|
lang = extract_language(value)
|
|
91
123
|
RDF::Literal.new(value.to_s, language: lang)
|
|
92
124
|
else
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
125
|
+
build_plain_literal(value)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def build_uri_reference_object(value, namespace_set)
|
|
130
|
+
resolved = if value.to_s.include?(":")
|
|
131
|
+
namespace_set.resolve_compact_iri(value.to_s)
|
|
132
|
+
else
|
|
133
|
+
value.to_s
|
|
134
|
+
end
|
|
135
|
+
RDF::URI.new(resolved)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def build_plain_literal(value)
|
|
139
|
+
case value
|
|
140
|
+
when Integer then RDF::Literal.new(value, datatype: RDF::XSD.integer)
|
|
141
|
+
when Float then RDF::Literal.new(value, datatype: RDF::XSD.double)
|
|
142
|
+
when TrueClass, FalseClass then RDF::Literal.new(value, datatype: RDF::XSD.boolean)
|
|
143
|
+
else RDF::Literal.new(value.to_s)
|
|
99
144
|
end
|
|
100
145
|
end
|
|
101
146
|
|
|
@@ -103,13 +148,12 @@ module Lutaml
|
|
|
103
148
|
ns_set = mapping.namespace_set
|
|
104
149
|
|
|
105
150
|
mapping.rdf_members.each do |member_rule|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
member_mapping = collection.first.class.mappings[:turtle]
|
|
110
|
-
next unless member_mapping
|
|
151
|
+
each_member(instance, member_rule) do |member|
|
|
152
|
+
member_mapping = member_mapping_for(member, :turtle)
|
|
153
|
+
next unless member_mapping
|
|
111
154
|
|
|
112
|
-
|
|
155
|
+
ns_set = ns_set.merge(member_mapping.namespace_set)
|
|
156
|
+
end
|
|
113
157
|
end
|
|
114
158
|
|
|
115
159
|
ns_set.each.with_object({}) do |ns, h|
|
|
@@ -119,38 +163,66 @@ module Lutaml
|
|
|
119
163
|
|
|
120
164
|
def extract_attributes(graph, mapping)
|
|
121
165
|
attrs = {}
|
|
122
|
-
|
|
166
|
+
type_uris = resolve_type_uris(mapping)
|
|
123
167
|
|
|
124
|
-
matching_subjects =
|
|
168
|
+
matching_subjects = find_subjects_by_types(graph, type_uris)
|
|
125
169
|
|
|
126
170
|
matching_subjects.each do |subject|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
next if stmts.empty?
|
|
130
|
-
|
|
131
|
-
values = stmts.map { |s| literal_to_ruby(s.object) }
|
|
132
|
-
attrs[rule.to] = values.length == 1 ? values.first : values
|
|
133
|
-
end
|
|
171
|
+
attrs["id"] = subject.to_s unless subject.node?
|
|
172
|
+
extract_predicate_attributes(graph, subject, mapping, attrs)
|
|
134
173
|
end
|
|
135
174
|
|
|
136
175
|
attrs
|
|
137
176
|
end
|
|
138
177
|
|
|
139
|
-
def
|
|
140
|
-
|
|
178
|
+
def extract_predicate_attributes(graph, subject, mapping, attrs)
|
|
179
|
+
mapping.rdf_predicates.each do |rule|
|
|
180
|
+
stmts = graph.query([subject, RDF::URI(rule.uri), nil])
|
|
181
|
+
next if stmts.empty?
|
|
182
|
+
|
|
183
|
+
values = stmts.map do |s|
|
|
184
|
+
literal_to_ruby(s.object, rule, mapping.namespace_set)
|
|
185
|
+
end
|
|
186
|
+
attrs[rule.to] = values.length == 1 ? values.first : values
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def find_subjects_by_types(graph, type_uris)
|
|
191
|
+
type_uris.flat_map do |type_uri|
|
|
192
|
+
graph.query([nil, RDF.type, RDF::URI(type_uri)]).map(&:subject).uniq
|
|
193
|
+
end.uniq
|
|
141
194
|
end
|
|
142
195
|
|
|
143
|
-
def literal_to_ruby(rdf_object)
|
|
196
|
+
def literal_to_ruby(rdf_object, rule, namespace_set)
|
|
144
197
|
case rdf_object
|
|
198
|
+
when RDF::URI
|
|
199
|
+
uri_to_ruby(rdf_object, rule, namespace_set)
|
|
145
200
|
when RDF::Literal
|
|
201
|
+
literal_value_to_ruby(rdf_object, rule)
|
|
202
|
+
else
|
|
203
|
+
rdf_object.to_s
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def uri_to_ruby(rdf_object, rule, namespace_set)
|
|
208
|
+
uri_str = rdf_object.to_s
|
|
209
|
+
if rule.kind == :uri_reference
|
|
210
|
+
namespace_set.compact(uri_str) || uri_str
|
|
211
|
+
else
|
|
212
|
+
uri_str
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def literal_value_to_ruby(rdf_object, rule)
|
|
217
|
+
if rule.kind == :lang_tagged && rdf_object.language
|
|
218
|
+
rdf_object.value
|
|
219
|
+
else
|
|
146
220
|
case rdf_object.datatype
|
|
147
221
|
when RDF::XSD.integer then rdf_object.value.to_i
|
|
148
222
|
when RDF::XSD.double, RDF::XSD.decimal, RDF::XSD.float then rdf_object.value.to_f
|
|
149
223
|
when RDF::XSD.boolean then rdf_object.value == "true"
|
|
150
224
|
else rdf_object.value
|
|
151
225
|
end
|
|
152
|
-
else
|
|
153
|
-
rdf_object.to_s
|
|
154
226
|
end
|
|
155
227
|
end
|
|
156
228
|
end
|
|
@@ -208,4 +208,243 @@ RSpec.describe Lutaml::JsonLd::Transform do
|
|
|
208
208
|
expect(restored.tags).to eq(["en", "fr"])
|
|
209
209
|
end
|
|
210
210
|
end
|
|
211
|
+
|
|
212
|
+
describe "multiple types" do
|
|
213
|
+
before do
|
|
214
|
+
stub_const("DctermsTestNs", Class.new(Lutaml::Rdf::Namespace) do
|
|
215
|
+
uri "http://purl.org/dc/terms/"
|
|
216
|
+
prefix "dcterms"
|
|
217
|
+
end)
|
|
218
|
+
|
|
219
|
+
stub_const("MultiTypeJsonLdModel", Class.new(Lutaml::Model::Serializable) do
|
|
220
|
+
attribute :name, :string
|
|
221
|
+
|
|
222
|
+
rdf do
|
|
223
|
+
namespace TestSkosNs, DctermsTestNs
|
|
224
|
+
|
|
225
|
+
subject { |m| "http://example.org/#{m.name}" } # rubocop:disable RSpec/NamedSubject
|
|
226
|
+
|
|
227
|
+
type ["skos:Concept", "dcterms:Agent"]
|
|
228
|
+
|
|
229
|
+
predicate :name, namespace: TestExNs, to: :name
|
|
230
|
+
end
|
|
231
|
+
end)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
it "generates @type as array for multiple types" do
|
|
235
|
+
instance = MultiTypeJsonLdModel.new(name: "multi")
|
|
236
|
+
parsed = JSON.parse(instance.to_jsonld)
|
|
237
|
+
expect(parsed["@type"]).to eq(["skos:Concept", "dcterms:Agent"])
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it "generates @type as string for single type" do
|
|
241
|
+
instance = JsonLdTestModel.new(name: "single")
|
|
242
|
+
parsed = JSON.parse(instance.to_jsonld)
|
|
243
|
+
expect(parsed["@type"]).to eq("skos:Concept")
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
describe "URI reference predicates" do
|
|
248
|
+
before do
|
|
249
|
+
stub_const("UriRefJsonLdModel", Class.new(Lutaml::Model::Serializable) do
|
|
250
|
+
attribute :name, :string
|
|
251
|
+
attribute :related, :string, collection: true
|
|
252
|
+
|
|
253
|
+
rdf do
|
|
254
|
+
namespace TestSkosNs, TestExNs
|
|
255
|
+
|
|
256
|
+
subject { |m| "http://example.org/#{m.name}" } # rubocop:disable RSpec/NamedSubject
|
|
257
|
+
|
|
258
|
+
type "skos:Concept"
|
|
259
|
+
|
|
260
|
+
predicate :name, namespace: TestExNs, to: :name
|
|
261
|
+
predicate :related, namespace: TestSkosNs, to: :related,
|
|
262
|
+
uri_reference: true
|
|
263
|
+
end
|
|
264
|
+
end)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
it "generates @type @id in context for uri_reference predicates" do
|
|
268
|
+
instance = UriRefJsonLdModel.new(name: "test", related: ["skos:other"])
|
|
269
|
+
parsed = JSON.parse(instance.to_jsonld)
|
|
270
|
+
expect(parsed["@context"]["related"]).to eq({
|
|
271
|
+
"@id" => "http://www.w3.org/2004/02/skos/core#related",
|
|
272
|
+
"@type" => "@id",
|
|
273
|
+
})
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
it "serializes URI reference as @id object" do
|
|
277
|
+
instance = UriRefJsonLdModel.new(name: "test", related: ["skos:other"])
|
|
278
|
+
parsed = JSON.parse(instance.to_jsonld)
|
|
279
|
+
expect(parsed["related"]).to eq([{ "@id" => "skos:other" }])
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
it "serializes single URI reference value as @id object" do
|
|
283
|
+
stub_const("SingleUriRefModel", Class.new(Lutaml::Model::Serializable) do
|
|
284
|
+
attribute :name, :string
|
|
285
|
+
attribute :link, :string
|
|
286
|
+
|
|
287
|
+
rdf do
|
|
288
|
+
namespace TestSkosNs, TestExNs
|
|
289
|
+
|
|
290
|
+
subject { |m| "http://example.org/#{m.name}" } # rubocop:disable RSpec/NamedSubject
|
|
291
|
+
|
|
292
|
+
type "skos:Concept"
|
|
293
|
+
|
|
294
|
+
predicate :name, namespace: TestExNs, to: :name
|
|
295
|
+
predicate :related, namespace: TestSkosNs, to: :link,
|
|
296
|
+
uri_reference: true
|
|
297
|
+
end
|
|
298
|
+
end)
|
|
299
|
+
|
|
300
|
+
instance = SingleUriRefModel.new(name: "test", link: "skos:something")
|
|
301
|
+
parsed = JSON.parse(instance.to_jsonld)
|
|
302
|
+
expect(parsed["related"]).to eq({ "@id" => "skos:something" })
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
describe "member linking predicates" do
|
|
307
|
+
before do
|
|
308
|
+
stub_const("JsonLdChildModel", Class.new(Lutaml::Model::Serializable) do
|
|
309
|
+
attribute :cid, :string
|
|
310
|
+
attribute :label, :string
|
|
311
|
+
|
|
312
|
+
rdf do
|
|
313
|
+
namespace TestSkosNs, TestExNs
|
|
314
|
+
|
|
315
|
+
subject { |m| "http://example.org/item/#{m.cid}" } # rubocop:disable RSpec/NamedSubject, RSpec/MultipleSubjects
|
|
316
|
+
|
|
317
|
+
type "skos:Concept"
|
|
318
|
+
|
|
319
|
+
predicate :prefLabel, namespace: TestSkosNs, to: :label
|
|
320
|
+
end
|
|
321
|
+
end)
|
|
322
|
+
|
|
323
|
+
stub_const("JsonLdParentModel", Class.new(Lutaml::Model::Serializable) do
|
|
324
|
+
attribute :name, :string
|
|
325
|
+
attribute :children, JsonLdChildModel, collection: true
|
|
326
|
+
|
|
327
|
+
rdf do
|
|
328
|
+
namespace TestSkosNs, TestExNs
|
|
329
|
+
|
|
330
|
+
subject { |m| "http://example.org/group/#{m.name}" } # rubocop:disable RSpec/NamedSubject, RSpec/MultipleSubjects
|
|
331
|
+
|
|
332
|
+
type "skos:Collection"
|
|
333
|
+
|
|
334
|
+
predicate :prefLabel, namespace: TestSkosNs, to: :name
|
|
335
|
+
|
|
336
|
+
members :children,
|
|
337
|
+
predicate_name: :member,
|
|
338
|
+
namespace: TestSkosNs
|
|
339
|
+
end
|
|
340
|
+
end)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
it "includes linking term in @context with @type @id" do
|
|
344
|
+
parent = JsonLdParentModel.new(
|
|
345
|
+
name: "grp1",
|
|
346
|
+
children: [JsonLdChildModel.new(cid: "a", label: "Alpha")],
|
|
347
|
+
)
|
|
348
|
+
parsed = JSON.parse(parent.to_jsonld)
|
|
349
|
+
expect(parsed["@context"]["member"]).to eq({
|
|
350
|
+
"@id" => "http://www.w3.org/2004/02/skos/core#member",
|
|
351
|
+
"@type" => "@id",
|
|
352
|
+
})
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
it "generates @id references for linked members in @graph" do
|
|
356
|
+
parent = JsonLdParentModel.new(
|
|
357
|
+
name: "grp1",
|
|
358
|
+
children: [
|
|
359
|
+
JsonLdChildModel.new(cid: "a", label: "Alpha"),
|
|
360
|
+
JsonLdChildModel.new(cid: "b", label: "Beta"),
|
|
361
|
+
],
|
|
362
|
+
)
|
|
363
|
+
parsed = JSON.parse(parent.to_jsonld)
|
|
364
|
+
parent_resource = parsed["@graph"].find { |r| r["@type"] }
|
|
365
|
+
expect(parent_resource["member"]).to eq([
|
|
366
|
+
{ "@id" => "http://example.org/item/a" },
|
|
367
|
+
{ "@id" => "http://example.org/item/b" },
|
|
368
|
+
])
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
it "includes member resources in @graph" do
|
|
372
|
+
parent = JsonLdParentModel.new(
|
|
373
|
+
name: "grp1",
|
|
374
|
+
children: [JsonLdChildModel.new(cid: "a", label: "Alpha")],
|
|
375
|
+
)
|
|
376
|
+
parsed = JSON.parse(parent.to_jsonld)
|
|
377
|
+
member = parsed["@graph"].find { |r| r["prefLabel"] == "Alpha" }
|
|
378
|
+
expect(member).not_to be_nil
|
|
379
|
+
expect(member["@id"]).to eq("http://example.org/item/a")
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
it "merges child namespaces into @context" do
|
|
383
|
+
parent = JsonLdParentModel.new(
|
|
384
|
+
name: "grp1",
|
|
385
|
+
children: [JsonLdChildModel.new(cid: "a", label: "Alpha")],
|
|
386
|
+
)
|
|
387
|
+
parsed = JSON.parse(parent.to_jsonld)
|
|
388
|
+
expect(parsed["@context"]["skos"]).to eq("http://www.w3.org/2004/02/skos/core#")
|
|
389
|
+
expect(parsed["@context"]["ex"]).to eq("http://example.org/")
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
it "omits linking key when members have no linking predicate" do
|
|
393
|
+
stub_const("UnlinkedChild", Class.new(Lutaml::Model::Serializable) do
|
|
394
|
+
attribute :label, :string
|
|
395
|
+
|
|
396
|
+
rdf do
|
|
397
|
+
namespace TestSkosNs
|
|
398
|
+
|
|
399
|
+
subject { |m| "http://example.org/#{m.label}" } # rubocop:disable RSpec/NamedSubject, RSpec/MultipleSubjects
|
|
400
|
+
|
|
401
|
+
predicate :prefLabel, namespace: TestSkosNs, to: :label
|
|
402
|
+
end
|
|
403
|
+
end)
|
|
404
|
+
|
|
405
|
+
stub_const("UnlinkedParent", Class.new(Lutaml::Model::Serializable) do
|
|
406
|
+
attribute :name, :string
|
|
407
|
+
attribute :items, UnlinkedChild, collection: true
|
|
408
|
+
|
|
409
|
+
rdf do
|
|
410
|
+
namespace TestSkosNs
|
|
411
|
+
|
|
412
|
+
subject { |m| "http://example.org/#{m.name}" } # rubocop:disable RSpec/NamedSubject, RSpec/MultipleSubjects
|
|
413
|
+
|
|
414
|
+
predicate :prefLabel, namespace: TestSkosNs, to: :name
|
|
415
|
+
|
|
416
|
+
members :items
|
|
417
|
+
end
|
|
418
|
+
end)
|
|
419
|
+
|
|
420
|
+
parent = UnlinkedParent.new(
|
|
421
|
+
name: "grp",
|
|
422
|
+
items: [UnlinkedChild.new(label: "a")],
|
|
423
|
+
)
|
|
424
|
+
parsed = JSON.parse(parent.to_jsonld)
|
|
425
|
+
expect(parsed["@context"]).not_to have_key("member")
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
describe "empty type array" do
|
|
430
|
+
before do
|
|
431
|
+
stub_const("NoTypeJsonLdModel", Class.new(Lutaml::Model::Serializable) do
|
|
432
|
+
attribute :name, :string
|
|
433
|
+
|
|
434
|
+
rdf do
|
|
435
|
+
namespace TestExNs
|
|
436
|
+
|
|
437
|
+
subject { |m| "http://example.org/#{m.name}" } # rubocop:disable RSpec/NamedSubject
|
|
438
|
+
|
|
439
|
+
predicate :name, namespace: TestExNs, to: :name
|
|
440
|
+
end
|
|
441
|
+
end)
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
it "omits @type when no types declared" do
|
|
445
|
+
instance = NoTypeJsonLdModel.new(name: "test")
|
|
446
|
+
parsed = JSON.parse(instance.to_jsonld)
|
|
447
|
+
expect(parsed).not_to have_key("@type")
|
|
448
|
+
end
|
|
449
|
+
end
|
|
211
450
|
end
|