active-triples 0.8.1 → 0.8.2
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/.travis.yml +8 -9
- data/CHANGES.md +69 -0
- data/Gemfile +0 -2
- data/Guardfile +1 -2
- data/active-triples.gemspec +3 -3
- data/lib/active/triples.rb +1 -0
- data/lib/active_triples.rb +4 -0
- data/lib/active_triples/configurable.rb +3 -1
- data/lib/active_triples/configuration.rb +1 -0
- data/lib/active_triples/configuration/item.rb +1 -0
- data/lib/active_triples/configuration/item_factory.rb +1 -0
- data/lib/active_triples/configuration/merge_item.rb +5 -2
- data/lib/active_triples/extension_strategy.rb +1 -0
- data/lib/active_triples/identifiable.rb +1 -0
- data/lib/active_triples/list.rb +2 -0
- data/lib/active_triples/nested_attributes.rb +1 -1
- data/lib/active_triples/node_config.rb +5 -3
- data/lib/active_triples/persistable.rb +1 -0
- data/lib/active_triples/persistence_strategies/parent_strategy.rb +104 -29
- data/lib/active_triples/persistence_strategies/persistence_strategy.rb +15 -7
- data/lib/active_triples/persistence_strategies/repository_strategy.rb +26 -22
- data/lib/active_triples/properties.rb +84 -6
- data/lib/active_triples/property.rb +35 -4
- data/lib/active_triples/property_builder.rb +38 -4
- data/lib/active_triples/rdf_source.rb +225 -75
- data/lib/active_triples/reflection.rb +42 -3
- data/lib/active_triples/relation.rb +330 -73
- data/lib/active_triples/repositories.rb +4 -2
- data/lib/active_triples/resource.rb +1 -0
- data/lib/active_triples/schema.rb +1 -0
- data/lib/active_triples/undefined_property_error.rb +27 -0
- data/lib/active_triples/version.rb +2 -1
- data/spec/active_triples/configurable_spec.rb +3 -2
- data/spec/active_triples/configuration_spec.rb +2 -1
- data/spec/active_triples/extension_strategy_spec.rb +2 -1
- data/spec/active_triples/identifiable_spec.rb +7 -11
- data/spec/active_triples/list_spec.rb +1 -4
- data/spec/active_triples/nested_attributes_spec.rb +4 -3
- data/spec/active_triples/persistable_spec.rb +4 -1
- data/spec/active_triples/persistence_strategies/parent_strategy_spec.rb +141 -11
- data/spec/active_triples/persistence_strategies/persistence_strategy_spec.rb +1 -0
- data/spec/active_triples/persistence_strategies/repository_strategy_spec.rb +32 -17
- data/spec/active_triples/properties_spec.rb +68 -33
- data/spec/active_triples/property_builder_spec.rb +36 -0
- data/spec/active_triples/property_spec.rb +15 -1
- data/spec/active_triples/rdf_source_spec.rb +544 -6
- data/spec/active_triples/reflection_spec.rb +78 -0
- data/spec/active_triples/relation_spec.rb +505 -3
- data/spec/active_triples/repositories_spec.rb +3 -1
- data/spec/active_triples/resource_spec.rb +90 -147
- data/spec/active_triples/schema_spec.rb +3 -2
- data/spec/active_triples_spec.rb +1 -0
- data/spec/integration/dummies/dummy_resource_a.rb +6 -0
- data/spec/integration/dummies/dummy_resource_b.rb +6 -0
- data/spec/integration/parent_persistence_spec.rb +18 -0
- data/spec/integration/reciprocal_properties_spec.rb +69 -0
- data/spec/pragmatic_context_spec.rb +10 -8
- data/spec/spec_helper.rb +5 -0
- data/spec/support/active_model_lint.rb +4 -6
- data/spec/support/dummies/basic_persistable.rb +2 -11
- data/spec/support/matchers.rb +11 -0
- data/spec/support/shared_examples/persistence_strategy.rb +3 -16
- metadata +20 -13
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "spec_helper"
|
2
3
|
describe ActiveTriples::Properties do
|
3
4
|
before do
|
@@ -12,46 +13,61 @@ describe ActiveTriples::Properties do
|
|
12
13
|
end
|
13
14
|
|
14
15
|
describe '#property' do
|
15
|
-
it '
|
16
|
-
DummyProperties.property :title, :predicate => RDF::DC.title
|
16
|
+
it 'sets a property as a NodeConfig' do
|
17
|
+
DummyProperties.property :title, :predicate => RDF::Vocab::DC.title
|
17
18
|
expect(DummyProperties.reflect_on_property(:title)).to be_kind_of ActiveTriples::NodeConfig
|
18
19
|
end
|
19
20
|
|
20
|
-
it '
|
21
|
-
DummyProperties.property :title, :predicate => RDF::DC.title
|
22
|
-
expect(DummyProperties.reflect_on_property(:title).predicate).to eql RDF::DC.title
|
21
|
+
it 'sets the correct property' do
|
22
|
+
DummyProperties.property :title, :predicate => RDF::Vocab::DC.title
|
23
|
+
expect(DummyProperties.reflect_on_property(:title).predicate).to eql RDF::Vocab::DC.title
|
23
24
|
end
|
24
25
|
|
25
|
-
it '
|
26
|
-
DummyProperties.property :title, :predicate => RDF::DC.title.to_s
|
27
|
-
expect(DummyProperties.reflect_on_property(:title).predicate).to eql RDF::DC.title
|
26
|
+
it 'sets the correct property from string' do
|
27
|
+
DummyProperties.property :title, :predicate => RDF::Vocab::DC.title.to_s
|
28
|
+
expect(DummyProperties.reflect_on_property(:title).predicate).to eql RDF::Vocab::DC.title
|
28
29
|
end
|
29
30
|
|
30
|
-
it '
|
31
|
-
DummyProperties.property :title, :predicate => RDF::DC.title do |index|
|
31
|
+
it 'sets index behaviors' do
|
32
|
+
DummyProperties.property :title, :predicate => RDF::Vocab::DC.title do |index|
|
32
33
|
index.as :facetable, :searchable
|
33
34
|
end
|
34
|
-
expect(DummyProperties.reflect_on_property(:title)[:behaviors])
|
35
|
+
expect(DummyProperties.reflect_on_property(:title)[:behaviors])
|
36
|
+
.to eq [:facetable, :searchable]
|
35
37
|
end
|
36
38
|
|
37
|
-
it '
|
38
|
-
DummyProperties.property :title, :predicate => RDF::DC.title, :class_name => RDF::Literal
|
39
|
+
it 'sets class name' do
|
40
|
+
DummyProperties.property :title, :predicate => RDF::Vocab::DC.title, :class_name => RDF::Literal
|
39
41
|
expect(DummyProperties.reflect_on_property(:title)[:class_name]).to eq RDF::Literal
|
40
42
|
end
|
41
43
|
|
42
|
-
it
|
43
|
-
DummyProperties
|
44
|
+
it 'sets persistence strategy' do
|
45
|
+
DummyProperties
|
46
|
+
.property :title, :predicate => RDF::Vocab::DC.title, :persist_to => :moomin
|
47
|
+
expect(DummyProperties.reflect_on_property(:title)[:persist_to]).to eq :moomin
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'sets arbitrary properties' do
|
51
|
+
DummyProperties.property :title, :predicate => RDF::Vocab::DC.title, :moomin => :moomin
|
52
|
+
expect(DummyProperties.reflect_on_property(:title)[:moomin]).to eq :moomin
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'constantizes string class names' do
|
56
|
+
DummyProperties.property :title, :predicate => RDF::Vocab::DC.title, :class_name => "RDF::Literal"
|
44
57
|
expect(DummyProperties.reflect_on_property(:title)[:class_name]).to eq RDF::Literal
|
45
58
|
end
|
46
59
|
|
47
|
-
it "
|
48
|
-
DummyProperties.property :title,
|
60
|
+
it "keeps strings which it can't constantize as strings" do
|
61
|
+
DummyProperties.property :title,
|
62
|
+
:predicate => RDF::Vocab::DC.title,
|
63
|
+
:class_name => "FakeClassName"
|
49
64
|
expect(DummyProperties.reflect_on_property(:title)[:class_name]).to eq "FakeClassName"
|
50
65
|
end
|
51
66
|
|
52
67
|
it 'raises error when defining properties that are already methods' do
|
53
68
|
DummyProperties.send :define_method, :type, lambda { }
|
54
|
-
expect { DummyProperties.property :type, :
|
69
|
+
expect { DummyProperties.property :type, predicate: RDF::Vocab::DC.type }
|
70
|
+
.to raise_error ArgumentError
|
55
71
|
end
|
56
72
|
|
57
73
|
it 'raises error when defining properties with no predicate' do
|
@@ -64,50 +80,54 @@ describe ActiveTriples::Properties do
|
|
64
80
|
|
65
81
|
it 'raises error when defining properties already have method setters' do
|
66
82
|
DummyProperties.send :define_method, :type=, lambda { }
|
67
|
-
expect { DummyProperties.property :type, :predicate => RDF::DC.type }.to raise_error ArgumentError
|
83
|
+
expect { DummyProperties.property :type, :predicate => RDF::Vocab::DC.type }.to raise_error ArgumentError
|
68
84
|
end
|
69
85
|
|
70
86
|
it 'allows resetting of properties' do
|
71
|
-
DummyProperties.property :title, :
|
72
|
-
DummyProperties.property :title, :
|
73
|
-
expect(DummyProperties.reflect_on_property(:title).predicate)
|
87
|
+
DummyProperties.property :title, predicate: RDF::Vocab::DC.alternative
|
88
|
+
DummyProperties.property :title, predicate: RDF::Vocab::DC.title
|
89
|
+
expect(DummyProperties.reflect_on_property(:title).predicate)
|
90
|
+
.to eq RDF::Vocab::DC.title
|
74
91
|
end
|
75
92
|
end
|
76
93
|
|
77
94
|
describe '#config_for_term_or_uri' do
|
78
95
|
before do
|
79
|
-
DummyProperties.property :title, :predicate => RDF::DC.title
|
96
|
+
DummyProperties.property :title, :predicate => RDF::Vocab::DC.title
|
80
97
|
end
|
81
98
|
|
82
99
|
it 'finds property configuration by term symbol' do
|
83
|
-
expect(DummyProperties.config_for_term_or_uri(:title))
|
100
|
+
expect(DummyProperties.config_for_term_or_uri(:title))
|
101
|
+
.to eq DummyProperties.properties['title']
|
84
102
|
end
|
85
103
|
|
86
104
|
it 'finds property configuration by term string' do
|
87
|
-
expect(DummyProperties.config_for_term_or_uri('title'))
|
105
|
+
expect(DummyProperties.config_for_term_or_uri('title'))
|
106
|
+
.to eq DummyProperties.properties['title']
|
88
107
|
end
|
89
108
|
|
90
109
|
it 'finds property configuration by term URI' do
|
91
|
-
expect(DummyProperties.config_for_term_or_uri(RDF::DC.title))
|
110
|
+
expect(DummyProperties.config_for_term_or_uri(RDF::Vocab::DC.title))
|
111
|
+
.to eq DummyProperties.properties['title']
|
92
112
|
end
|
93
113
|
end
|
94
114
|
|
95
115
|
describe '#fields' do
|
96
116
|
before do
|
97
|
-
DummyProperties.property :title, :predicate => RDF::DC.title
|
98
|
-
DummyProperties.property :name, :predicate => RDF::FOAF.name
|
117
|
+
DummyProperties.property :title, :predicate => RDF::Vocab::DC.title
|
118
|
+
DummyProperties.property :name, :predicate => RDF::Vocab::FOAF.name
|
99
119
|
end
|
100
120
|
|
101
121
|
it 'lists its terms' do
|
102
|
-
expect(DummyProperties.fields).to
|
122
|
+
expect(DummyProperties.fields).to contain_exactly(:title, :name)
|
103
123
|
end
|
104
124
|
end
|
105
125
|
|
106
126
|
context "when using a subclass" do
|
107
127
|
before do
|
108
|
-
DummyProperties.property :title, :predicate => RDF::DC.title
|
128
|
+
DummyProperties.property :title, :predicate => RDF::Vocab::DC.title
|
109
129
|
class DummySubClass < DummyProperties
|
110
|
-
property :source, :predicate => RDF::DC11[:source]
|
130
|
+
property :source, :predicate => RDF::Vocab::DC11[:source]
|
111
131
|
end
|
112
132
|
end
|
113
133
|
|
@@ -116,8 +136,23 @@ describe ActiveTriples::Properties do
|
|
116
136
|
end
|
117
137
|
|
118
138
|
it 'should carry properties from superclass' do
|
119
|
-
expect(DummySubClass.reflect_on_property(:title))
|
120
|
-
|
139
|
+
expect(DummySubClass.reflect_on_property(:title))
|
140
|
+
.to be_kind_of ActiveTriples::NodeConfig
|
141
|
+
expect(DummySubClass.reflect_on_property(:source))
|
142
|
+
.to be_kind_of ActiveTriples::NodeConfig
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe '#generated_property_methods' do
|
147
|
+
it 'returns a GeneratedPropertyMethods module' do
|
148
|
+
expect(DummyProperties.generated_property_methods)
|
149
|
+
.to eq DummyProperties::GeneratedPropertyMethods
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'has setter and getter instance methods for set properties' do
|
153
|
+
DummyProperties.property :title, :predicate => RDF::Vocab::DC.title
|
154
|
+
expect(DummyProperties.generated_property_methods.instance_methods)
|
155
|
+
.to contain_exactly(:title, :title=)
|
121
156
|
end
|
122
157
|
end
|
123
158
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe ActiveTriples::PropertyBuilder do
|
5
|
+
subject { described_class.new(name, options) }
|
6
|
+
let(:name) { :moomin }
|
7
|
+
let(:predicate) { :predicate_uri }
|
8
|
+
let(:options) { { predicate: predicate } }
|
9
|
+
|
10
|
+
it { is_expected.to have_attributes(name: name) }
|
11
|
+
it { is_expected.to have_attributes(options: options) }
|
12
|
+
|
13
|
+
describe '#build' do
|
14
|
+
it 'gives a config for name' do
|
15
|
+
expect(subject.build)
|
16
|
+
.to have_attributes(term: name, predicate: predicate)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'yields an IndexObject' do
|
20
|
+
expect { |b| subject.build(&b) }.to yield_control
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '.create_builder' do
|
25
|
+
it 'raises when property name is not a symbol' do
|
26
|
+
expect { described_class.create_builder('name', options) }
|
27
|
+
.to raise_error ArgumentError
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'raises when predicate is invalid' do
|
31
|
+
options[:predicate] = Time.now
|
32
|
+
expect { described_class.create_builder(name, options) }
|
33
|
+
.to raise_error ArgumentError
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'spec_helper'
|
2
3
|
|
3
|
-
|
4
|
+
describe ActiveTriples::Property do
|
4
5
|
subject { described_class.new(options) }
|
5
6
|
let(:options) do
|
6
7
|
{
|
@@ -26,4 +27,17 @@ RSpec.describe ActiveTriples::Property do
|
|
26
27
|
)
|
27
28
|
end
|
28
29
|
end
|
30
|
+
|
31
|
+
it 'requires a :name' do
|
32
|
+
expect { described_class.new({}) }.to raise_error(KeyError)
|
33
|
+
end
|
34
|
+
|
35
|
+
context '#cast' do
|
36
|
+
it 'has a default of false' do
|
37
|
+
expect(described_class.new(:name => :title).cast).to eq(false)
|
38
|
+
end
|
39
|
+
it 'allows for the default to be overridden' do
|
40
|
+
expect(described_class.new(:name => :title, :cast => true).cast).to eq(true)
|
41
|
+
end
|
42
|
+
end
|
29
43
|
end
|
@@ -1,16 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'spec_helper'
|
3
|
+
require 'rdf/turtle'
|
2
4
|
require 'rdf/spec/enumerable'
|
3
5
|
require 'rdf/spec/queryable'
|
4
6
|
require 'rdf/spec/countable'
|
5
7
|
require 'rdf/spec/mutable'
|
6
8
|
|
7
9
|
describe ActiveTriples::RDFSource do
|
10
|
+
it_behaves_like 'an ActiveModel' do
|
11
|
+
let(:am_lint_class) do
|
12
|
+
class AMLintClass
|
13
|
+
include ActiveTriples::RDFSource
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
after { Object.send(:remove_const, :AMLintClass) if defined?(AMLintClass) }
|
18
|
+
end
|
19
|
+
|
8
20
|
before { @enumerable = subject }
|
9
21
|
let(:source_class) { Class.new { include ActiveTriples::RDFSource } }
|
10
22
|
let(:uri) { RDF::URI('http://example.org/moomin') }
|
11
23
|
|
12
24
|
subject { source_class.new }
|
13
25
|
|
26
|
+
shared_context 'with properties' do
|
27
|
+
let(:source_class) do
|
28
|
+
class SourceWithCreator
|
29
|
+
include ActiveTriples::RDFSource
|
30
|
+
property :creator, predicate: RDF::Vocab::DC.creator
|
31
|
+
end
|
32
|
+
SourceWithCreator
|
33
|
+
end
|
34
|
+
|
35
|
+
let(:predicate) { RDF::Vocab::DC.creator }
|
36
|
+
let(:property_name) { :creator }
|
37
|
+
end
|
38
|
+
|
14
39
|
describe 'RDF interface' do
|
15
40
|
it { is_expected.to be_enumerable }
|
16
41
|
it { is_expected.to be_queryable }
|
@@ -23,7 +48,7 @@ describe ActiveTriples::RDFSource do
|
|
23
48
|
it_behaves_like 'an RDF::Enumerable'
|
24
49
|
|
25
50
|
let(:queryable) { enumerable }
|
26
|
-
it_behaves_like 'an RDF::Queryable'
|
51
|
+
# it_behaves_like 'an RDF::Queryable'
|
27
52
|
|
28
53
|
let(:countable) { enumerable }
|
29
54
|
it_behaves_like 'an RDF::Countable'
|
@@ -112,7 +137,107 @@ describe ActiveTriples::RDFSource do
|
|
112
137
|
end
|
113
138
|
end
|
114
139
|
|
115
|
-
describe '#
|
140
|
+
describe '#attributes' do
|
141
|
+
include_context 'with properties'
|
142
|
+
|
143
|
+
it 'has bnode id' do
|
144
|
+
expect(subject.attributes['id']).to eq subject.rdf_subject.id
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'has empty properties' do
|
148
|
+
expect(subject.attributes['creator']).to be_empty
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'has registered properties' do
|
152
|
+
subject.creator = 'moomin'
|
153
|
+
expect(subject.attributes['creator']).to contain_exactly 'moomin'
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'has unregistered properties' do
|
157
|
+
predicate = RDF::OWL.sameAs
|
158
|
+
subject << [subject, predicate, 'moomin']
|
159
|
+
expect(subject.attributes[predicate.to_s]).to contain_exactly 'moomin'
|
160
|
+
end
|
161
|
+
|
162
|
+
context 'with uri' do
|
163
|
+
subject { source_class.new(uri) }
|
164
|
+
|
165
|
+
it 'has uri id' do
|
166
|
+
expect(subject.attributes['id']).to eq subject.rdf_subject.to_s
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'has creator' do
|
170
|
+
subject.creator = 'moomin'
|
171
|
+
expect(subject.attributes['creator']).to contain_exactly 'moomin'
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
describe '#fetch' do
|
177
|
+
it 'raises an error when it is a node' do
|
178
|
+
expect { subject.fetch }
|
179
|
+
.to raise_error "#{subject} is a blank node; Cannot fetch a resource " \
|
180
|
+
'without a URI'
|
181
|
+
end
|
182
|
+
|
183
|
+
context 'with a valid URI' do
|
184
|
+
subject { source_class.new(uri) }
|
185
|
+
|
186
|
+
context 'with a bad link' do
|
187
|
+
before { stub_request(:get, uri).to_return(:status => 404) }
|
188
|
+
|
189
|
+
it 'raises an error if no block is given' do
|
190
|
+
expect { subject.fetch }.to raise_error IOError
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'yields self to block' do
|
194
|
+
expect { |block| subject.fetch(&block) }.to yield_with_args(subject)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context 'with a working link' do
|
199
|
+
before do
|
200
|
+
stub_request(:get, uri).to_return(:status => 200,
|
201
|
+
:body => graph.dump(:ttl))
|
202
|
+
end
|
203
|
+
|
204
|
+
let(:graph) { RDF::Graph.new << statement }
|
205
|
+
|
206
|
+
let(:statement) do
|
207
|
+
RDF::Statement(subject, RDF::Vocab::DC.title, 'moomin')
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'loads retrieved graph into its own' do
|
211
|
+
expect { subject.fetch }
|
212
|
+
.to change { subject.statements.to_a }
|
213
|
+
.from(a_collection_containing_exactly())
|
214
|
+
.to(a_collection_containing_exactly(statement))
|
215
|
+
end
|
216
|
+
|
217
|
+
it 'merges retrieved graph into its own' do
|
218
|
+
existing = RDF::Statement(subject, RDF::Vocab::DC.creator, 'Tove')
|
219
|
+
subject << existing
|
220
|
+
|
221
|
+
expect { subject.fetch }
|
222
|
+
.to change { subject.statements.to_a }
|
223
|
+
.from(a_collection_containing_exactly(existing))
|
224
|
+
.to(a_collection_containing_exactly(statement, existing))
|
225
|
+
end
|
226
|
+
|
227
|
+
it "passes extra arguments to RDF::Reader" do
|
228
|
+
expect(RDF::Reader).to receive(:open).with(subject.rdf_subject,
|
229
|
+
{ base_uri: subject.rdf_subject,
|
230
|
+
headers: { Accept: 'x-humans/as-they-are' } })
|
231
|
+
subject.fetch(headers: { Accept: 'x-humans/as-they-are' })
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
describe '#graph_name' do
|
238
|
+
it 'returns nil' do
|
239
|
+
expect(subject.graph_name).to be_nil
|
240
|
+
end
|
116
241
|
end
|
117
242
|
|
118
243
|
describe '#humanize' do
|
@@ -137,16 +262,281 @@ describe ActiveTriples::RDFSource do
|
|
137
262
|
end
|
138
263
|
end
|
139
264
|
|
265
|
+
describe '#rdf_label' do
|
266
|
+
let(:label_prop) { RDF::Vocab::SKOS.prefLabel }
|
267
|
+
|
268
|
+
it 'returns an array of label values' do
|
269
|
+
expect(subject.rdf_label).to be_kind_of Array
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'returns the default label values' do
|
273
|
+
subject << [subject.rdf_subject, label_prop, 'Comet in Moominland']
|
274
|
+
expect(subject.rdf_label).to contain_exactly('Comet in Moominland')
|
275
|
+
end
|
276
|
+
|
277
|
+
it 'prioritizes configured label values' do
|
278
|
+
custom_label = RDF::URI('http://example.org/custom_label')
|
279
|
+
subject.class.configure rdf_label: custom_label
|
280
|
+
subject << [subject.rdf_subject, custom_label, RDF::Literal('New Label')]
|
281
|
+
subject << [subject.rdf_subject, label_prop, 'Comet in Moominland']
|
282
|
+
|
283
|
+
expect(subject.rdf_label).to contain_exactly('New Label')
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
describe '#get_values' do
|
288
|
+
include_context 'with properties'
|
289
|
+
|
290
|
+
before { statements.each { |statement| subject << statement } }
|
291
|
+
|
292
|
+
let(:values) { ['Tove Jansson', subject] }
|
293
|
+
|
294
|
+
let(:statements) do
|
295
|
+
values.map { |value| RDF::Statement(subject, predicate, value) }
|
296
|
+
end
|
297
|
+
|
298
|
+
context 'with no matching property' do
|
299
|
+
it 'is empty' do
|
300
|
+
expect(subject.get_values(:not_a_predicate))
|
301
|
+
.to be_a_relation_containing()
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
context 'with an empty predicate' do
|
306
|
+
it 'is empty' do
|
307
|
+
expect(subject.get_values(RDF::URI('http://example.org/empty')))
|
308
|
+
.to be_a_relation_containing()
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
it 'gets values for a property name' do
|
313
|
+
expect(subject.get_values(property_name))
|
314
|
+
.to be_a_relation_containing(*values)
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'gets values for a predicate' do
|
318
|
+
expect(subject.get_values(predicate))
|
319
|
+
.to be_a_relation_containing(*values)
|
320
|
+
end
|
321
|
+
|
322
|
+
it 'gets values with two args' do
|
323
|
+
val = 'momma'
|
324
|
+
other_uri = uri / val
|
325
|
+
subject << RDF::Statement(other_uri, predicate, val)
|
326
|
+
|
327
|
+
expect(subject.get_values(other_uri, predicate))
|
328
|
+
.to be_a_relation_containing(val)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
140
332
|
describe '#set_value' do
|
141
333
|
it 'raises argument error when given too many arguments' do
|
142
334
|
expect { subject.set_value(double, double, double, double) }
|
143
335
|
.to raise_error ArgumentError
|
144
336
|
end
|
145
337
|
|
146
|
-
|
338
|
+
context 'when given an unregistered property name' do
|
339
|
+
it 'raises an error' do
|
340
|
+
expect { subject.set_value(:not_a_property, '') }.to raise_error do |err|
|
341
|
+
expect(err).to be_a ActiveTriples::UndefinedPropertyError
|
342
|
+
expect(err.klass).to eq subject.class
|
343
|
+
expect(err.property).to eq :not_a_property
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
it 'is a no-op' do
|
348
|
+
subject << RDF::Statement(subject, RDF::Vocab::DC.title, 'Moomin')
|
349
|
+
expect { subject.set_value(:not_a_property, '') rescue nil }
|
350
|
+
.not_to change { subject.triples.to_a }
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
shared_examples 'setting values' do
|
355
|
+
include_context 'with properties'
|
356
|
+
after do
|
357
|
+
Object.send(:remove_const, 'SourceWithCreator') if
|
358
|
+
defined? SourceWithCreator
|
359
|
+
end
|
360
|
+
|
361
|
+
let(:statements) do
|
362
|
+
Array.wrap(value).map { |val| RDF::Statement(subject, predicate, val) }
|
363
|
+
end
|
364
|
+
|
365
|
+
it 'raises a ValueError when setting a nonsense value' do
|
366
|
+
expect { subject.set_value(predicate, Object.new) }
|
367
|
+
.to raise_error ActiveTriples::Relation::ValueError
|
368
|
+
end
|
369
|
+
|
370
|
+
it 'sets a value' do
|
371
|
+
expect { subject.set_value(predicate, value) }
|
372
|
+
.to change { subject.statements }
|
373
|
+
.to(a_collection_containing_exactly(*statements))
|
374
|
+
end
|
375
|
+
|
376
|
+
it 'sets a value with a property name' do
|
377
|
+
expect { subject.set_value(property_name, value) }
|
378
|
+
.to change { subject.statements }
|
379
|
+
.to(a_collection_containing_exactly(*statements))
|
380
|
+
end
|
381
|
+
|
382
|
+
it 'overwrites existing values' do
|
383
|
+
old_vals = ['old value',
|
384
|
+
RDF::Node.new,
|
385
|
+
RDF::Vocab::DC.type,
|
386
|
+
RDF::URI('----')]
|
387
|
+
|
388
|
+
subject.set_value(predicate, old_vals)
|
389
|
+
|
390
|
+
expect { subject.set_value(predicate, value) }
|
391
|
+
.to change { subject.statements }
|
392
|
+
.to(a_collection_containing_exactly(*statements))
|
393
|
+
end
|
394
|
+
|
395
|
+
it 'returns the set values in a Relation' do
|
396
|
+
expect(subject.set_value(predicate, value))
|
397
|
+
.to be_a_relation_containing(*Array.wrap(value))
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
context 'with string literal' do
|
402
|
+
include_examples 'setting values' do
|
403
|
+
let(:value) { 'moomin' }
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
context 'with multiple values' do
|
408
|
+
include_examples 'setting values' do
|
409
|
+
let(:value) { ['moominpapa', 'moominmama'] }
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
context 'with typed literal' do
|
414
|
+
include_examples 'setting values' do
|
415
|
+
let(:value) { Date.today }
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
context 'with RDF Term' do
|
420
|
+
include_examples 'setting values' do
|
421
|
+
let(:value) { RDF::Node.new }
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
context 'with RDFSource node' do
|
426
|
+
include_examples 'setting values' do
|
427
|
+
let(:value) { source_class.new }
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
context 'with RDFSource uri' do
|
432
|
+
include_examples 'setting values' do
|
433
|
+
let(:value) { source_class.new(uri) }
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
context 'with self' do
|
438
|
+
include_examples 'setting values' do
|
439
|
+
let(:value) { subject }
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
context 'with mixed values' do
|
444
|
+
include_examples 'setting values' do
|
445
|
+
let(:value) do
|
446
|
+
['moomin',
|
447
|
+
Date.today,
|
448
|
+
RDF::Node.new,
|
449
|
+
source_class.new,
|
450
|
+
source_class.new(uri),
|
451
|
+
subject]
|
452
|
+
end
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
context 'with reciprocal relations' do
|
457
|
+
let(:document) { source_class.new }
|
458
|
+
let(:person) { source_class.new }
|
459
|
+
|
460
|
+
it 'handles setting reciprocally' do
|
461
|
+
document.set_value(RDF::Vocab::DC.creator, person)
|
462
|
+
person.set_value(RDF::Vocab::FOAF.publications, document)
|
463
|
+
|
464
|
+
expect(person.get_values(RDF::Vocab::FOAF.publications))
|
465
|
+
.to contain_exactly(document)
|
466
|
+
expect(document.get_values(RDF::Vocab::DC.creator))
|
467
|
+
.to contain_exactly(person)
|
468
|
+
end
|
469
|
+
|
470
|
+
it 'handles setting' do
|
471
|
+
document.set_value(RDF::Vocab::DC.creator, person)
|
472
|
+
person.set_value(RDF::Vocab::FOAF.knows, subject)
|
473
|
+
subject.set_value(RDF::Vocab::FOAF.publications, document)
|
474
|
+
subject.set_value(RDF::OWL.sameAs, subject)
|
475
|
+
|
476
|
+
expect(subject.get_values(RDF::Vocab::FOAF.publications))
|
477
|
+
.to contain_exactly(document)
|
478
|
+
expect(subject.get_values(RDF::OWL.sameAs))
|
479
|
+
.to contain_exactly(subject)
|
480
|
+
expect(document.get_values(RDF::Vocab::DC.creator))
|
481
|
+
.to contain_exactly(person)
|
482
|
+
end
|
483
|
+
|
484
|
+
it 'handles setting circularly' do
|
485
|
+
document.set_value(RDF::Vocab::DC.creator, [person, subject])
|
486
|
+
person.set_value(RDF::Vocab::FOAF.knows, subject)
|
487
|
+
|
488
|
+
expect(document.get_values(RDF::Vocab::DC.creator))
|
489
|
+
.to contain_exactly(person, subject)
|
490
|
+
expect(person.get_values(RDF::Vocab::FOAF.knows))
|
491
|
+
.to contain_exactly subject
|
492
|
+
end
|
493
|
+
|
494
|
+
it 'handles setting circularly within ancestor list' do
|
495
|
+
person2 = source_class.new
|
496
|
+
subject.set_value(RDF::Vocab::DC.relation, document)
|
497
|
+
document.set_value(RDF::Vocab::DC.relation, person)
|
498
|
+
person.set_value(RDF::Vocab::DC.relation, person2)
|
499
|
+
person2.set_value(RDF::Vocab::DC.relation, document)
|
500
|
+
|
501
|
+
expect(person.get_values(RDF::Vocab::DC.relation))
|
502
|
+
.to contain_exactly person2
|
503
|
+
expect(person2.get_values(RDF::Vocab::DC.relation))
|
504
|
+
.to contain_exactly document
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
describe 'capturing child nodes' do
|
509
|
+
let(:other) { source_class.new }
|
510
|
+
|
511
|
+
it 'adds child node data to own graph' do
|
512
|
+
other << RDF::Statement(:s, RDF::URI('p'), 'o')
|
513
|
+
expect { subject.set_value(RDF::OWL.sameAs, other) }
|
514
|
+
.to change { subject.statements.to_a }.to include(*other.statements.to_a)
|
515
|
+
end
|
516
|
+
|
517
|
+
it 'does not change persistence strategy of added node' do
|
518
|
+
expect { subject.set_value(RDF::OWL.sameAs, other) }
|
519
|
+
.not_to change { other.persistence_strategy }
|
520
|
+
end
|
521
|
+
|
522
|
+
it 'does not capture a child node when it already persists to a parent' do
|
523
|
+
third = source_class.new
|
524
|
+
third.set_value(RDF::OWL.sameAs, other)
|
525
|
+
|
526
|
+
child_other = third.get_values(RDF::OWL.sameAs).first
|
527
|
+
expect { subject.set_value(RDF::OWL.sameAs, child_other) }
|
528
|
+
.not_to change { child_other.persistence_strategy.parent }
|
529
|
+
end
|
530
|
+
end
|
147
531
|
end
|
148
532
|
|
149
533
|
describe 'validation' do
|
534
|
+
let(:invalid_statement) do
|
535
|
+
RDF::Statement.from([RDF::Literal.new('blah'),
|
536
|
+
RDF::Literal.new('blah'),
|
537
|
+
RDF::Literal.new('blah')])
|
538
|
+
end
|
539
|
+
|
150
540
|
it { is_expected.to be_valid }
|
151
541
|
|
152
542
|
it 'is valid with valid statements' do
|
@@ -168,7 +558,7 @@ describe ActiveTriples::RDFSource do
|
|
168
558
|
end
|
169
559
|
|
170
560
|
context 'with invalid statement' do
|
171
|
-
before { subject <<
|
561
|
+
before { subject << invalid_statement }
|
172
562
|
|
173
563
|
it 'is invalid' do
|
174
564
|
expect(subject).to be_invalid
|
@@ -181,15 +571,63 @@ describe ActiveTriples::RDFSource do
|
|
181
571
|
.to({ base: ["The underlying graph must be valid"] })
|
182
572
|
end
|
183
573
|
end
|
574
|
+
|
575
|
+
context 'with ActiveModel validation' do
|
576
|
+
let(:source_class) do
|
577
|
+
class Validation
|
578
|
+
include ActiveTriples::RDFSource
|
579
|
+
|
580
|
+
validates_presence_of :title
|
581
|
+
|
582
|
+
property :title, predicate: RDF::Vocab::DC.title
|
583
|
+
end
|
584
|
+
|
585
|
+
Validation
|
586
|
+
end
|
587
|
+
|
588
|
+
after { Object.send(:remove_const, :Validation) }
|
589
|
+
|
590
|
+
context 'with invalid property' do
|
591
|
+
it { is_expected.to be_invalid }
|
592
|
+
|
593
|
+
it 'has errors' do
|
594
|
+
expect { subject.valid? }
|
595
|
+
.to change { subject.errors.messages }
|
596
|
+
.from({})
|
597
|
+
.to({ title: ["can't be blank"] })
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
context 'when properties are valid' do
|
602
|
+
before { subject.title = 'moomin' }
|
603
|
+
|
604
|
+
it { is_expected.to be_valid }
|
605
|
+
|
606
|
+
context 'and has invaild statements' do
|
607
|
+
before { subject << invalid_statement }
|
608
|
+
|
609
|
+
it { is_expected.to be_invalid }
|
610
|
+
|
611
|
+
it 'has errors' do
|
612
|
+
expect { subject.valid? }
|
613
|
+
.to change { subject.errors.messages }
|
614
|
+
.from({})
|
615
|
+
.to(include({ base: ["The underlying graph must be valid"] }))
|
616
|
+
end
|
617
|
+
end
|
618
|
+
end
|
619
|
+
end
|
184
620
|
end
|
185
|
-
let(:dummy_source) { Class.new { include ActiveTriples::RDFSource } }
|
186
621
|
|
187
622
|
describe ".apply_schema" do
|
623
|
+
let(:dummy_source) { Class.new { include ActiveTriples::RDFSource } }
|
624
|
+
|
188
625
|
before do
|
189
626
|
class MyDataModel < ActiveTriples::Schema
|
190
|
-
property :test_title, :predicate => RDF::DC.title
|
627
|
+
property :test_title, :predicate => RDF::Vocab::DC.title
|
191
628
|
end
|
192
629
|
end
|
630
|
+
|
193
631
|
after do
|
194
632
|
Object.send(:remove_const, "MyDataModel")
|
195
633
|
end
|
@@ -199,4 +637,104 @@ describe ActiveTriples::RDFSource do
|
|
199
637
|
expect{dummy_source.new.test_title}.not_to raise_error
|
200
638
|
end
|
201
639
|
end
|
640
|
+
|
641
|
+
describe 'sources with properties with class_name defined' do
|
642
|
+
before(:context) do
|
643
|
+
class DummyChapter < ActiveTriples::Resource
|
644
|
+
configure repository: :default, type: RDF::URI('http://www.example.com/type/Chapter')
|
645
|
+
property :title, predicate: RDF::URI('http://www.example.com/ontology/title')
|
646
|
+
property :subtitle, predicate: RDF::URI('http://www.example.com/ontology/subtitle')
|
647
|
+
end
|
648
|
+
|
649
|
+
class DummyBook < ActiveTriples::Resource
|
650
|
+
configure repository: :default, type: RDF::URI('http://www.example.com/type/Book')
|
651
|
+
property :title, predicate: RDF::URI('http://www.example.com/ontology/title')
|
652
|
+
property :has_chapter, predicate: RDF::URI('http://www.example.com/ontology/hasChapter'), class_name: DummyChapter # Explicit Link
|
653
|
+
end
|
654
|
+
end
|
655
|
+
|
656
|
+
context 'when loading models from graph' do
|
657
|
+
before(:context) do
|
658
|
+
r = RDF::Repository.new
|
659
|
+
ActiveTriples::Repositories.repositories[:default] = r
|
660
|
+
|
661
|
+
@book_url = 'http://www.example.com/BOOK_URI'
|
662
|
+
@book_title = 'Example Book.'
|
663
|
+
@chapter_title = 'Chapter 1'
|
664
|
+
ttl = "<#{@book_url}> a <http://www.example.com/type/Book>;
|
665
|
+
<http://www.example.com/ontology/hasChapter> [
|
666
|
+
a <http://www.example.com/type/Chapter>;
|
667
|
+
<http://www.example.com/ontology/title> \"#{@chapter_title}\"
|
668
|
+
];
|
669
|
+
<http://www.example.com/ontology/title> \"#{@book_title}\" ."
|
670
|
+
book_graph = ::RDF::Graph.new.from_ttl ttl
|
671
|
+
r = ActiveTriples::Repositories.repositories[:default]
|
672
|
+
r << book_graph
|
673
|
+
|
674
|
+
@book = DummyBook.new(RDF::URI.new(@book_url))
|
675
|
+
end
|
676
|
+
|
677
|
+
it 'populates DummyBook properly' do
|
678
|
+
expect(@book.rdf_subject.to_s).to eq @book_url
|
679
|
+
expect(@book).to be_a DummyBook
|
680
|
+
expect(@book.type).to include(RDF::URI.new('http://www.example.com/type/Book'))
|
681
|
+
expect(@book.title.first).to eq @book_title
|
682
|
+
end
|
683
|
+
|
684
|
+
it 'populates DummyChapter properly' do
|
685
|
+
chapter = @book.has_chapter.first
|
686
|
+
expect(chapter).to be_a DummyChapter
|
687
|
+
expect(chapter.title.first).to eq @chapter_title
|
688
|
+
expect(chapter.type).to include(RDF::URI.new('http://www.example.com/type/Chapter'))
|
689
|
+
end
|
690
|
+
end
|
691
|
+
|
692
|
+
|
693
|
+
context 'when loading models through properties' do
|
694
|
+
before(:context) do
|
695
|
+
r = RDF::Repository.new
|
696
|
+
ActiveTriples::Repositories.repositories[:default] = r
|
697
|
+
|
698
|
+
bk1 = DummyBook.new('http://www.example.com/book1')
|
699
|
+
bk1.title = 'Learning about Explicit Links in ActiveTriples'
|
700
|
+
|
701
|
+
ch1 = DummyChapter.new('http://www.example.com/book1/chapter1')
|
702
|
+
ch1.title = 'Defining a source with an Explicit Link'
|
703
|
+
bk1.has_chapter = ch1
|
704
|
+
ch1.persist!
|
705
|
+
bk1.persist!
|
706
|
+
|
707
|
+
@bk1 = DummyBook.new('http://www.example.com/book1')
|
708
|
+
@ch1 = DummyChapter.new('http://www.example.com/book1/chapter1')
|
709
|
+
end
|
710
|
+
|
711
|
+
it 'populates DummyBook (resumed resource) properly' do
|
712
|
+
expect(@bk1.type.first).to eq RDF::URI('http://www.example.com/type/Book')
|
713
|
+
expect(@bk1.title.first).to eq 'Learning about Explicit Links in ActiveTriples'
|
714
|
+
end
|
715
|
+
|
716
|
+
|
717
|
+
it 'populates DummyChapter (property resource) properly' do
|
718
|
+
ch1 = @bk1.has_chapter.first
|
719
|
+
expect(ch1.type.first).to eq RDF::URI('http://www.example.com/type/Chapter')
|
720
|
+
expect(ch1.title.first).to eq 'Defining a source with an Explicit Link'
|
721
|
+
end
|
722
|
+
|
723
|
+
it 'populates DummyChapter (directly from repository) properly' do
|
724
|
+
expect(@ch1.type.first).to eq RDF::URI('http://www.example.com/type/Chapter')
|
725
|
+
expect(@ch1.title.first).to eq 'Defining a source with an Explicit Link'
|
726
|
+
end
|
727
|
+
|
728
|
+
it 'does not reload from repository twice' do
|
729
|
+
ch1 = @bk1.has_chapter.first
|
730
|
+
expect(ch1.title.first).to eq 'Defining a source with an Explicit Link'
|
731
|
+
|
732
|
+
@ch1.subtitle = 'Changed after original load'
|
733
|
+
@ch1.persist!
|
734
|
+
expect(@ch1.subtitle).to eq ['Changed after original load']
|
735
|
+
|
736
|
+
expect(ch1.subtitle).to eq []
|
737
|
+
end
|
738
|
+
end
|
739
|
+
end
|
202
740
|
end
|