active-triples 0.8.1 → 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|