hydra-pcdm 0.0.1 → 0.1.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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -0
  3. data/Gemfile +0 -4
  4. data/README.md +2 -2
  5. data/hydra-pcdm.gemspec +2 -1
  6. data/lib/hydra/pcdm.rb +21 -30
  7. data/lib/hydra/pcdm/ancestor_checker.rb +18 -0
  8. data/lib/hydra/pcdm/collection_indexer.rb +2 -2
  9. data/lib/hydra/pcdm/deep_member_iterator.rb +31 -0
  10. data/lib/hydra/pcdm/models/concerns/collection_behavior.rb +20 -57
  11. data/lib/hydra/pcdm/models/concerns/object_behavior.rb +12 -37
  12. data/lib/hydra/pcdm/models/concerns/pcdm_behavior.rb +34 -0
  13. data/lib/hydra/pcdm/object_indexer.rb +1 -1
  14. data/lib/hydra/pcdm/services/file/get_mime_type.rb +1 -1
  15. data/lib/hydra/pcdm/validators.rb +8 -0
  16. data/lib/hydra/pcdm/validators/ancestor_validator.rb +26 -0
  17. data/lib/hydra/pcdm/validators/composite_validator.rb +17 -0
  18. data/lib/hydra/pcdm/validators/pcdm_object_validator.rb +9 -0
  19. data/lib/hydra/pcdm/validators/pcdm_validator.rb +9 -0
  20. data/lib/hydra/pcdm/version.rb +1 -1
  21. data/lib/hydra/pcdm/vocab/pcdm_terms.rb +1 -1
  22. data/spec/hydra/pcdm/ancestor_checker_spec.rb +29 -0
  23. data/spec/hydra/pcdm/collection_indexer_spec.rb +11 -11
  24. data/spec/hydra/pcdm/deep_member_iterator_spec.rb +52 -0
  25. data/spec/hydra/pcdm/models/collection_spec.rb +691 -17
  26. data/spec/hydra/pcdm/models/file_spec.rb +16 -0
  27. data/spec/hydra/pcdm/models/object_spec.rb +576 -27
  28. data/spec/hydra/pcdm/object_indexer_spec.rb +6 -6
  29. data/spec/spec_helper.rb +8 -1
  30. metadata +31 -52
  31. data/lib/hydra/pcdm/services/collection/add_collection.rb +0 -20
  32. data/lib/hydra/pcdm/services/collection/add_object.rb +0 -19
  33. data/lib/hydra/pcdm/services/collection/add_related_object.rb +0 -21
  34. data/lib/hydra/pcdm/services/collection/get_collections.rb +0 -18
  35. data/lib/hydra/pcdm/services/collection/get_objects.rb +0 -18
  36. data/lib/hydra/pcdm/services/collection/get_related_objects.rb +0 -17
  37. data/lib/hydra/pcdm/services/collection/remove_collection.rb +0 -36
  38. data/lib/hydra/pcdm/services/collection/remove_object.rb +0 -43
  39. data/lib/hydra/pcdm/services/collection/remove_related_object.rb +0 -36
  40. data/lib/hydra/pcdm/services/object/add_object.rb +0 -20
  41. data/lib/hydra/pcdm/services/object/add_related_object.rb +0 -21
  42. data/lib/hydra/pcdm/services/object/get_objects.rb +0 -18
  43. data/lib/hydra/pcdm/services/object/get_related_objects.rb +0 -17
  44. data/lib/hydra/pcdm/services/object/remove_object.rb +0 -43
  45. data/lib/hydra/pcdm/services/object/remove_related_object.rb +0 -36
  46. data/spec/hydra/pcdm/services/collection/add_collection_spec.rb +0 -197
  47. data/spec/hydra/pcdm/services/collection/add_object_spec.rb +0 -132
  48. data/spec/hydra/pcdm/services/collection/add_related_object_spec.rb +0 -94
  49. data/spec/hydra/pcdm/services/collection/get_collections_spec.rb +0 -40
  50. data/spec/hydra/pcdm/services/collection/get_objects_spec.rb +0 -40
  51. data/spec/hydra/pcdm/services/collection/get_related_objects_spec.rb +0 -37
  52. data/spec/hydra/pcdm/services/collection/remove_collection_spec.rb +0 -143
  53. data/spec/hydra/pcdm/services/collection/remove_object_spec.rb +0 -180
  54. data/spec/hydra/pcdm/services/collection/remove_related_object_spec.rb +0 -146
  55. data/spec/hydra/pcdm/services/file/add_type_spec.rb +0 -19
  56. data/spec/hydra/pcdm/services/object/add_object_spec.rb +0 -186
  57. data/spec/hydra/pcdm/services/object/add_related_object_spec.rb +0 -94
  58. data/spec/hydra/pcdm/services/object/get_objects_spec.rb +0 -33
  59. data/spec/hydra/pcdm/services/object/get_related_objects_spec.rb +0 -39
  60. data/spec/hydra/pcdm/services/object/remove_object_spec.rb +0 -158
  61. data/spec/hydra/pcdm/services/object/remove_related_object_spec.rb +0 -126
@@ -3,7 +3,7 @@ module Hydra::PCDM
3
3
 
4
4
  def self.call(path)
5
5
  raise ArgumentError, "supplied argument should be a path to a file" unless path.is_a?(String)
6
- mime_types = MIME::Types.of(::File.basename(path))
6
+ mime_types = ::MIME::Types.of(::File.basename(path))
7
7
  mime_types.empty? ? "application/octet-stream" : mime_types.first.content_type
8
8
  end
9
9
 
