active-fedora 9.10.4 → 9.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/History.txt +0 -13
  4. data/lib/active_fedora.rb +7 -0
  5. data/lib/active_fedora/aggregation.rb +11 -0
  6. data/lib/active_fedora/aggregation/base_extension.rb +17 -0
  7. data/lib/active_fedora/aggregation/list_source.rb +103 -0
  8. data/lib/active_fedora/aggregation/ordered_reader.rb +27 -0
  9. data/lib/active_fedora/aggregation/proxy.rb +18 -0
  10. data/lib/active_fedora/associations.rb +40 -0
  11. data/lib/active_fedora/associations/builder/aggregation.rb +51 -0
  12. data/lib/active_fedora/associations/builder/filter.rb +18 -0
  13. data/lib/active_fedora/associations/builder/orders.rb +63 -0
  14. data/lib/active_fedora/associations/filter_association.rb +71 -0
  15. data/lib/active_fedora/associations/orders_association.rb +149 -0
  16. data/lib/active_fedora/attached_files.rb +2 -1
  17. data/lib/active_fedora/attribute_methods.rb +4 -4
  18. data/lib/active_fedora/autosave_association.rb +14 -0
  19. data/lib/active_fedora/base.rb +1 -0
  20. data/lib/active_fedora/cleaner.rb +1 -1
  21. data/lib/active_fedora/errors.rb +3 -0
  22. data/lib/active_fedora/fedora.rb +17 -7
  23. data/lib/active_fedora/file/streaming.rb +13 -4
  24. data/lib/active_fedora/orders.rb +12 -0
  25. data/lib/active_fedora/orders/collection_proxy.rb +8 -0
  26. data/lib/active_fedora/orders/list_node.rb +161 -0
  27. data/lib/active_fedora/orders/ordered_list.rb +264 -0
  28. data/lib/active_fedora/orders/target_proxy.rb +60 -0
  29. data/lib/active_fedora/reflection.rb +215 -42
  30. data/lib/active_fedora/validations.rb +1 -1
  31. data/lib/active_fedora/version.rb +1 -1
  32. data/lib/generators/active_fedora/config/solr/templates/solr/config/schema.xml +1 -1
  33. data/solr/config/schema.xml +1 -1
  34. data/spec/integration/attributes_spec.rb +8 -0
  35. data/spec/integration/file_spec.rb +22 -0
  36. data/spec/integration/has_many_associations_spec.rb +23 -0
  37. data/spec/integration/versionable_spec.rb +6 -6
  38. data/spec/unit/active_fedora_spec.rb +1 -1
  39. data/spec/unit/aggregation/list_source_spec.rb +134 -0
  40. data/spec/unit/aggregation/ordered_reader_spec.rb +43 -0
  41. data/spec/unit/fedora_spec.rb +1 -1
  42. data/spec/unit/filter_spec.rb +133 -0
  43. data/spec/unit/ordered_spec.rb +369 -0
  44. data/spec/unit/orders/list_node_spec.rb +151 -0
  45. data/spec/unit/orders/ordered_list_spec.rb +335 -0
  46. data/spec/unit/orders/reflection_spec.rb +22 -0
  47. data/spec/unit/reflection_spec.rb +2 -4
  48. metadata +25 -3
@@ -9,7 +9,7 @@ module ActiveFedora
9
9
  # rescue ActiveFedora::RecordInvalid => invalid
10
10
  # puts invalid.record.errors
11
11
  # end
12
- class RecordInvalid < StandardError
12
+ class RecordInvalid < ActiveFedoraError
13
13
  attr_reader :record
14
14
  def initialize(record)
15
15
  @record = record
@@ -1,3 +1,3 @@
1
1
  module ActiveFedora
2
- VERSION = "9.10.4".freeze
2
+ VERSION = "9.11.0".freeze
3
3
  end
@@ -103,7 +103,7 @@
103
103
  http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
104
104
  -->
105
105
  <fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
106
- geo="true" distErrPct="0.025" maxDistErr="0.000009" units="degrees" />
106
+ geo="true" distErrPct="0.025" maxDistErr="0.000009" distanceUnits="degrees" />
107
107
 
