active-triples 0.7.6 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -3
  3. data/AUTHORS +1 -1
  4. data/CHANGES.md +12 -17
  5. data/Gemfile +2 -0
  6. data/README.md +2 -2
  7. data/active-triples.gemspec +6 -5
  8. data/lib/active_triples.rb +56 -5
  9. data/lib/active_triples/configurable.rb +13 -17
  10. data/lib/active_triples/list.rb +22 -21
  11. data/lib/active_triples/persistable.rb +100 -0
  12. data/lib/active_triples/persistence_strategies/parent_strategy.rb +89 -0
  13. data/lib/active_triples/persistence_strategies/persistence_strategy.rb +77 -0
  14. data/lib/active_triples/persistence_strategies/repository_strategy.rb +78 -0
  15. data/lib/active_triples/properties.rb +2 -1
  16. data/lib/active_triples/rdf_source.rb +94 -159
  17. data/lib/active_triples/relation.rb +17 -45
  18. data/lib/active_triples/repositories.rb +23 -9
  19. data/lib/active_triples/version.rb +1 -1
  20. data/spec/active_triples/configurable_spec.rb +5 -9
  21. data/spec/active_triples/identifiable_spec.rb +1 -18
  22. data/spec/active_triples/list_spec.rb +0 -4
  23. data/spec/active_triples/persistable_spec.rb +93 -0
  24. data/spec/active_triples/persistence_strategies/parent_strategy_spec.rb +93 -0
  25. data/spec/active_triples/persistence_strategies/persistence_strategy_spec.rb +24 -0
  26. data/spec/active_triples/persistence_strategies/repository_strategy_spec.rb +120 -0
  27. data/spec/active_triples/rdf_source_spec.rb +217 -9
  28. data/spec/active_triples/relation_spec.rb +51 -4
  29. data/spec/active_triples/repositories_spec.rb +17 -7
  30. data/spec/active_triples/resource_spec.rb +6 -53
  31. data/spec/active_triples_spec.rb +50 -1
  32. data/spec/spec_helper.rb +5 -1
  33. data/spec/support/active_model_lint.rb +6 -3
  34. data/spec/support/dummies/basic_persistable.rb +13 -0
  35. data/spec/support/shared_examples/persistence_strategy.rb +36 -0
  36. metadata +32 -9
  37. 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 "#fetch" do
9
- it "passes extra arguments to RDF::Reader" do
10
- expect(RDF::Reader).to receive(:open).with(subject.rdf_subject,
11
- { base_uri: subject.rdf_subject,
12
- headers: { Accept: 'x-humans/as-they-are' } })
13
- subject.fetch(headers: { Accept: 'x-humans/as-they-are' })
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 "applies the schema" do
27
- source_class.apply_schema MyDataModel
234
+ it "should apply the schema" do
235
+ dummy_source.apply_schema MyDataModel
28
236
 
29
- expect { source_class.new.test_title }.not_to raise_error
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 ArgumentError
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 ArgumentError
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
- subject { described_class.new(double("parent", reflections: []), "value" ) }
45
- before { allow(subject.parent).to receive(:rdf_subject) { "parent subject" } }
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