@@ -0,0 +1,8 @@
1
+ module Hydra::PCDM
2
+ module Validators
3
+ autoload :AncestorValidator, 'hydra/pcdm/validators/ancestor_validator'
4
+ autoload :PCDMValidator, 'hydra/pcdm/validators/pcdm_validator'
5
+ autoload :CompositeValidator, 'hydra/pcdm/validators/composite_validator'
6
+ autoload :PCDMObjectValidator, 'hydra/pcdm/validators/pcdm_object_validator'
7
+ end
8
+ end
@@ -0,0 +1,26 @@
1
+ module Hydra::PCDM::Validators
2
+ class AncestorValidator
3
+ def self.validate!(association,record)
4
+ new(association.owner, record).validate!
5
+ end
6
+
7
+ attr_reader :owner, :record
8
+ delegate :ancestor?, to: :ancestor_checker
9
+ def initialize(owner, record)
10
+ @owner = owner
11
+ @record = record
12
+ end
13
+
14
+ def validate!
15
+ if ancestor?(record)
16
+ raise ArgumentError, "#{record.class} with ID: #{record.id} failed to pass AncestorChecker validation"
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def ancestor_checker
23
+ @ancestor_checker ||= ::Hydra::PCDM::AncestorChecker.new(owner)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ module Hydra::PCDM::Validators
2
+ ##
3
+ # Object which acts as one validator but delegates to many.
4
+ class CompositeValidator
5
+ attr_reader :validators
6
+
7
+ def initialize(*validators)
8
+ @validators = validators.compact
9
+ end
10
+
11
+ def validate!(reflection, record)
12
+ validators.each do |validator|
13
+ validator.validate!(reflection, record)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ module Hydra::PCDM::Validators
2
+ class PCDMObjectValidator
3
+ def self.validate!(association, record)
4
+ unless record.try(:pcdm_object?)
5
+ raise ActiveFedora::AssociationTypeMismatch.new "#{record} is not a PCDM object."
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Hydra::PCDM::Validators
2
+ class PCDMValidator
3
+ def self.validate!(reflection, record)
4
+ if !record.try(:pcdm_object?) && !record.try(:pcdm_collection?)
5
+ raise ActiveFedora::AssociationTypeMismatch.new "#{record} is not a PCDM object or collection."
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,5 +1,5 @@
1
1
  module Hydra
2
2
  module PCDM
3
- VERSION = "0.0.1"
3
+ VERSION = "0.1.0"
4
4
  end
5
5
  end
@@ -82,6 +82,6 @@ module RDFVocabularies
82
82
  "dc:title" => %(Portland Common Data Model).freeze,
83
83
  label: "".freeze,
84
84
  "owl:versionInfo" => %(2015/03/16).freeze,