108
108
  <fieldType name="text" class="solr.TextField" omitNorms="false">
109
109
  <analyzer>
@@ -103,7 +103,7 @@
103
103
  http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
104
104
  -->
105
105
  <fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
106
- geo="true" distErrPct="0.025" maxDistErr="0.000009" units="degrees" />
106
+ geo="true" distErrPct="0.025" maxDistErr="0.000009" distanceUnits="degrees" />
107
107
 
108
108
  <fieldType name="text" class="solr.TextField" omitNorms="false">
109
109
  <analyzer>
@@ -147,4 +147,12 @@ describe "delegating attributes" do
147
147
  end
148
148
  end
149
149
  end
150
+
151
+ describe 'dangerous attributes' do
152
+ it 'raises an exception if a dangerous attribute is defined' do
153
+ Deprecation.silence(ActiveFedora::Attributes) do
154
+ expect { TitledObject.has_attributes :save, datastream: 'foo', multiple: false }.to raise_error ActiveFedora::DangerousAttributeError
155
+ end
156
+ end
157
+ end
150
158
  end
@@ -149,6 +149,28 @@ describe ActiveFedora::File do
149
149
  it { should eq 'two2thre' }
150
150
  end
151
151
  end
152
+
153
+ context "when the request results in a redirect" do
154
+ before do
155
+ test_object.add_file('one1two2threfour', path: 'webm', mime_type: 'video/webm')
156
+ test_object.add_file('', path: 'redirector', mime_type: "message/external-body; access-type=URL; url=\"#{test_object.webm.uri}\"")
157
+ test_object.save!
158
+ end
159
+ subject { str = ''; test_object.redirector.stream.each { |chunk| str << chunk }; str }
160
+ it { should eq 'one1two2threfour' }
161
+ end
162
+
163
+ context "when there are more than 3 requests because of redirects" do
164
+ before do
165
+ test_object.add_file('', path: 'one', mime_type: "message/external-body; access-type=URL; url=\"#{test_object.attached_files[path].uri}\"")
166
+ test_object.add_file('', path: 'two', mime_type: "message/external-body; access-type=URL; url=\"#{test_object.one.uri}\"")
167
+ test_object.add_file('', path: 'three', mime_type: "message/external-body; access-type=URL; url=\"#{test_object.two.uri}\"")
168
+ test_object.save!
169
+ end
170
+ it "raises a HTTP redirect too deep Error" do
171
+ expect { test_object.three.stream.each { |chunk| chunk } }.to raise_error('HTTP redirect too deep')
172
+ end
173
+ end
152
174
  end
153
175
  end
154
176
  end
@@ -423,4 +423,27 @@ describe ActiveFedora::Associations::HasManyAssociation do
423
423
  end
424
424
  end
425
425
  end
426
+
427
+ describe "when destroying the owner" do
428
+ before do
429
+ class Book < ActiveFedora::Base
430
+ has_many :permissions, dependent: :destroy
431
+ end
432
+
433
+ class Permission < ActiveFedora::Base
434
+ belongs_to :book, predicate: ActiveFedora::RDF::Fcrepo::RelsExt.hasConstituent
435
+ end
436
+ end
437
+ let(:book) { Book.create! }
438
+ let!(:permissions) { Permission.create!(book: book) }
439
+
440
+ after do
441
+ Object.send(:remove_const, :Book)
442
+ Object.send(:remove_const, :Permission)
443
+ end
444
+
445
+ it "the object is destroyed" do
446
+ expect { book.destroy }.to change { Permission.count }.by(-1)
447
+ end
448
+ end
426
449
  end
@@ -179,8 +179,8 @@ describe ActiveFedora::Versionable do
179
179
  @original_size = subject.size
180
180
  end
181
181
 
182
- it "sets model_type to versionable" do
183
- expect(subject.model_type).to include RDF::URI.new('http://www.jcp.org/jcr/mix/1.0versionable')
182
+ it "links to versions endpoint" do
183
+ expect(subject.metadata.ldp_source.graph.query(predicate: ::RDF::Vocab::Fcrepo4.hasVersions).objects).to_not be_empty
184
184
  end
185
185
 
186
186
  it "has one version" do
