active-triples 0.7.6 → 0.8.0
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 +5 -3
- data/AUTHORS +1 -1
- data/CHANGES.md +12 -17
- data/Gemfile +2 -0
- data/README.md +2 -2
- data/active-triples.gemspec +6 -5
- data/lib/active_triples.rb +56 -5
- data/lib/active_triples/configurable.rb +13 -17
- data/lib/active_triples/list.rb +22 -21
- data/lib/active_triples/persistable.rb +100 -0
- data/lib/active_triples/persistence_strategies/parent_strategy.rb +89 -0
- data/lib/active_triples/persistence_strategies/persistence_strategy.rb +77 -0
- data/lib/active_triples/persistence_strategies/repository_strategy.rb +78 -0
- data/lib/active_triples/properties.rb +2 -1
- data/lib/active_triples/rdf_source.rb +94 -159
- data/lib/active_triples/relation.rb +17 -45
- data/lib/active_triples/repositories.rb +23 -9
- data/lib/active_triples/version.rb +1 -1
- data/spec/active_triples/configurable_spec.rb +5 -9
- data/spec/active_triples/identifiable_spec.rb +1 -18
- data/spec/active_triples/list_spec.rb +0 -4
- data/spec/active_triples/persistable_spec.rb +93 -0
- data/spec/active_triples/persistence_strategies/parent_strategy_spec.rb +93 -0
- data/spec/active_triples/persistence_strategies/persistence_strategy_spec.rb +24 -0
- data/spec/active_triples/persistence_strategies/repository_strategy_spec.rb +120 -0
- data/spec/active_triples/rdf_source_spec.rb +217 -9
- data/spec/active_triples/relation_spec.rb +51 -4
- data/spec/active_triples/repositories_spec.rb +17 -7
- data/spec/active_triples/resource_spec.rb +6 -53
- data/spec/active_triples_spec.rb +50 -1
- data/spec/spec_helper.rb +5 -1
- data/spec/support/active_model_lint.rb +6 -3
- data/spec/support/dummies/basic_persistable.rb +13 -0
- data/spec/support/shared_examples/persistence_strategy.rb +36 -0
- metadata +32 -9
- data/spec/active_triples/validations_spec.rb +0 -33
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ActiveTriples::PersistenceStrategy do
|
4
|
+
let(:klass) { Class.new { include ActiveTriples::PersistenceStrategy } }
|
5
|
+
subject { klass.new }
|
6
|
+
|
7
|
+
describe '#persist!' do
|
8
|
+
it 'raises as not implemented' do
|
9
|
+
expect { subject.persist! }.to raise_error NotImplementedError
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#erase_old_resource' do
|
14
|
+
it 'raises as not implemented' do
|
15
|
+
expect { subject.erase_old_resource }.to raise_error NotImplementedError
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#reload' do
|
20
|
+
it 'raises as not implemented' do
|
21
|
+
expect { subject.reload }.to raise_error NotImplementedError
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ActiveTriples::RepositoryStrategy do
|
4
|
+
subject { described_class.new(rdf_source) }
|
5
|
+
let(:rdf_source) { ActiveTriples::Resource.new }
|
6
|
+
|
7
|
+
let(:statement) do
|
8
|
+
RDF::Statement.new(rdf_source.to_term, RDF::DC.title, 'moomin')
|
9
|
+
end
|
10
|
+
|
11
|
+
it_behaves_like 'a persistence strategy'
|
12
|
+
|
13
|
+
shared_context 'with repository' do
|
14
|
+
before do
|
15
|
+
allow(subject.obj.singleton_class).to receive(:repository)
|
16
|
+
.and_return(:my_repo)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#destroy' do
|
21
|
+
shared_examples 'destroy resource' do
|
22
|
+
it 'removes the resource from the repository' do
|
23
|
+
subject.persist!
|
24
|
+
expect { subject.destroy }
|
25
|
+
.to change { subject.repository.count }.from(1).to(0)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'marks resource as destroyed' do
|
30
|
+
subject.destroy
|
31
|
+
expect(subject).to be_destroyed
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'leaves other resources unchanged' do
|
35
|
+
subject.repository <<
|
36
|
+
RDF::Statement(RDF::Node.new, RDF::DC.title, 'snorkmaiden')
|
37
|
+
expect { subject.destroy }
|
38
|
+
.not_to change { subject.repository.count }
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'with statements' do
|
42
|
+
before { rdf_source << statement }
|
43
|
+
|
44
|
+
include_examples 'destroy resource'
|
45
|
+
|
46
|
+
context 'with subjects' do
|
47
|
+
before do
|
48
|
+
subject.obj.set_subject! RDF::URI('http://example.org/moomin')
|
49
|
+
end
|
50
|
+
|
51
|
+
include_examples 'destroy resource'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#destroyed?' do
|
57
|
+
it 'is false' do
|
58
|
+
expect(subject).not_to be_destroyed
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe '#persist!' do
|
63
|
+
it 'writes to #repository' do
|
64
|
+
rdf_source << statement
|
65
|
+
subject.persist!
|
66
|
+
expect(subject.repository.statements)
|
67
|
+
.to contain_exactly *rdf_source.statements
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe '#erase_old_resource' do
|
72
|
+
it 'removes statements with subject from the repository'
|
73
|
+
it 'removes statements about node from the repository'
|
74
|
+
end
|
75
|
+
|
76
|
+
describe '#reload' do
|
77
|
+
it 'when both repository and object are empty returns true' do
|
78
|
+
expect(subject.reload).to be true
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'with unknown content in repo' do
|
82
|
+
include_context 'with repository' do
|
83
|
+
before do
|
84
|
+
allow(ActiveTriples::Repositories.repositories)
|
85
|
+
.to receive(:[]).with(:my_repo).and_return(repo)
|
86
|
+
repo << statement
|
87
|
+
end
|
88
|
+
|
89
|
+
let(:repo) { RDF::Repository.new }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe '#repository' do
|
95
|
+
it 'gives a repository when none is configured' do
|
96
|
+
expect(subject.repository).to be_a RDF::Repository
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'defaults to an ad-hoc in memory RDF::Repository' do
|
100
|
+
expect(subject.repository).to be_ephemeral
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'with repository configured' do
|
104
|
+
include_context 'with repository'
|
105
|
+
|
106
|
+
let(:repo) { double('repo') }
|
107
|
+
|
108
|
+
it 'when repository is not registered raises an error' do
|
109
|
+
expect { subject.repository }
|
110
|
+
.to raise_error ActiveTriples::RepositoryNotFoundError
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'gets repository' do
|
114
|
+
allow(ActiveTriples::Repositories.repositories)
|
115
|
+
.to receive(:[]).with(:my_repo).and_return(repo)
|
116
|
+
expect(subject.repository).to eq repo
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -1,18 +1,226 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'rdf/spec/enumerable'
|
3
|
+
require 'rdf/spec/queryable'
|
4
|
+
require 'rdf/spec/countable'
|
5
|
+
require 'rdf/spec/mutable'
|
2
6
|
|
3
7
|
describe ActiveTriples::RDFSource do
|
8
|
+
before { @enumerable = subject }
|
4
9
|
let(:source_class) { Class.new { include ActiveTriples::RDFSource } }
|
10
|
+
let(:uri) { RDF::URI('http://example.org/moomin') }
|
5
11
|
|
6
12
|
subject { source_class.new }
|
7
13
|
|
8
|
-
describe
|
9
|
-
it
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
+
describe 'RDF interface' do
|
15
|
+
it { is_expected.to be_enumerable }
|
16
|
+
it { is_expected.to be_queryable }
|
17
|
+
it { is_expected.to be_countable }
|
18
|
+
it { is_expected.to be_a_value }
|
19
|
+
# it { is_expected.to be_a_term }
|
20
|
+
# it { is_expected.to be_a_resource }
|
21
|
+
|
22
|
+
let(:enumerable) { source_class.new }
|
23
|
+
it_behaves_like 'an RDF::Enumerable'
|
24
|
+
|
25
|
+
let(:queryable) { enumerable }
|
26
|
+
it_behaves_like 'an RDF::Queryable'
|
27
|
+
|
28
|
+
let(:countable) { enumerable }
|
29
|
+
it_behaves_like 'an RDF::Countable'
|
30
|
+
|
31
|
+
let(:mutable) { enumerable }
|
32
|
+
it_behaves_like 'an RDF::Mutable'
|
33
|
+
|
34
|
+
describe 'Term behavior' do
|
35
|
+
it { is_expected.to be_term }
|
36
|
+
|
37
|
+
it 'is termified when added to an Statement' do
|
38
|
+
expect(RDF::Statement(subject, nil, nil).subject).to eq subject
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'as a node' do
|
42
|
+
describe '#uri?' do
|
43
|
+
it { is_expected.not_to be_uri }
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#node?' do
|
47
|
+
it { is_expected.to be_node }
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#to_term' do
|
51
|
+
its(:to_term) { is_expected.to be_node }
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#to_base' do
|
55
|
+
its(:to_base) { is_expected.to be_a String }
|
56
|
+
its(:to_base) { is_expected.to eq subject.to_term.to_base }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'as a uri' do
|
61
|
+
subject { source_class.new(uri) }
|
62
|
+
|
63
|
+
describe '#uri?' do
|
64
|
+
it { is_expected.to be_uri }
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '#node?' do
|
68
|
+
it { is_expected.not_to be_node }
|
69
|
+
end
|
70
|
+
|
71
|
+
describe '#to_term' do
|
72
|
+
its(:to_term) { is_expected.to be_uri }
|
73
|
+
end
|
74
|
+
|
75
|
+
describe '#to_uri' do
|
76
|
+
its(:to_uri) { is_expected.to be_uri }
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#to_base' do
|
80
|
+
its(:to_base) { is_expected.to be_a String }
|
81
|
+
its(:to_base) { is_expected.to eq subject.to_term.to_base }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe '#==' do
|
88
|
+
shared_examples 'Term equality' do
|
89
|
+
it 'equals itself' do
|
90
|
+
expect(subject).to eq subject
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'equals its own Term' do
|
94
|
+
expect(subject).to eq subject.to_term
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'is symmetric' do
|
98
|
+
expect(subject.to_term).to eq subject
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'does not equal another term' do
|
102
|
+
expect(subject).not_to eq RDF::Node.new
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
include_examples 'Term equality'
|
107
|
+
|
108
|
+
context 'with a URI' do
|
109
|
+
include_examples 'Term equality' do
|
110
|
+
subject { source_class.new(uri) }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe '#id' do
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '#humanize' do
|
119
|
+
it 'gives the "" for a node' do
|
120
|
+
expect(subject.humanize).to eq ''
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'gives a URI string for a URI resource' do
|
124
|
+
allow(subject).to receive(:rdf_subject).and_return(uri)
|
125
|
+
expect(subject.humanize).to eq uri.to_s
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe '#rdf_subject' do
|
130
|
+
its(:rdf_subject) { is_expected.to be_a_node }
|
131
|
+
|
132
|
+
context 'with a URI' do
|
133
|
+
subject { source_class.new(uri) }
|
134
|
+
|
135
|
+
its(:rdf_subject) { is_expected.to be_a_uri }
|
136
|
+
its(:rdf_subject) { is_expected.to eq uri }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe '#set_value' do
|
141
|
+
it 'raises argument error when given too many arguments' do
|
142
|
+
expect { subject.set_value(double, double, double, double) }
|
143
|
+
.to raise_error ArgumentError
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'sets a value'
|
147
|
+
end
|
148
|
+
|
149
|
+
describe "inheritance" do
|
150
|
+
before do
|
151
|
+
class PrincipalResource
|
152
|
+
include ActiveTriples::RDFSource
|
153
|
+
|
154
|
+
configure type: RDF::FOAF.Agent
|
155
|
+
property :name, predicate: RDF::FOAF.name
|
156
|
+
end
|
157
|
+
|
158
|
+
class UserSource < PrincipalResource
|
159
|
+
configure type: RDF::FOAF.Person
|
160
|
+
end
|
161
|
+
|
162
|
+
class DummySource
|
163
|
+
include ActiveTriples::RDFSource
|
164
|
+
|
165
|
+
property :creator, predicate: RDF::DC.creator
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
after do
|
170
|
+
Object.send(:remove_const, :PrincipalResource)
|
171
|
+
Object.send(:remove_const, :UserSource)
|
172
|
+
Object.send(:remove_const, :DummySource)
|
173
|
+
end
|
174
|
+
|
175
|
+
let(:dummy) { DummySource.new }
|
176
|
+
let(:bob) { UserSource.new.tap {|u| u.name = "bob"} }
|
177
|
+
let(:sally) { UserSource.new.tap {|u| u.name = "sally"} }
|
178
|
+
|
179
|
+
it "should replace values" do
|
180
|
+
dummy.creator = bob
|
181
|
+
expect(dummy.creator).to eq [bob]
|
182
|
+
dummy.creator = sally
|
183
|
+
expect(dummy.creator).to eq [sally]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe 'validation' do
|
188
|
+
it { is_expected.to be_valid }
|
189
|
+
|
190
|
+
it 'is valid with valid statements' do
|
191
|
+
subject.insert(*RDF::Spec.quads)
|
192
|
+
expect(subject).to be_valid
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'is valid with valid URI' do
|
196
|
+
source_class.new(uri)
|
197
|
+
expect(subject).to be_valid
|
198
|
+
end
|
199
|
+
|
200
|
+
context 'with invalid URI' do
|
201
|
+
before do
|
202
|
+
allow(subject).to receive(:rdf_subject).and_return(RDF::URI('----'))
|
203
|
+
end
|
204
|
+
|
205
|
+
it { is_expected.not_to be_valid }
|
206
|
+
end
|
207
|
+
|
208
|
+
context 'with invalid statement' do
|
209
|
+
before { subject << RDF::Statement.from([nil, nil, nil]) }
|
210
|
+
|
211
|
+
it 'is invalid' do
|
212
|
+
expect(subject).to be_invalid
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'adds error message' do
|
216
|
+
expect { subject.valid? }
|
217
|
+
.to change { subject.errors.messages }
|
218
|
+
.from({})
|
219
|
+
.to({ base: ["The underlying graph must be valid"] })
|
220
|
+
end
|
14
221
|
end
|
15
222
|
end
|
223
|
+
let(:dummy_source) { Class.new { include ActiveTriples::RDFSource } }
|
16
224
|
|
17
225
|
describe ".apply_schema" do
|
18
226
|
before do
|
@@ -23,10 +231,10 @@ describe ActiveTriples::RDFSource do
|
|
23
231
|
after do
|
24
232
|
Object.send(:remove_const, "MyDataModel")
|
25
233
|
end
|
26
|
-
it "
|
27
|
-
|
234
|
+
it "should apply the schema" do
|
235
|
+
dummy_source.apply_schema MyDataModel
|
28
236
|
|
29
|
-
expect
|
237
|
+
expect{dummy_source.new.test_title}.not_to raise_error
|
30
238
|
end
|
31
239
|
end
|
32
240
|
end
|
@@ -3,6 +3,10 @@ require 'rdf/isomorphic'
|
|
3
3
|
|
4
4
|
describe ActiveTriples::Relation do
|
5
5
|
|
6
|
+
subject do
|
7
|
+
described_class.new(double("parent", reflections: []), double("value args"))
|
8
|
+
end
|
9
|
+
|
6
10
|
describe "#rdf_subject" do
|
7
11
|
let(:parent_resource) { double("parent resource", reflections: {}) }
|
8
12
|
|
@@ -11,7 +15,7 @@ describe ActiveTriples::Relation do
|
|
11
15
|
context "when relation has 0 value arguments" do
|
12
16
|
before { subject.value_arguments = double(length: 0) }
|
13
17
|
it "should raise an error" do
|
14
|
-
expect { subject.send(:rdf_subject) }.to raise_error
|
18
|
+
expect { subject.send(:rdf_subject) }.to raise_error
|
15
19
|
end
|
16
20
|
end
|
17
21
|
context "when term has 1 value argument" do
|
@@ -35,45 +39,57 @@ describe ActiveTriples::Relation do
|
|
35
39
|
context "when relation has 3 value arguments" do
|
36
40
|
before { subject.value_arguments = double(length: 3) }
|
37
41
|
it "should raise an error" do
|
38
|
-
expect { subject.send(:rdf_subject) }.to raise_error
|
42
|
+
expect { subject.send(:rdf_subject) }.to raise_error
|
39
43
|
end
|
40
44
|
end
|
41
45
|
end
|
42
46
|
|
43
47
|
describe "#valid_datatype?" do
|
44
|
-
|
45
|
-
|
48
|
+
before do
|
49
|
+
allow(subject.parent).to receive(:rdf_subject) { "parent subject" }
|
50
|
+
end
|
51
|
+
|
46
52
|
context "the value is not a Resource" do
|
47
53
|
it "should be true if value is a String" do
|
48
54
|
expect(subject.send(:valid_datatype?, "foo")).to be true
|
49
55
|
end
|
56
|
+
|
50
57
|
it "should be true if value is a Symbol" do
|
51
58
|
expect(subject.send(:valid_datatype?, :foo)).to be true
|
52
59
|
end
|
60
|
+
|
53
61
|
it "should be true if the value is a Numeric" do
|
54
62
|
expect(subject.send(:valid_datatype?, 1)).to be true
|
55
63
|
expect(subject.send(:valid_datatype?, 0.1)).to be true
|
56
64
|
end
|
65
|
+
|
57
66
|
it "should be true if the value is a Date" do
|
58
67
|
expect(subject.send(:valid_datatype?, Date.today)).to be true
|
59
68
|
end
|
69
|
+
|
60
70
|
it "should be true if the value is a Time" do
|
61
71
|
expect(subject.send(:valid_datatype?, Time.now)).to be true
|
62
72
|
end
|
73
|
+
|
63
74
|
it "should be true if the value is a boolean" do
|
64
75
|
expect(subject.send(:valid_datatype?, false)).to be true
|
65
76
|
expect(subject.send(:valid_datatype?, true)).to be true
|
66
77
|
end
|
67
78
|
end
|
79
|
+
|
68
80
|
context "the value is a Resource" do
|
69
81
|
after { Object.send(:remove_const, :DummyResource) }
|
82
|
+
|
70
83
|
let(:resource) { DummyResource.new }
|
84
|
+
|
71
85
|
context "and the resource class does not include RDF::Isomorphic" do
|
72
86
|
before { class DummyResource; include ActiveTriples::RDFSource; end }
|
87
|
+
|
73
88
|
it "should be false" do
|
74
89
|
expect(subject.send(:valid_datatype?, resource)).to be false
|
75
90
|
end
|
76
91
|
end
|
92
|
+
|
77
93
|
context "and the resource class includes RDF:Isomorphic" do
|
78
94
|
before do
|
79
95
|
class DummyResource
|
@@ -81,10 +97,12 @@ describe ActiveTriples::Relation do
|
|
81
97
|
include RDF::Isomorphic
|
82
98
|
end
|
83
99
|
end
|
100
|
+
|
84
101
|
it "should be false" do
|
85
102
|
expect(subject.send(:valid_datatype?, resource)).to be false
|
86
103
|
end
|
87
104
|
end
|
105
|
+
|
88
106
|
context "and the resource class includes RDF::Isomorphic and aliases :== to :isomorphic_with?" do
|
89
107
|
before do
|
90
108
|
class DummyResource
|
@@ -93,6 +111,7 @@ describe ActiveTriples::Relation do
|
|
93
111
|
alias_method :==, :isomorphic_with?
|
94
112
|
end
|
95
113
|
end
|
114
|
+
|
96
115
|
it "should be false" do
|
97
116
|
expect(subject.send(:valid_datatype?, resource)).to be false
|
98
117
|
end
|
@@ -100,4 +119,32 @@ describe ActiveTriples::Relation do
|
|
100
119
|
end
|
101
120
|
end
|
102
121
|
|
122
|
+
describe '#empty_property' do
|
123
|
+
|
124
|
+
before { resource << RDF::Statement(resource, property, 'value') }
|
125
|
+
|
126
|
+
subject { described_class.new(resource, value_args) }
|
127
|
+
let(:resource) { ActiveTriples::Resource.new }
|
128
|
+
let(:property) { RDF::URI.new('http://example.com/moomin') }
|
129
|
+
|
130
|
+
let(:value_args) do
|
131
|
+
double('value args',
|
132
|
+
length: 1,
|
133
|
+
first: 'first',
|
134
|
+
last: property)
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'deletes values from property' do
|
138
|
+
expect { subject.empty_property }.to change { subject.result }
|
139
|
+
.from(['value']).to([])
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'deletes multiple values from property' do
|
143
|
+
values = [Date.today, 'value2', RDF::Node.new, true]
|
144
|
+
resource.set_value(property, values)
|
145
|
+
|
146
|
+
expect { subject.empty_property }.to change { subject.result }
|
147
|
+
.from(values).to([])
|
148
|
+
end
|
149
|
+
end
|
103
150
|
end
|