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.
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