@@ -305,8 +305,8 @@ describe ActiveFedora::Versionable do
305
305
  @original_size = subject.size
306
306
  end
307
307
 
308
- it "sets model_type to versionable" do
309
- expect(subject.model_type).to include RDF::URI.new('http://www.jcp.org/jcr/mix/1.0versionable')
308
+ it "links to versions endpoint" do
309
+ expect(subject.metadata.ldp_source.graph.query(predicate: ::RDF::Vocab::Fcrepo4.hasVersions).objects).to_not be_empty
310
310
  end
311
311
 
312
312
  it "has one version" do
@@ -427,8 +427,8 @@ describe ActiveFedora::Versionable do
427
427
  subject.create_version
428
428
  end
429
429
 
430
- it "sets model_type to versionable" do
431
- expect(subject.model_type).to include RDF::URI.new('http://www.jcp.org/jcr/mix/1.0versionable')
430
+ it "links to versions endpoint" do
431
+ expect(subject.metadata.ldp_source.graph.query(predicate: ::RDF::Vocab::Fcrepo4.hasVersions).objects).to_not be_empty
432
432
  end
433
433
 
434
434
  it "has one version" do
@@ -28,7 +28,7 @@ describe ActiveFedora do
28
28
  it "does not connect and warn" do
29
29
  expect(ActiveFedora::Base.logger).to receive(:warn)
30
30
  expect {
31
- ActiveFedora::Fedora.new(url: bad_url, base_path: '/test', user: user, password: password)
31
+ ActiveFedora::Fedora.new(url: bad_url, base_path: '/test', user: user, password: password).connection
32
32
  }.to raise_error Ldp::HttpError
33
33
  end
34
34
  end
