active-fedora 9.10.4 → 9.11.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 (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