active-triples 0.8.1 → 0.8.2

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