@@ -0,0 +1,134 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ActiveFedora::Aggregation::ListSource do
4
+ subject { described_class.new }
5
+
6
+ describe "#head" do
7
+ it "is nil by default" do
8
+ expect(subject.head).to eq nil
9
+ end
10
+
11
+ it "is settable" do
12
+ subject.head = RDF::URI("test.org")
13
+
14
+ expect(subject.head_id.first).to eq RDF::URI("test.org")
15
+ end
16
+
17
+ it "maps to IANA.first" do
18
+ expect(subject.class.properties["head"].predicate).to eq ::RDF::Vocab::IANA["first"]
19
+ end
20
+ end
21
+
22
+ describe "#order_will_change!" do
23
+ it "marks it as changed" do
24
+ expect(subject).not_to be_changed
25
+ subject.order_will_change!
26
+ expect(subject).to be_changed
27
+ expect(subject.ordered_self).to be_changed
28
+ end
29
+ end
30
+
31
+ describe "#tail" do
32
+ it "is nil by default" do
33
+ expect(subject.tail).to eq nil
34
+ end
35
+
36
+ it "is settable" do
37
+ subject.tail = RDF::URI("test.org")
38
+
39
+ expect(subject.tail_id.first).to eq RDF::URI("test.org")
40
+ end
41
+
42
+ it "maps to IANA.last" do
43
+ expect(subject.class.properties["tail"].predicate).to eq ::RDF::Vocab::IANA["last"]
44
+ end
45
+ end
46
+
47
+ describe "#changed?" do
48
+ context "when nothing has changed" do
49
+ it "is false" do
50
+ expect(subject).not_to be_changed
51
+ end
52
+ end
53
+ context "when the ordered list is changed" do
54
+ it "is true" do
55
+ allow(subject.ordered_self).to receive(:changed?).and_return(true)
56
+
57
+ expect(subject).to be_changed
58
+ end
59
+ end
60
+ context "when the ordered list is not changed" do
61
+ it "is false" do
62
+ allow(subject.ordered_self).to receive(:changed?).and_return(false)
63
+
64
+ expect(subject).not_to be_changed
65
+ end
66
+ end
67
+ end
68
+
69
+ describe "#save" do
70
+ context "when nothing has changed" do
71
+ it "does not persist ordered_self" do
72
+ allow(subject.ordered_self).to receive(:to_graph)
73
+
74
+ subject.save
75
+
76
+ expect(subject.ordered_self).not_to have_received(:to_graph)
77
+ end
78
+ end
79
+ context "when attributes have changed, but not ordered list" do
80
+ it "does not persist ordered self" do
81
+ allow(subject.ordered_self).to receive(:to_graph)
82
+ subject.nodes += [RDF::URI("http://test.org")]
83
+
84
+ subject.save
85
+
86
+ expect(subject.ordered_self).not_to have_received(:to_graph)
87
+ end
88
+ end
89
+ context "when ordered list has changed" do
90
+ it "persists it" do
91
+ allow(subject.ordered_self).to receive(:to_graph).and_call_original
92
+ allow(subject.ordered_self).to receive(:changed?).and_return(true)
93
+
94
+ subject.save
95
+
96
+ expect(subject.ordered_self).to have_received(:to_graph)
97
+ end
98
+ end
99
+ end
100
+
101
+ describe "#serializable_hash" do
102
+ it "does not serialize nodes" do
103
+ subject.nodes += [RDF::URI("http://test.org")]
104
+
105
+ expect(subject.serializable_hash).not_to have_key "nodes"
106
+ end
107
+ it "does not serialize head" do
108
+ subject.head = RDF::URI("http://test.org")
109
+
110
+ expect(subject.serializable_hash).not_to have_key "head"
111
+ end
112
+ it "does not serialize tail" do
113
+ subject.tail = RDF::URI("http://test.org")
114
+
115
+ expect(subject.serializable_hash).not_to have_key "tail"
116
+ end
117
+ end
118
+
119
+ describe "#to_solr" do
120
+ before do
121
+ class Member < ActiveFedora::Base
122
+ end
123
+ end
124
+ after do
125
+ Object.send(:remove_const, :Member)
126
+ end
127
+ it "can index" do
128
+ m = Member.create
129
+ proxy_in = RDF::URI(ActiveFedora::Base.translate_id_to_uri.call("banana"))
130
+ subject.ordered_self.append_target m, proxy_in: proxy_in
131
+ expect(subject.to_solr).to include ordered_targets_ssim: [m.id], proxy_in_ssi: "banana"
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ActiveFedora::Aggregation::OrderedReader do
4
+ subject { described_class.new(root) }
5
+ let(:root) { instance_double(ActiveFedora::Aggregation::ListSource) }
6
+
7
+ describe "#each" do
8
+ it "iterates a linked list" do
9
+ head = build_node
10
+ tail = build_node(prev_node: head)
11
+ allow(head).to receive(:next).and_return(tail)
12
+ allow(root).to receive(:head).and_return(head)
13
+ expect(subject.to_a).to eq [head, tail]
14
+ end
15
+ it "only goes as deep as necessary" do
16
+ head = build_node
17
+ tail = build_node(prev_node: head)
18
+ allow(head).to receive(:next).and_return(tail)
19
+ allow(root).to receive(:head).and_return(head)
20
+ expect(subject.first).to eq head
21
+ expect(head).not_to have_received(:next)
22
+ end
23
+ context "when the prev is wrong" do
24
+ it "fixes it up" do
25
+ head = build_node
26
+ bad_node = build_node
27
+ tail = build_node(prev_node: bad_node)
28
+ allow(head).to receive(:next).and_return(tail)
29
+ allow(root).to receive(:head).and_return(head)
30
+ allow(tail).to receive(:prev=)
31
+ expect(subject.to_a).to eq [head, tail]
32
+ expect(tail).to have_received(:prev=).with(head)
33
+ end
34
+ end
35
+ end
36
+
37
+ def build_node(prev_node: nil, next_node: nil)
38
+ node = instance_double(ActiveFedora::Orders::ListNode)
39
+ allow(node).to receive(:next).and_return(next_node)
40
+ allow(node).to receive(:prev).and_return(prev_node)
41
+ node
42
+ end
43
+ end
@@ -12,7 +12,7 @@ describe ActiveFedora::Fedora do
12
12
  }
13
13
  }
14
14
  specify {
15
- expect(Faraday).to receive(:new).with("https://example.com", ssl: { ca_path: '/path/to/certs' }).twice.and_call_original
15
+ expect(Faraday).to receive(:new).with("https://example.com", ssl: { ca_path: '/path/to/certs' }).and_call_original
16
16
  subject.authorized_connection
17
17
  }