85
- "rdfs:seeAlso" => %(https://wiki.duraspace.org/display/FF/Portland+Common+Data+Model).freeze
85
+ "rdfs:seeAlso" => %(https://github.com/duraspace/pcdm/wiki).freeze
86
86
  end
87
87
  end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Hydra::PCDM::AncestorChecker do
4
+ subject { described_class.new(record) }
5
+
6
+ describe "#ancestor?" do
7
+ let(:record) { instance_double(Hydra::PCDM::Object) }
8
+ let(:member) { record }
9
+ let(:result) { subject.ancestor?(member) }
10
+
11
+ context "when the member is the record itself" do
12
+ it "is true" do
13
+ expect(result).to eq true
14
+ end
15
+ end
16
+ context "when the member is not an ancestor" do
17
+ let(:member) { instance_double(Hydra::PCDM::Object, members: []) }
18
+ it "is false" do
19
+ expect(result).to eq false
20
+ end
21
+ end
22
+ context "when the member is an ancestor" do
23
+ let(:member) { instance_double(Hydra::PCDM::Object, members: [record]) }
24
+ it "is true" do
25
+ expect(result).to eq true
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,25 +1,25 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Hydra::PCDM::CollectionIndexer do
4
- let(:collection) { Hydra::PCDM::Collection.new }
5
- let(:child_collections1) { Hydra::PCDM::Collection.new(id: '123') }
6
- let(:child_collections2) { Hydra::PCDM::Collection.new(id: '456') }
7
- let(:object1) { Hydra::PCDM::Object.new(id: '789') }
8
- let(:indexer) { described_class.new(collection) }
4
+ let(:collection) { Hydra::PCDM::Collection.new }
5
+ let(:child_collection_ids) { ['123', '456'] }
6
+ let(:child_object_ids) { ['789'] }
7
+ let(:member_ids) { ['123', '456', '789'] }
8
+ let(:indexer) { described_class.new(collection) }
9
9
 
10
10
  before do
11
- allow(collection).to receive(:child_collections).and_return([child_collections1, child_collections2])
12
- allow(collection).to receive(:objects).and_return([object1])
13
- allow(collection).to receive(:member_ids).and_return(['123', '456', '789'])
11
+ allow(collection).to receive(:child_collection_ids).and_return(child_collection_ids)
12
+ allow(collection).to receive(:child_object_ids).and_return(child_object_ids)
13
+ allow(collection).to receive(:member_ids).and_return(member_ids)
14
14
  end
15
15
 
16
16
  describe "#generate_solr_document" do
17
17
  subject { indexer.generate_solr_document }
18
18
 
19
19
  it "has fields" do
20
- expect(subject['child_collections_ssim']).to eq ['123', '456']
21
- expect(subject['objects_ssim']).to eq ['789']
22
- expect(subject['members_ssim']).to eq ['123', '456', '789']
20
+ expect(subject['child_collection_ids_ssim']).to eq ['123', '456']
21
+ expect(subject['child_object_ids_ssim']).to eq ['789']
22
+ expect(subject['member_ids_ssim']).to eq ['123', '456', '789']
23
23
  end
24
24
  end
25
25
  end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Hydra::PCDM::DeepMemberIterator do
4
+ subject { described_class.new(record) }
5
+ let(:record) { instance_double(Hydra::PCDM::Object, members: members) }
6
+ let(:members) { [] }
7
+ describe "#each" do
8
+ context "with no members" do
9
+ it "should return an empty array" do
10
+ expect(subject.to_a).to eq []
11
+ end
12
+ end
13
+ context "with a member" do
14
+ let(:members) { [ instance_double(Hydra::PCDM::Object, members: []) ] }
15
+ it "should return that member" do
16
+ expect(subject.to_a).to eq members
17
+ end
18
+ end
19
+ context "with deep members" do
20
+ let(:member_1) { instance_double(Hydra::PCDM::Object, members: [member_3]) }
21
+ let(:member_2) { instance_double(Hydra::PCDM::Object, members: [member_4]) }
22
+ let(:member_3) { instance_double(Hydra::PCDM::Object, members: []) }
23
+ let(:member_4) { instance_double(Hydra::PCDM::Object, members: []) }
24
+ let(:members) { [ member_1, member_2] }
25
+ it "should do a breadth first iteration of members" do
26
+ expect(subject.to_a).to eq [member_1, member_2, member_3, member_4]
27
+ end
28
+ end
29
+ context "with n levels deep" do
30
+ let(:member_1) { instance_double(Hydra::PCDM::Object, members: [member_2]) }
31
+ let(:member_2) { instance_double(Hydra::PCDM::Object, members: [member_3]) }
32
+ let(:member_3) { instance_double(Hydra::PCDM::Object, members: []) }
33
+ let(:members) { [ member_1] }
34
+ it "should traverse it" do
35
+ expect(subject.to_a).to eq [member_1, member_2, member_3]
36
+ end
37
+ end
38
+ end
39
+ describe ".include?" do
40
+ context "with n levels deep" do
41
+ let(:member_1) { instance_double(Hydra::PCDM::Object, members: [member_2]) }
42
+ let(:member_2) { instance_double(Hydra::PCDM::Object, members: [member_3]) }
43
+ let(:member_3) { instance_double(Hydra::PCDM::Object, members: []) }
44
+ let(:members) { [ member_1] }
45
+ it "should not go any deeper than necessary" do
46
+ expect(subject).to include(member_2)
47
+ expect(member_2).not_to have_received(:members)
48
+ expect(member_3).not_to have_received(:members)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -2,40 +2,714 @@ require 'spec_helper'
2
2
 
3
3
  describe Hydra::PCDM::Collection do
4
4
 
5
- let(:collection1) { Hydra::PCDM::Collection.create }
6
- let(:collection2) { Hydra::PCDM::Collection.create }
7
- let(:collection3) { Hydra::PCDM::Collection.create }
5
+ let(:collection1) { Hydra::PCDM::Collection.new }
6
+ let(:collection2) { Hydra::PCDM::Collection.new }
7
+ let(:collection3) { Hydra::PCDM::Collection.new }
8
8
 
9
- let(:object1) { Hydra::PCDM::Object.create }
10
- let(:object2) { Hydra::PCDM::Object.create }
9
+ let(:object1) { Hydra::PCDM::Object.new }
10
+ let(:object2) { Hydra::PCDM::Object.new }
11
+
12
+ describe "adding collections" do
13
+ describe "with acceptable inputs" do
14
+ subject { Hydra::PCDM::Collection.new }
15
+ it 'should add collections, sub-collections, and repeating collections' do
16
+ subject.child_collections << collection1 # first add
17
+ subject.child_collections << collection2 # second add to same collection
18
+ subject.child_collections << collection1 # repeat a collection
19
+ collection1.child_collections << collection3 # add sub-collection
20
+ expect( subject.child_collections ).to eq [collection1,collection2,collection1]
21
+ expect( collection1.child_collections ).to eq [collection3]
22
+ end
23
+ it 'should add an object to collection with collections and objects' do
24
+ subject.members << collection1
25
+ subject.members << collection2
26
+ subject.members << object1
27
+ subject.members << object2
28
+ subject.members << collection3
29
+ expect( subject.members ).to eq [collection1,collection2,object1,object2,collection3]
30
+ expect( subject.child_collections ).to eq [collection1,collection2,collection3]
31
+ expect( subject.child_objects ).to eq [object1,object2]
32
+ end
33
+ end
34
+
35
+ describe 'aggregates collections that implement Hydra::PCDM' do
36
+ before do
37
+ class Kollection < ActiveFedora::Base
38
+ include Hydra::PCDM::CollectionBehavior
39
+ end
40
+ end
41
+ after { Object.send(:remove_const, :Kollection) }
42
+ let(:kollection1) { Kollection.new }
43
+
44
+ it 'should accept implementing collection as a child' do
45
+ subject.child_collections << kollection1
46
+ expect( subject.child_collections ).to eq [kollection1]
47
+ end
48
+
49
+ it 'should accept implementing collection as a parent' do
50
+ kollection1.child_collections << collection1
51
+ expect( kollection1.child_collections ).to eq [collection1]
52
+ end
53
+ end
54
+
55
+ describe 'aggregates collections that extend Hydra::PCDM' do
56
+ before do
57
+ class Cullection < Hydra::PCDM::Collection
58
+ end
59
+ end
60
+ after { Object.send(:remove_const, :Cullection) }
61
+ let(:cullection1) { Cullection.new }
62
+
63
+ it 'should accept extending collection as a child' do
64
+ subject.child_collections << cullection1
65
+ expect( subject.child_collections ).to eq [cullection1]
66
+ end
67
+
68
+ it 'should accept extending collection as a parent' do
69
+ cullection1.child_collections << collection1
70
+ expect( cullection1.child_collections ).to eq [collection1]
71
+ end
72
+ end
73
+
74
+ describe 'with unacceptable input types' do
75
+ before(:all) do
76
+ @object101 = Hydra::PCDM::Object.new
77
+ @file101 = Hydra::PCDM::File.new
78
+ @non_PCDM_object = "I'm not a PCDM object"
79
+ @af_base_object = ActiveFedora::Base.new
80
+ end
81
+
82
+ context 'that are unacceptable child collections' do
83
+ let(:error_type1) { ArgumentError }
84
+ let(:error_message1) { 'Hydra::PCDM::Object with ID: was expected to pcdm_collection?, but it was false' }
85
+ let(:error_type2) { NoMethodError }
86
+ let(:error_message2) { /undefined method `pcdm_collection\?' for .*/ }
87
+
88
+ it 'should NOT aggregate Hydra::PCDM::Objects in collections aggregation' do
89
+ expect{ collection1.child_collections << @object101 }.to raise_error(error_type1,error_message1)
90
+ end
91
+
92
+ it 'should NOT aggregate Hydra::PCDM::Files in collections aggregation' do
93
+ expect{ collection1.child_collections << @file101 }.to raise_error(error_type2,error_message2)
94
+ end
95
+
96
+ it 'should NOT aggregate non-PCDM objects in collections aggregation' do
97
+ expect{ collection1.child_collections << @non_PCDM_object }.to raise_error(error_type2,error_message2)
98
+ end
99
+
100
+ it 'should NOT aggregate AF::Base objects in collections aggregation' do
101
+ expect{ collection1.child_collections << @af_base_object }.to raise_error(error_type2,error_message2)
102
+ end
103
+ end
104
+ end
105
+
106
+ describe "adding collections that are ancestors" do
107
+ let(:error_type) { ArgumentError }
108
+ let(:error_message) { 'Hydra::PCDM::Collection with ID: failed to pass AncestorChecker validation' }
109
+
110
+ context "when the source collection is the same" do
111
+ it "raises an error" do
112
+ expect{ subject.child_collections << subject }.to raise_error(error_type, error_message)
113
+ end
114
+ end
115
+
116
+ before do
117
+ subject.child_collections << collection1
118
+ end
119
+
120
+ it "raises and error" do
121
+ expect{ collection1.child_collections << subject }.to raise_error(error_type, error_message)
122
+ end
123
+
124
+ context "with more ancestors" do
125
+ before do
126
+ collection1.child_collections << collection2
127
+ end
128
+
129
+ it "raises an error" do
130
+ expect{ collection2.child_collections << subject }.to raise_error(error_type, error_message)
131
+ end
132
+
133
+ context "with a more complicated example" do
134
+ before do
135
+ collection2.child_collections << collection3
136
+ end
137
+
138
+ it "raises errors" do
139
+ expect{ collection3.child_collections << subject }.to raise_error(error_type, error_message)
140
+ expect{ collection3.child_collections << collection1 }.to raise_error(error_type, error_message)
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ describe "removing collections" do
148
+ subject { Hydra::PCDM::Collection.new }
149
+
150
+ context 'when it is the only collection' do
151
+
152
+ before do
153
+ subject.members << collection1
154
+ expect( subject.child_collections ).to eq [collection1]
155
+ end
156
+
157
+ it 'should remove collection while changes are in memory' do
158
+ expect( subject.members.delete collection1 ).to eq [collection1]
159
+ expect( subject.child_collections ).to eq []
160
+ end
161
+
162
+ it 'should remove collection only when objects and all changes are in memory' do
163
+ subject.members << object1
164
+ subject.members << object2
165
+ expect( subject.members.delete collection1 ).to eq [collection1]
166
+ expect( subject.child_collections ).to eq []
167
+ expect( subject.child_objects ).to eq [object1,object2]
168
+ end
169
+ end
170
+
171
+ context 'when multiple collections' do
172
+ before do
173
+ subject.members << collection1
174
+ subject.members << collection2
175
+ subject.members << collection3
176
+ expect( subject.child_collections ).to eq [collection1,collection2,collection3]
177
+ end
178
+
179
+ it 'should remove first collection when changes are in memory' do
180
+ expect( subject.members.delete collection1 ).to eq [collection1]
181
+ expect( subject.child_collections ).to eq [collection2,collection3]
182
+ end
183
+
184
+ it 'should remove last collection when changes are in memory' do
185
+ expect( subject.members.delete collection3 ).to eq [collection3]
186
+ expect( subject.child_collections ).to eq [collection1,collection2]
187
+ end
188
+
189
+ it 'should remove middle collection when changes are in memory' do
190
+ expect( subject.members.delete collection2 ).to eq [collection2]
191
+ expect( subject.child_collections ).to eq [collection1,collection3]
192
+ end
193
+
194
+ it 'should remove middle collection when changes are saved' do
195
+ expect( subject.child_collections ).to eq [collection1,collection2,collection3]
196
+ subject.save
197
+ expect( subject.members.delete collection2 ).to eq [collection2]
198
+ expect( subject.child_collections ).to eq [collection1,collection3]
199
+ end
200
+ end
201
+ context 'when collection is missing' do
202
+ it 'and 0 sub-collections should return empty array' do
203
+ expect( subject.members.delete collection1 ).to eq []
204
+ end
205
+
206
+ it 'and multiple sub-collections should return empty array when changes are in memory' do
207
+ subject.members << collection1
208
+ subject.members << collection3
209
+ expect( subject.members.delete collection2 ).to eq []
210
+ end
211
+
212
+ it 'should return empty array when changes are saved' do
213
+ subject.members << collection1
214
+ subject.members << collection3
215
+ subject.save
216
+ expect( subject.members.delete collection2 ).to eq []
217
+ end
218
+ end
219
+ end
220
+
221
+ describe "adding objects" do
222
+ context 'with acceptable inputs' do
223
+ it 'should add objects, sub-collections, and repeating collections' do
224
+ subject.child_objects << object1 # first add
225
+ subject.child_objects << object2 # second add to same collection
226
+ subject.child_objects << object1 # repeat an object
227
+ expect( subject.child_objects ).to eq [object1,object2,object1]
228
+ end
229
+
230
+ context 'with collections and objects' do
231
+ it 'should add an object to collection with collections and objects' do
232
+ subject.child_objects << object1
233
+ subject.child_collections << collection1
234
+ subject.child_collections << collection2
235
+ subject.child_objects << object2
236
+ expect( subject.child_objects ).to eq [object1,object2]
237
+ end
238
+ end
239
+
240
+ describe 'aggregates objects that implement Hydra::PCDM' do
241
+ before do
242
+ class Ahbject < ActiveFedora::Base
243
+ include Hydra::PCDM::ObjectBehavior
244
+ end
245
+ end
246
+ after { Object.send(:remove_const, :Ahbject) }
247
+ let(:ahbject1) { Ahbject.new }
248
+
249
+ it 'should accept implementing object as a child' do
250
+ subject.child_objects << ahbject1
251
+ expect( subject.child_objects ).to eq [ahbject1]
252
+ end
253
+
254
+ end
255
+
256
+ describe 'aggregates objects that extend Hydra::PCDM' do
257
+ before do
258
+ class Awbject < Hydra::PCDM::Object
259
+ end
260
+ end
261
+ after { Object.send(:remove_const, :Awbject) }
262
+ let(:awbject1) { Awbject.new }
263
+
264
+ it 'should accept extending object as a child' do
265
+ subject.child_objects << awbject1
266
+ expect( subject.child_objects ).to eq [awbject1]
267
+ end
268
+ end
269
+ end
270
+
271
+ context 'with unacceptable inputs' do
272
+ before(:all) do
273
+ @file101 = Hydra::PCDM::File.new
274
+ @non_PCDM_object = "I'm not a PCDM object"
275
+ @af_base_object = ActiveFedora::Base.new
276
+ end
277
+
278
+ context 'with unacceptable objects' do
279
+ let(:error_type1) { ArgumentError }
280
+ let(:error_message1) { 'Hydra::PCDM::Collection with ID: was expected to pcdm_object?, but it was false' }
281
+ let(:error_type2) { NoMethodError }
282
+ let(:error_message2) { /undefined method `pcdm_object\?' for .*/ }
283
+
284
+ it 'should NOT aggregate Hydra::PCDM::Collection in objects aggregation' do
285
+ expect{ collection1.child_objects << collection2 }.to raise_error(error_type1,error_message1)
286
+ end
287
+
288
+ it 'should NOT aggregate Hydra::PCDM::Files in objects aggregation' do
289
+ expect{ collection1.child_objects << @file101 }.to raise_error(error_type2,error_message2)
290
+ end
291
+
292
+ it 'should NOT aggregate non-PCDM objects in objects aggregation' do
293
+ expect{ collection1.child_objects << @non_PCDM_object }.to raise_error(error_type2,error_message2)
294
+ end
295
+
296
+ it 'should NOT aggregate AF::Base objects in objects aggregation' do
297
+ expect{ collection1.child_objects << @af_base_object }.to raise_error(error_type2,error_message2)
298
+ end
299
+ end
300
+ end
301
+ end
302
+
303
+ describe "removing objects" do
304
+ context 'when it is the only object' do
305
+ before do
306
+ subject.child_objects << object1
307
+ expect( subject.child_objects ).to eq [object1]
308
+ end
309
+
310
+ it 'should remove object while changes are in memory' do
311
+ expect( subject.child_objects.delete object1 ).to eq [object1]
312
+ expect( subject.child_objects ).to eq []
313
+ end
314
+
315
+ it 'should remove object only when collections and all changes are in memory' do
316
+ subject.child_collections << collection1
317
+ subject.child_collections << collection2
318
+ expect( subject.child_objects.delete object1 ).to eq [object1]
319
+ expect( subject.child_objects ).to eq []
320
+ expect( subject.child_collections ).to eq [collection1,collection2]
321
+ end
322
+ end
323
+
324
+ context 'when multiple objects' do
325
+ let(:object3) { Hydra::PCDM::Object.new }
326
+
327
+ before do
328
+ subject.child_objects << object1
329
+ subject.child_objects << object2
330
+ subject.child_objects << object3
331
+ expect( subject.child_objects ).to eq [object1,object2,object3]
332
+ end
333
+
334
+ it 'should remove first object when changes are in memory' do
335
+ expect( subject.child_objects.delete object1 ).to eq [object1]
336
+ expect( subject.child_objects ).to eq [object2,object3]
337
+ end
338
+
339
+ it 'should remove middle object when changes are in memory' do
340
+ expect( subject.child_objects.delete object2 ).to eq [object2]
341
+ expect( subject.child_objects ).to eq [object1,object3]
342
+ end
343
+
344
+ it 'should remove last object when changes are in memory' do
345
+ expect( subject.child_objects.delete object3 ).to eq [object3]
346
+ expect( subject.child_objects ).to eq [object1,object2]
347
+ end
348
+ it 'should remove middle object when changes are saved' do
349
+ expect( subject.child_objects.delete object2 ).to eq [object2]
350
+ expect( subject.child_objects ).to eq [object1,object3]
351
+ subject.save
352
+ expect( subject.reload.child_objects ).to eq [object1,object3]
353
+ end
354
+ end
355
+
356
+ context 'when object repeats' do
357
+ let(:object3) { Hydra::PCDM::Object.new }
358
+ let(:object4) { Hydra::PCDM::Object.new }
359
+ let(:object5) { Hydra::PCDM::Object.new }
360
+
361
+ before do
362
+ subject.child_objects << object1
363
+ subject.child_objects << object2
364
+ subject.child_objects << object3
365
+ subject.child_objects << object2
366
+ subject.child_objects << object4
367
+ subject.child_objects << object2
368
+ subject.child_objects << object5
369
+ expect( subject.child_objects ).to eq [object1,object2,object3,object2,object4,object2,object5]
370
+ end
371
+
372
+ # TODO pending implementation of multiple objects
373
+
374
+ it 'should remove first occurrence when changes in memory' do
375
+ expect( subject.child_objects.delete object2 ).to eq [object2]
376
+ expect( subject.child_objects ).to eq [object1,object3,object4,object5]
377
+ end
378
+
379
+ it 'should remove last occurrence when changes in memory' do
380
+ skip( "pending resolution of AF-agg 46 and PCDM 102") do
381
+ expect( subject.child_objects.delete object2, -1 ).to eq object2
382
+ expect( subject.child_objects ).to eq [object1,object2,object3,object2,object4,object5]
383
+ end
384
+ end
385
+
386
+ it 'should remove nth occurrence when changes in memory' do
387
+ skip( "pending resolution of AF-agg 46 and PCDM 102") do
388
+ expect( subject.child_objects.delete object2, 2 ).to eq object2
389
+ expect( subject.child_objects ).to eq [object1,object2,object3,object4,object2,object5]
390
+ end
391
+ end
392
+ it 'should remove nth occurrence when changes are saved' do
393
+ skip( "pending resolution of AF-agg 46 and PCDM 102") do
394
+ expect( subject.child_objects ).to eq [object1,object2,object3,object2,object4,object2,object5]
395
+ subject.save
396
+ expect( subject.reload.child_objects ).to eq [object1,object2,object3,object2,object4,object2,object5]
397
+
398
+ expect( subject.child_objects.delete object2, 2 ).to eq object2
399
+ subject.save
400
+ expect( subject.reload.child_objects ).to eq [object1,object2,object3,object4,object2,object5]
401
+ end
402
+ end
403
+ end
404
+
405
+ context 'when object is missing' do
406
+ let(:object3) { Hydra::PCDM::Object.new }
407
+
408
+ it 'and 0 objects in collection should return empty array' do
409
+ expect( subject.child_objects.delete object1 ).to eq []
410
+ end
411
+
412
+ it 'and multiple objects in collection should return empty array when changes are in memory' do
413
+ subject.child_objects << object1
414
+ subject.child_objects << object2
415
+ expect( subject.child_objects.delete object3 ).to eq []
416
+ end
417
+
418
+ it 'should return empty array when changes are saved' do
419
+ subject.child_objects << object1
420
+ subject.child_objects << object2
421
+ subject.save
422
+ expect( subject.reload.child_objects.delete object3 ).to eq []
423
+ end
424
+ end
425
+ end
426
+
427
+ describe "add related objects" do
428
+ context 'with acceptable collections' do
429
+
430
+ it 'should add objects to the related object set' do
431
+ collection1.related_objects << object1 # first add
432
+ collection1.related_objects << object2 # second add to same collection
433
+ collection1.save
434
+ related_objects = collection1.reload.related_objects
435
+ expect( related_objects.include? object1 ).to be true
436
+ expect( related_objects.include? object2 ).to be true
437
+ expect( related_objects.size ).to eq 2
438
+ end
439
+
440
+ it 'should be empty when no related objects' do
441
+ expect( collection1.related_objects ).to eq []
442
+ end
443
+
444
+ it 'should not repeat objects in the related object set' do
445
+ skip 'pending resolution of ActiveFedora issue #853' do
446
+ collection1.related_objects << object1 # first add
447
+ collection1.related_objects << object2 # second add to same collection
448
+ collection1.related_objects << object1 # repeat an object replaces the object
449
+ related_objects = collection1.related_objects
450
+ expect( related_objects.include? object1 ).to be true
451
+ expect( related_objects.include? object2 ).to be true
452
+ expect( related_objects.size ).to eq 2
453
+ end
454
+ end
455
+ end
456
+ context 'with unacceptable inputs' do
457
+ before(:all) do
458
+ @file101 = Hydra::PCDM::File.new
459
+ @non_PCDM_object = "I'm not a PCDM object"
460
+ @af_base_object = ActiveFedora::Base.new
461
+ end
462
+
463
+ context 'with unacceptable related objects' do
464
+ it 'should NOT aggregate Hydra::PCDM::Collection in objects aggregation' do
465
+ expect{ collection2.related_objects << collection1 }.to raise_error(ActiveFedora::AssociationTypeMismatch,/Hydra::PCDM::Collection:.* is not a PCDM object/)
466
+ end
467
+
468
+ it 'should NOT aggregate Hydra::PCDM::Files in objects aggregation' do
469
+ expect{ collection2.related_objects << @file101 }.to raise_error(ActiveFedora::AssociationTypeMismatch,/ActiveFedora::Base\(#\d+\) expected, got Hydra::PCDM::File\(#\d+\)/)
470
+ end
471
+
472
+ it 'should NOT aggregate non-PCDM objects in objects aggregation' do
473
+ expect{ collection2.related_objects << @non_PCDM_object }.to raise_error(ActiveFedora::AssociationTypeMismatch,/ActiveFedora::Base\(#\d+\) expected, got String\(#\d+\)/)
474
+ end
475
+
476
+ it 'should NOT aggregate AF::Base objects in objects aggregation' do
477
+ expect{ collection2.related_objects << @af_base_object }.to raise_error(ActiveFedora::AssociationTypeMismatch,/ActiveFedora::Base:.*> is not a PCDM object/)
478
+ end
479
+ end
480
+
481
+ context 'with unacceptable parent object' do
482
+ it 'should NOT accept Hydra::PCDM::Files as parent object' do
483
+ expect{ @file1.related_objects << object1 }.to raise_error(NoMethodError)
484
+ end
485
+
486
+ it 'should NOT accept non-PCDM objects as parent object' do
487
+ expect{ @non_PCDM_object.related_objects << object1 }.to raise_error(NoMethodError)
488
+ end
489
+
490
+ it 'should NOT accept AF::Base objects as parent object' do
491
+ expect{ @af_base_object.related_objects << object1 }.to raise_error(NoMethodError)
492
+ end
493
+
494
+ it 'Hydra::PCDM::File should NOT have related files' do
495
+ expect{ @file1.related_objects }.to raise_error(NoMethodError)
496
+ end
497
+
498
+ it 'Non-PCDM objects should should NOT have related objects' do
499
+ expect{ @non_PCDM_object.related_objects }.to raise_error(NoMethodError)
500
+ end
501
+
502
+ it 'AF::Base should NOT have related_objects' do
503
+ expect{ @af_base_object.related_objects }.to raise_error(NoMethodError)
504
+ end
505
+ end
506
+ end
507
+ end
508
+
509
+ describe "remove related objects" do
510
+ context 'when it is the only related object' do
511
+ let(:object3) { Hydra::PCDM::Object.new }
512
+
513
+ before do
514
+ subject.related_objects << object1
515
+ expect( subject.related_objects ).to eq [object1]
516
+ end
517
+
518
+ it 'should remove related object while changes are in memory' do
519
+ expect( subject.related_objects.delete object1 ).to eq [object1]
520
+ expect( subject.related_objects ).to eq []
521
+ end
522
+
523
+ it 'should remove related object only when objects & collections and all changes are in memory' do
524
+ subject.child_collections << collection1
525
+ subject.child_collections << collection2
526
+ subject.child_objects << object3
527
+ subject.child_objects << object2
528
+ expect( subject.related_objects.delete object1 ).to eq [object1]
529
+ expect( subject.related_objects ).to eq []
530
+ expect( subject.child_collections ).to eq [collection1,collection2]
531
+ expect( subject.child_objects ).to eq [object3,object2]
532
+ end
533
+ end
534
+
535
+ context 'when multiple related objects' do
536
+ let(:object3) { Hydra::PCDM::Object.new }
537
+
538
+ before do
539
+ subject.related_objects << object1
540
+ subject.related_objects << object2
541
+ subject.related_objects << object3
542
+ expect( subject.related_objects ).to eq [object1,object2,object3]
543
+ end
544
+
545
+ it 'should remove first related object when changes are in memory' do
546
+ expect( subject.related_objects.delete object1 ).to eq [object1]
547
+ expect( subject.related_objects ).to eq [object2,object3]
548
+ end
549
+
550
+ it 'should remove last related object when changes are in memory' do
551
+ expect( subject.related_objects.delete object3 ).to eq [object3]
552
+ expect( subject.related_objects ).to eq [object1,object2]
553
+ end
554
+
555
+ it 'should remove middle related object when changes are in memory' do
556
+ expect( subject.related_objects.delete object2 ).to eq [object2]
557
+ expect( subject.related_objects ).to eq [object1,object3]
558
+ end
559
+
560
+ it 'should remove middle related object when changes are saved' do
561
+ expect( subject.related_objects ).to eq [object1,object2,object3]
562
+ expect( subject.related_objects.delete object2 ).to eq [object2]
563
+ subject.save
564
+ expect( subject.reload.related_objects ).to eq [object1,object3]
565
+ end
566
+ end
567
+
568
+ context 'when related object is missing' do
569
+ let(:object3) { Hydra::PCDM::Object.new }
570
+
571
+ it 'should return empty array when 0 related objects and 0 collections and objects' do
572
+ expect( subject.related_objects.delete object1 ).to eq []
573
+ end
574
+
575
+ it 'should return empty array when 0 related objects, but has collections and objects and changes in memory' do
576
+ subject.members << collection1
577
+ subject.members << collection2
578
+ subject.members << object1
579
+ subject.members << object2
580
+ expect( subject.related_objects.delete object1 ).to eq []
581
+ end
582
+
583
+ it 'should return empty array when other related objects and changes are in memory' do
584
+ subject.related_objects << object1
585
+ subject.related_objects << object3
586
+ expect( subject.related_objects.delete object2 ).to eq []
587
+ end
588
+
589
+ it 'should return empty array when changes are saved' do
590
+ subject.related_objects << object1
591
+ subject.related_objects << object3
592
+ subject.save
593
+ expect( subject.reload.related_objects.delete object2 ).to eq []
594
+ end
595
+ end
596
+ end
597
+
598
+ context 'with unacceptable inputs' do
599
+ before(:all) do
600
+ @file101 = Hydra::PCDM::File.new
601
+ @non_PCDM_object = "I'm not a PCDM object"
602
+ @af_base_object = ActiveFedora::Base.new
603
+ end
604
+
605
+ context 'that are unacceptable parent collections' do
606
+ it 'should NOT accept Hydra::PCDM::Files as parent collection' do
607
+ expect{ @file101.related_objects.delete object1 }.to raise_error(NoMethodError)
608
+ end
609
+
610
+ it 'should NOT accept non-PCDM objects as parent collection' do
611
+ expect{ @non_PCDM_object.related_objects.delete object1 }.to raise_error(NoMethodError)
612
+ end
613
+
614
+ it 'should NOT accept AF::Base objects as parent collection' do
615
+ expect{ @af_base_object.related_objects.delete object1 }.to raise_error(NoMethodError)
616
+ end
617
+ end
618
+ end
11
619
 
12
620
  describe '#child_collections=' do
13
621
  it 'should aggregate collections' do
14
622
  collection1.child_collections = [collection2, collection3]
15
- collection1.save
16
623
  expect(collection1.child_collections).to eq [collection2, collection3]
17
624
  end
18
625
  end
19
626
 
20
- describe '#objects=' do
627
+ describe '#child_objects=' do
21
628
  it 'should aggregate objects' do
22
- collection1.objects = [object1,object2]
23
- collection1.save
24
- expect(collection1.objects).to eq [object1,object2]
629
+ collection1.child_objects = [object1,object2]
630
+ expect(collection1.child_objects).to eq [object1,object2]
631
+ end
632
+ end
633
+
634
+ describe "#child_collection_ids" do
635
+ let(:child1) { described_class.new(id: '1') }
636
+ let(:child2) { described_class.new(id: '2') }
637
+ let(:object) { described_class.new }
638
+ before { object.child_collections = [child1, child2] }
639
+
640
+ subject { object.child_collection_ids }
641
+
642
+ it { is_expected.to eq ["1", "2"] }
643
+ end
644
+
645
+ describe 'child_collections and child_objects' do
646
+ subject { Hydra::PCDM::Collection.new }
647
+
648
+ it 'should return empty array when no members' do
649
+ expect( subject.child_collections ).to eq []
650
+ expect( subject.child_objects ).to eq []
651
+ end
652
+
653
+ it 'child_collections should return empty array when only objects are aggregated' do
654
+ subject.members << object1
655
+ subject.members << object2
656
+ expect( subject.child_collections ).to eq []
657
+ end
658
+
659
+ it 'child_objects should return empty array when only collections are aggregated' do
660
+ subject.members << collection1
661
+ subject.members << collection2
662
+ expect( subject.child_objects ).to eq []
663
+ end
664
+
665
+ context 'should only contain members of the correct type' do
666
+ it 'should only return collections' do
667
+ subject.child_collections << collection1
668
+ subject.members << collection2
669
+ subject.child_objects << object1
670
+ subject.members << object2
671
+ expect( subject.child_collections ).to eq [collection1,collection2]
672
+ expect( subject.child_objects ).to eq [object1,object2]
673
+ expect( subject.members ).to eq [collection1,collection2,object1,object2]
674
+ end
25
675
  end
26
676
  end
27
677
 
28
- describe 'Related objects' do
29
- let(:object1) { Hydra::PCDM::Object.create }
30
- let(:collection1) { Hydra::PCDM::Collection.create }
678
+ context 'when aggregated by other objects' do
31
679
 
32
680
  before do
33
- collection1.related_objects = [object1]
34
- collection1.save
681
+ # Using before(:all) and instance variable because regular :let syntax had a significant impact on performance
682
+ # All of the tests in this context are describing idempotent behavior, so isolation between examples isn't necessary.
683
+ @collection1 = Hydra::PCDM::Collection.new
684
+ @collection2 = Hydra::PCDM::Collection.new
685
+ @collection = Hydra::PCDM::Collection.new
686
+ @collection1.child_collections << @collection
687
+ @collection2.child_collections << @collection
688
+ allow(@collection).to receive(:id).and_return("banana")
689
+ proxies = [
690
+ build_proxy(container: @collection1),
691
+ build_proxy(container: @collection2),
692
+ ]
693
+ allow(ActiveFedora::Aggregation::Proxy).to receive(:where).with(proxyFor_ssim: @collection.id).and_return(proxies)
35
694
  end
36
695
 
37
- it 'persists' do
38
- expect(collection1.reload.related_objects).to eq [object1]
696
+ describe 'parents' do
697
+ subject { @collection.parents }
698
+ it "finds all nodes that aggregate the object with hasMember" do
699
+ expect(subject).to include(@collection1, @collection2)
700
+ expect(subject.count).to eq 2
701
+ end
702
+ end
703
+
704
+ describe 'parent_collections' do
705
+ subject { @collection.parent_collections }
706
+ it "finds collections that aggregate the object with hasMember" do
707
+ expect(subject).to include(@collection1, @collection2)
708
+ expect(subject.count).to eq 2
709
+ end
710
+ end
711
+ def build_proxy(container:)
712
+ instance_double(ActiveFedora::Aggregation::Proxy, container: container)
39
713
  end
40
714
  end
41
715