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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +8 -9
  3. data/CHANGES.md +69 -0
  4. data/Gemfile +0 -2
  5. data/Guardfile +1 -2
  6. data/active-triples.gemspec +3 -3
  7. data/lib/active/triples.rb +1 -0
  8. data/lib/active_triples.rb +4 -0
  9. data/lib/active_triples/configurable.rb +3 -1
  10. data/lib/active_triples/configuration.rb +1 -0
  11. data/lib/active_triples/configuration/item.rb +1 -0
  12. data/lib/active_triples/configuration/item_factory.rb +1 -0
  13. data/lib/active_triples/configuration/merge_item.rb +5 -2
  14. data/lib/active_triples/extension_strategy.rb +1 -0
  15. data/lib/active_triples/identifiable.rb +1 -0
  16. data/lib/active_triples/list.rb +2 -0
  17. data/lib/active_triples/nested_attributes.rb +1 -1
  18. data/lib/active_triples/node_config.rb +5 -3
  19. data/lib/active_triples/persistable.rb +1 -0
  20. data/lib/active_triples/persistence_strategies/parent_strategy.rb +104 -29
  21. data/lib/active_triples/persistence_strategies/persistence_strategy.rb +15 -7
  22. data/lib/active_triples/persistence_strategies/repository_strategy.rb +26 -22
  23. data/lib/active_triples/properties.rb +84 -6
  24. data/lib/active_triples/property.rb +35 -4
  25. data/lib/active_triples/property_builder.rb +38 -4
  26. data/lib/active_triples/rdf_source.rb +225 -75
  27. data/lib/active_triples/reflection.rb +42 -3
  28. data/lib/active_triples/relation.rb +330 -73
  29. data/lib/active_triples/repositories.rb +4 -2
  30. data/lib/active_triples/resource.rb +1 -0
  31. data/lib/active_triples/schema.rb +1 -0
  32. data/lib/active_triples/undefined_property_error.rb +27 -0
  33. data/lib/active_triples/version.rb +2 -1
  34. data/spec/active_triples/configurable_spec.rb +3 -2
  35. data/spec/active_triples/configuration_spec.rb +2 -1
  36. data/spec/active_triples/extension_strategy_spec.rb +2 -1
  37. data/spec/active_triples/identifiable_spec.rb +7 -11
  38. data/spec/active_triples/list_spec.rb +1 -4
  39. data/spec/active_triples/nested_attributes_spec.rb +4 -3
  40. data/spec/active_triples/persistable_spec.rb +4 -1
  41. data/spec/active_triples/persistence_strategies/parent_strategy_spec.rb +141 -11
  42. data/spec/active_triples/persistence_strategies/persistence_strategy_spec.rb +1 -0
  43. data/spec/active_triples/persistence_strategies/repository_strategy_spec.rb +32 -17
  44. data/spec/active_triples/properties_spec.rb +68 -33
  45. data/spec/active_triples/property_builder_spec.rb +36 -0
  46. data/spec/active_triples/property_spec.rb +15 -1
  47. data/spec/active_triples/rdf_source_spec.rb +544 -6
  48. data/spec/active_triples/reflection_spec.rb +78 -0
  49. data/spec/active_triples/relation_spec.rb +505 -3
  50. data/spec/active_triples/repositories_spec.rb +3 -1
  51. data/spec/active_triples/resource_spec.rb +90 -147
  52. data/spec/active_triples/schema_spec.rb +3 -2
  53. data/spec/active_triples_spec.rb +1 -0
  54. data/spec/integration/dummies/dummy_resource_a.rb +6 -0
  55. data/spec/integration/dummies/dummy_resource_b.rb +6 -0
  56. data/spec/integration/parent_persistence_spec.rb +18 -0
  57. data/spec/integration/reciprocal_properties_spec.rb +69 -0
  58. data/spec/pragmatic_context_spec.rb +10 -8
  59. data/spec/spec_helper.rb +5 -0
  60. data/spec/support/active_model_lint.rb +4 -6
  61. data/spec/support/dummies/basic_persistable.rb +2 -11
  62. data/spec/support/matchers.rb +11 -0
  63. data/spec/support/shared_examples/persistence_strategy.rb +3 -16
  64. 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 'should set a property as a NodeConfig' do
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 'should set the correct property' do
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 'should set the correct property from string' do
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 'should set index behaviors' do
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]).to eq [:facetable, :searchable]
35
+ expect(DummyProperties.reflect_on_property(:title)[:behaviors])
36
+ .to eq [:facetable, :searchable]
35
37
  end
36
38
 
37
- it 'should set class name' do
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 "should constantize string class names" do
43
- DummyProperties.property :title, :predicate => RDF::DC.title, :class_name => "RDF::Literal"
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 "should keep strings which it can't constantize as strings" do
48
- DummyProperties.property :title, :predicate => RDF::DC.title, :class_name => "FakeClassName"
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, :predicate => RDF::DC.type }.to raise_error ArgumentError
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, :predicate => RDF::DC.alternative
72
- DummyProperties.property :title, :predicate => RDF::DC.title
73
- expect(DummyProperties.reflect_on_property(:title).predicate).to eq RDF::DC.title
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)).to eq DummyProperties.properties['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')).to eq DummyProperties.properties['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)).to eq DummyProperties.properties['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 eq [:title, :name]
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)).to be_kind_of ActiveTriples::NodeConfig
120
- expect(DummySubClass.reflect_on_property(:source)).to be_kind_of ActiveTriples::NodeConfig
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
- RSpec.describe ActiveTriples::Property do
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 '#id' do
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
- it 'sets a value'
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 << RDF::Statement.from([nil, nil, nil]) }
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