18
18
  end
@@ -0,0 +1,133 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveFedora::Associations::FilterAssociation do
4
+ before do
5
+ class Image < ActiveFedora::Base
6
+ ordered_aggregation :members, through: :list_source, class_name: 'ActiveFedora::Base'
7
+
8
+ filters_association :members, as: :child_objects, condition: :pcdm_object?
9
+ filters_association :members, as: :child_collections, condition: :pcdm_collection?
10
+ end
11
+
12
+ class TestObject < ActiveFedora::Base
13
+ def pcdm_object?
14
+ true
15
+ end
16
+
17
+ def pcdm_collection?
18
+ false
19
+ end
20
+ end
21
+
22
+ class TestCollection < ActiveFedora::Base
23
+ def pcdm_object?
24
+ false
25
+ end
26
+
27
+ def pcdm_collection?
28
+ true
29
+ end
30
+ end
31
+ end
32
+
33
+ after do
34
+ Object.send(:remove_const, :Image)
35
+ Object.send(:remove_const, :TestObject)
36
+ Object.send(:remove_const, :TestCollection)
37
+ end
38
+
39
+ let(:image) { Image.new }
40
+ let(:test_object) { TestObject.new }
41
+ let(:test_collection) { TestCollection.new }
42
+
43
+ describe "setting" do
44
+ context "when an incorrect object type is sent" do
45
+ it "raises an error" do
46
+ image.child_collections
47
+ expect { image.child_collections = [test_object] }.to raise_error ArgumentError
48
+ end
49
+ end
50
+
51
+ context "when the parent is already loaded" do
52
+ let(:another_collection) { TestCollection.new }
53
+ before do
54
+ image.members = [test_object, test_collection]
55
+ image.child_collections = [another_collection]
56
+ end
57
+ it "overwrites existing matches" do
58
+ expect(image.members).to eq [test_object, another_collection]
59
+ end
60
+ end
61
+ end
62
+
63
+ describe "appending" do
64
+ context "when an incorrect object type is sent" do
65
+ it "raises an error" do
66
+ expect { image.child_collections << test_object }.to raise_error ArgumentError
67
+ end
68
+ end
69
+
70
+ context "when the parent is already loaded" do
71
+ let(:another_collection) { TestCollection.new }
72
+ before do
73
+ image.members = [test_object, test_collection]
74
+ image.child_collections << [another_collection]
75
+ end
76
+
77
+ it "updates the parent" do
78
+ expect(image.members).to eq [test_object, test_collection, another_collection]
79
+ end
80
+ end
81
+ end
82
+
83
+ describe "#size" do
84
+ it "returns the size" do
85
+ # Need to persist so that count_records will be called.
86
+ image.save
87
+ test_object.save
88
+ image.members = [test_object]
89
+
90
+ expect(image.reload.child_objects.size).to eq 1
91
+ end
92
+ end
93
+
94
+ describe "reading" do
95
+ before do
96
+ image.members = [test_object, test_collection]
97
+ end
98
+
99
+ it "returns the objects of the correct type" do
100
+ expect(image.child_objects).to eq [test_object]
101
+ end
102
+
103
+ describe "when the parent association is changed" do
104
+ before do
105
+ image.child_objects = [test_object]
106
+ image.child_objects.to_a # this would cause the @target of the association to be populated
107
+ image.members = [test_collection]
108
+ end
109
+
110
+ it "updates the filtered relation" do
111
+ expect(image.child_objects).to eq []
112
+ end
113
+ end
114
+
115
+ describe "#_ids" do
116
+ it "returns just the ids" do
117
+ expect(image.child_object_ids).to eq [test_object.id]
118
+ end
119
+ end
120
+ end
121
+
122
+ describe "#delete" do
123
+ let(:another_object) { TestObject.new }
124
+ before do
125
+ image.members = [test_object, test_collection, another_object]
126
+ image.child_objects.delete(test_object)
127
+ end
128
+
129
+ subject { image.members }
130
+
131
+ it { is_expected.to eq [test_collection, another_object] }
132
+ end
133
+ end