valkyrie 0.0.0 → 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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -3
  3. data/.rubocop.yml +28 -0
  4. data/Gemfile +6 -1
  5. data/README.md +145 -10
  6. data/Rakefile +59 -1
  7. data/bin/console +1 -1
  8. data/config/valkyrie.yml +8 -0
  9. data/db/config.yml +17 -0
  10. data/db/migrate/20160111215816_enable_uuid_extension.rb +6 -0
  11. data/db/migrate/20161007101725_create_orm_resources.rb +10 -0
  12. data/db/migrate/20170124135846_add_model_type_to_orm_resources.rb +6 -0
  13. data/db/migrate/20170531004548_change_model_type_to_internal_model.rb +6 -0
  14. data/db/schema.rb +65 -0
  15. data/db/seeds.rb +8 -0
  16. data/lib/config/database_connection.rb +15 -0
  17. data/lib/generators/valkyrie/resource_generator.rb +27 -0
  18. data/lib/generators/valkyrie/templates/resource.rb.erb +9 -0
  19. data/lib/generators/valkyrie/templates/resource_spec.rb.erb +13 -0
  20. data/lib/valkyrie.rb +76 -1
  21. data/lib/valkyrie/adapter_container.rb +12 -0
  22. data/lib/valkyrie/change_set.rb +84 -0
  23. data/lib/valkyrie/decorators/decorator_list.rb +15 -0
  24. data/lib/valkyrie/decorators/decorator_with_arguments.rb +14 -0
  25. data/lib/valkyrie/derivative_service.rb +42 -0
  26. data/lib/valkyrie/engine.rb +10 -0
  27. data/lib/valkyrie/file_characterization_service.rb +42 -0
  28. data/lib/valkyrie/id.rb +32 -0
  29. data/lib/valkyrie/indexers/access_controls_indexer.rb +19 -0
  30. data/lib/valkyrie/local_file_service.rb +11 -0
  31. data/lib/valkyrie/metadata_adapter.rb +22 -0
  32. data/lib/valkyrie/persist_derivatives.rb +29 -0
  33. data/lib/valkyrie/persistence.rb +14 -0
  34. data/lib/valkyrie/persistence/buffered_persister.rb +28 -0
  35. data/lib/valkyrie/persistence/composite_persister.rb +29 -0
  36. data/lib/valkyrie/persistence/delete_tracking_buffer.rb +21 -0
  37. data/lib/valkyrie/persistence/fedora.rb +11 -0
  38. data/lib/valkyrie/persistence/fedora/list_node.rb +88 -0
  39. data/lib/valkyrie/persistence/fedora/metadata_adapter.rb +45 -0
  40. data/lib/valkyrie/persistence/fedora/ordered_list.rb +146 -0
  41. data/lib/valkyrie/persistence/fedora/ordered_reader.rb +28 -0
  42. data/lib/valkyrie/persistence/fedora/persister.rb +47 -0
  43. data/lib/valkyrie/persistence/fedora/persister/model_converter.rb +199 -0
  44. data/lib/valkyrie/persistence/fedora/persister/orm_converter.rb +338 -0
  45. data/lib/valkyrie/persistence/fedora/persister/resource_factory.rb +21 -0
  46. data/lib/valkyrie/persistence/fedora/query_service.rb +80 -0
  47. data/lib/valkyrie/persistence/memory.rb +8 -0
  48. data/lib/valkyrie/persistence/memory/metadata_adapter.rb +22 -0
  49. data/lib/valkyrie/persistence/memory/persister.rb +58 -0
  50. data/lib/valkyrie/persistence/memory/query_service.rb +86 -0
  51. data/lib/valkyrie/persistence/postgres.rb +6 -0
  52. data/lib/valkyrie/persistence/postgres/metadata_adapter.rb +20 -0
  53. data/lib/valkyrie/persistence/postgres/orm.rb +9 -0
  54. data/lib/valkyrie/persistence/postgres/orm/resource.rb +7 -0
  55. data/lib/valkyrie/persistence/postgres/orm_converter.rb +118 -0
  56. data/lib/valkyrie/persistence/postgres/persister.rb +33 -0
  57. data/lib/valkyrie/persistence/postgres/queries.rb +8 -0
  58. data/lib/valkyrie/persistence/postgres/queries/find_inverse_references_query.rb +31 -0
  59. data/lib/valkyrie/persistence/postgres/queries/find_members_query.rb +33 -0
  60. data/lib/valkyrie/persistence/postgres/queries/find_references_query.rb +33 -0
  61. data/lib/valkyrie/persistence/postgres/query_service.rb +53 -0
  62. data/lib/valkyrie/persistence/postgres/resource_converter.rb +18 -0
  63. data/lib/valkyrie/persistence/postgres/resource_factory.rb +30 -0
  64. data/lib/valkyrie/persistence/solr.rb +6 -0
  65. data/lib/valkyrie/persistence/solr/metadata_adapter.rb +42 -0
  66. data/lib/valkyrie/persistence/solr/model_converter.rb +270 -0
  67. data/lib/valkyrie/persistence/solr/orm_converter.rb +252 -0
  68. data/lib/valkyrie/persistence/solr/persister.rb +32 -0
  69. data/lib/valkyrie/persistence/solr/queries.rb +11 -0
  70. data/lib/valkyrie/persistence/solr/queries/default_paginator.rb +16 -0
  71. data/lib/valkyrie/persistence/solr/queries/find_all_query.rb +33 -0
  72. data/lib/valkyrie/persistence/solr/queries/find_by_id_query.rb +24 -0
  73. data/lib/valkyrie/persistence/solr/queries/find_inverse_references_query.rb +30 -0
  74. data/lib/valkyrie/persistence/solr/queries/find_members_query.rb +43 -0
  75. data/lib/valkyrie/persistence/solr/queries/find_references_query.rb +34 -0
  76. data/lib/valkyrie/persistence/solr/query_service.rb +48 -0
  77. data/lib/valkyrie/persistence/solr/repository.rb +36 -0
  78. data/lib/valkyrie/persistence/solr/resource_factory.rb +24 -0
  79. data/lib/valkyrie/rdf_patches.rb +17 -0
  80. data/lib/valkyrie/resource.rb +106 -0
  81. data/lib/valkyrie/resource/access_controls.rb +13 -0
  82. data/lib/valkyrie/specs/shared_specs.rb +10 -0
  83. data/lib/valkyrie/specs/shared_specs/change_set_persister.rb +60 -0
  84. data/lib/valkyrie/specs/shared_specs/derivative_service.rb +30 -0
  85. data/lib/valkyrie/specs/shared_specs/file.rb +12 -0
  86. data/lib/valkyrie/specs/shared_specs/file_characterization_service.rb +33 -0
  87. data/lib/valkyrie/specs/shared_specs/metadata_adapter.rb +10 -0
  88. data/lib/valkyrie/specs/shared_specs/persister.rb +154 -0
  89. data/lib/valkyrie/specs/shared_specs/queries.rb +128 -0
  90. data/lib/valkyrie/specs/shared_specs/resource.rb +71 -0
  91. data/lib/valkyrie/specs/shared_specs/storage_adapter.rb +44 -0
  92. data/lib/valkyrie/storage.rb +8 -0
  93. data/lib/valkyrie/storage/disk.rb +55 -0
  94. data/lib/valkyrie/storage/fedora.rb +71 -0
  95. data/lib/valkyrie/storage/memory.rb +31 -0
  96. data/lib/valkyrie/storage_adapter.rb +100 -0
  97. data/lib/valkyrie/types.rb +34 -0
  98. data/lib/valkyrie/value_mapper.rb +67 -0
  99. data/lib/valkyrie/version.rb +2 -1
  100. data/lib/valkyrie/vocab/pcdm_use.rb +73 -0
  101. data/valkyrie.gemspec +33 -7
  102. metadata +462 -7
  103. data/.travis.yml +0 -5
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+ RSpec.shared_examples 'a Valkyrie::Persister' do |*flags|
3
+ before do
4
+ raise 'persister must be set with `let(:persister)`' unless defined? persister
5
+ class CustomResource < Valkyrie::Resource
6
+ include Valkyrie::Resource::AccessControls
7
+ attribute :id, Valkyrie::Types::ID.optional
8
+ attribute :title
9
+ attribute :author
10
+ attribute :member_ids
11
+ attribute :nested_resource
12
+ end
13
+ end
14
+ after do
15
+ Object.send(:remove_const, :CustomResource)
16
+ end
17
+
18
+ subject { persister }
19
+ let(:resource_class) { CustomResource }
20
+ let(:resource) { resource_class.new }
21
+
22
+ it { is_expected.to respond_to(:save).with_keywords(:resource) }
23
+ it { is_expected.to respond_to(:save_all).with_keywords(:resources) }
24
+ it { is_expected.to respond_to(:delete).with_keywords(:resource) }
25
+
26
+ it "can save a resource" do
27
+ expect(persister.save(resource: resource).id).not_to be_blank
28
+ end
29
+
30
+ it "can save multiple resources at once" do
31
+ resource2 = resource_class.new
32
+ results = persister.save_all(resources: [resource, resource2])
33
+
34
+ expect(results.map(&:id).uniq.length).to eq 2
35
+ end
36
+
37
+ it "can save nested resources" do
38
+ book2 = resource_class.new(title: "Nested")
39
+ book3 = persister.save(resource: resource_class.new(nested_resource: book2))
40
+
41
+ reloaded = query_service.find_by(id: book3.id)
42
+ expect(reloaded.nested_resource.first.title).to eq ["Nested"]
43
+ end
44
+
45
+ it "can mix properties with nested resources" do
46
+ pending "No support for mixed nesting." if flags.include?(:no_mixed_nesting)
47
+ book2 = resource_class.new(title: "Nested", id: SecureRandom.uuid)
48
+ book3 = persister.save(resource: resource_class.new(nested_resource: [book2, "Alabama"]))
49
+
50
+ reloaded = query_service.find_by(id: book3.id)
51
+ expect(reloaded.nested_resource.map { |x| x.try(:title) }).to include ["Nested"]
52
+ expect(reloaded.nested_resource).to include "Alabama"
53
+ end
54
+
55
+ it "can support deep nesting of resources" do
56
+ pending "No support for deep nesting." if flags.include?(:no_deep_nesting)
57
+ book = resource_class.new(title: "Sub-nested", author: [Valkyrie::ID.new("test"), RDF::Literal.new("Test", language: :fr), RDF::URI("http://test.com")])
58
+ book2 = resource_class.new(title: "Nested", nested_resource: book)
59
+ book3 = persister.save(resource: resource_class.new(nested_resource: book2))
60
+
61
+ reloaded = query_service.find_by(id: book3.id)
62
+ expect(reloaded.nested_resource.first.title).to eq ["Nested"]
63
+ expect(reloaded.nested_resource.first.nested_resource.first.title).to eq ["Sub-nested"]
64
+ expect(reloaded.nested_resource.first.nested_resource.first.author).to contain_exactly Valkyrie::ID.new("test"), RDF::Literal.new("Test", language: :fr), RDF::URI("http://test.com")
65
+ end
66
+
67
+ it "stores created_at/updated_at" do
68
+ book = persister.save(resource: resource_class.new)
69
+ book.title = "test"
70
+ book = persister.save(resource: book)
71
+ expect(book.created_at).not_to be_blank
72
+ expect(book.updated_at).not_to be_blank
73
+ expect(book.created_at).not_to be_kind_of Array
74
+ expect(book.updated_at).not_to be_kind_of Array
75
+ end
76
+
77
+ it "can handle language-typed RDF properties" do
78
+ book = persister.save(resource: resource_class.new(title: ["Test1", RDF::Literal.new("Test", language: :fr)]))
79
+ reloaded = query_service.find_by(id: book.id)
80
+ expect(reloaded.title).to contain_exactly "Test1", RDF::Literal.new("Test", language: :fr)
81
+ end
82
+
83
+ it "can store Valkyrie::Ids" do
84
+ shared_title = persister.save(resource: resource_class.new(id: "test"))
85
+ book = persister.save(resource: resource_class.new(title: [shared_title.id, Valkyrie::ID.new("adapter://1"), "test"]))
86
+ reloaded = query_service.find_by(id: book.id)
87
+ expect(reloaded.title).to contain_exactly(shared_title.id, Valkyrie::ID.new("adapter://1"), "test")
88
+ expect([shared_title.id, Valkyrie::ID.new("adapter://1"), "test"]).to contain_exactly(*reloaded.title)
89
+ end
90
+
91
+ it "can store ::RDF::URIs" do
92
+ book = persister.save(resource: resource_class.new(title: [::RDF::URI("http://example.com")]))
93
+ reloaded = query_service.find_by(id: book.id)
94
+ expect(reloaded.title).to contain_exactly RDF::URI("http://example.com")
95
+ end
96
+
97
+ it "can store integers" do
98
+ book = persister.save(resource: resource_class.new(title: [1]))
99
+ reloaded = query_service.find_by(id: book.id)
100
+ expect(reloaded.title).to contain_exactly 1
101
+ end
102
+
103
+ it "can store datetimes" do
104
+ time1 = DateTime.current
105
+ time2 = Time.current.in_time_zone
106
+ book = persister.save(resource: resource_class.new(title: [time1], author: [time2]))
107
+
108
+ reloaded = query_service.find_by(id: book.id)
109
+
110
+ expect(reloaded.title.first.to_i).to eq(time1.to_i)
111
+ expect(reloaded.title.first.zone).to eq('UTC')
112
+ expect(reloaded.author.first.to_i).to eq(time2.to_i)
113
+ expect(reloaded.author.first.zone).to eq('UTC')
114
+ end
115
+
116
+ context "parent tests" do
117
+ let(:book) { persister.save(resource: resource_class.new) }
118
+ let(:book2) { persister.save(resource: resource_class.new) }
119
+
120
+ it "can order members" do
121
+ book3 = persister.save(resource: resource_class.new)
122
+ parent = persister.save(resource: resource_class.new(member_ids: [book2.id, book.id]))
123
+ parent.member_ids = parent.member_ids + [book3.id]
124
+ parent = persister.save(resource: parent)
125
+ reloaded = query_service.find_by(id: parent.id)
126
+ expect(reloaded.member_ids).to eq [book2.id, book.id, book3.id]
127
+ end
128
+
129
+ it "can remove members" do
130
+ parent = persister.save(resource: resource_class.new(member_ids: [book2.id, book.id]))
131
+ parent.member_ids = parent.member_ids - [book2.id]
132
+ parent = persister.save(resource: parent)
133
+ expect(parent.member_ids).to eq [book.id]
134
+ end
135
+ end
136
+
137
+ it "doesn't override a resource that already has an ID" do
138
+ book = persister.save(resource: resource_class.new)
139
+ id = book.id
140
+ output = persister.save(resource: book)
141
+ expect(output.id).to eq id
142
+ end
143
+
144
+ it "can find that resource again" do
145
+ id = persister.save(resource: resource).id
146
+ expect(query_service.find_by(id: id)).to be_kind_of resource_class
147
+ end
148
+
149
+ it "can delete objects" do
150
+ persisted = persister.save(resource: resource)
151
+ persister.delete(resource: persisted)
152
+ expect { query_service.find_by(id: persisted.id) }.to raise_error ::Valkyrie::Persistence::ObjectNotFoundError
153
+ end
154
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+ RSpec.shared_examples 'a Valkyrie query provider' do
3
+ before do
4
+ raise 'adapter must be set with `let(:adapter)`' unless
5
+ defined? adapter
6
+ class CustomResource < Valkyrie::Resource
7
+ attribute :id, Valkyrie::Types::ID.optional
8
+ attribute :title
9
+ attribute :member_ids
10
+ attribute :a_member_of
11
+ end
12
+ class SecondResource < Valkyrie::Resource
13
+ attribute :id, Valkyrie::Types::ID.optional
14
+ end
15
+ end
16
+ after do
17
+ Object.send(:remove_const, :CustomResource)
18
+ Object.send(:remove_const, :SecondResource)
19
+ end
20
+ let(:resource_class) { CustomResource }
21
+ let(:query_service) { adapter.query_service }
22
+ let(:persister) { adapter.persister }
23
+ subject { adapter.query_service }
24
+
25
+ it { is_expected.to respond_to(:find_all).with(0).arguments }
26
+ it { is_expected.to respond_to(:find_all_of_model).with_keywords(:model) }
27
+ it { is_expected.to respond_to(:find_by).with_keywords(:id) }
28
+ it { is_expected.to respond_to(:find_members).with_keywords(:resource) }
29
+ it { is_expected.to respond_to(:find_references_by).with_keywords(:resource, :property) }
30
+ it { is_expected.to respond_to(:find_inverse_references_by).with_keywords(:resource, :property) }
31
+ it { is_expected.to respond_to(:find_parents).with_keywords(:resource) }
32
+
33
+ describe ".find_all" do
34
+ it "returns all created resources" do
35
+ resource1 = persister.save(resource: resource_class.new)
36
+ resource2 = persister.save(resource: resource_class.new)
37
+
38
+ expect(query_service.find_all.map(&:id)).to contain_exactly resource1.id, resource2.id
39
+ end
40
+ end
41
+
42
+ describe ".find_all_of_model" do
43
+ it "returns all of that model" do
44
+ persister.save(resource: resource_class.new)
45
+ resource2 = persister.save(resource: SecondResource.new)
46
+
47
+ expect(query_service.find_all_of_model(model: SecondResource).map(&:id)).to contain_exactly resource2.id
48
+ end
49
+ it "returns an empty array if there are none" do
50
+ expect(query_service.find_all_of_model(model: SecondResource).to_a).to eq []
51
+ end
52
+ end
53
+
54
+ describe ".find_by" do
55
+ it "returns a resource by id" do
56
+ resource = persister.save(resource: resource_class.new)
57
+
58
+ expect(query_service.find_by(id: resource.id).id).to eq resource.id
59
+ end
60
+ it "returns a Valkyrie::Persistence::ObjectNotFoundError for a non-found ID" do
61
+ expect { query_service.find_by(id: "123123123") }.to raise_error ::Valkyrie::Persistence::ObjectNotFoundError
62
+ end
63
+ end
64
+
65
+ describe ".find_members" do
66
+ it "returns all a resource's members in order" do
67
+ child1 = persister.save(resource: resource_class.new)
68
+ child2 = persister.save(resource: resource_class.new)
69
+ parent = persister.save(resource: resource_class.new(member_ids: [child2.id, child1.id]))
70
+
71
+ expect(query_service.find_members(resource: parent).map(&:id).to_a).to eq [child2.id, child1.id]
72
+ expect(query_service.find_by(id: parent.id).member_ids).to eq [child2.id, child1.id]
73
+ end
74
+ it "doesn't error when there's no resource ID" do
75
+ parent = resource_class.new
76
+ expect(query_service.find_members(resource: parent).to_a).to eq []
77
+ end
78
+ it "returns an empty array if there are none" do
79
+ expect(query_service.find_all.to_a).to eq []
80
+ end
81
+ end
82
+
83
+ describe ".find_references_by" do
84
+ it "returns all references given in a property" do
85
+ parent = persister.save(resource: resource_class.new)
86
+ child = persister.save(resource: resource_class.new(a_member_of: [parent.id]))
87
+ persister.save(resource: resource_class.new)
88
+
89
+ expect(query_service.find_references_by(resource: child, property: :a_member_of).map(&:id).to_a).to eq [parent.id]
90
+ end
91
+ it "returns an empty array if there are none" do
92
+ child = persister.save(resource: resource_class.new)
93
+ expect(query_service.find_references_by(resource: child, property: :a_member_of).to_a).to eq []
94
+ end
95
+ end
96
+
97
+ describe ".find_inverse_references_by" do
98
+ it "returns everything which references the given resource by the given property" do
99
+ parent = persister.save(resource: resource_class.new)
100
+ child = persister.save(resource: resource_class.new(a_member_of: [parent.id]))
101
+ persister.save(resource: resource_class.new)
102
+ persister.save(resource: SecondResource.new)
103
+
104
+ expect(query_service.find_inverse_references_by(resource: parent, property: :a_member_of).map(&:id).to_a).to eq [child.id]
105
+ end
106
+ it "returns an empty array if there are none" do
107
+ parent = persister.save(resource: resource_class.new)
108
+
109
+ expect(query_service.find_inverse_references_by(resource: parent, property: :a_member_of).to_a).to eq []
110
+ end
111
+ end
112
+
113
+ describe ".find_parents" do
114
+ it "returns all parent resources" do
115
+ child1 = persister.save(resource: resource_class.new)
116
+ child2 = persister.save(resource: resource_class.new)
117
+ parent = persister.save(resource: resource_class.new(member_ids: [child1.id, child2.id]))
118
+ parent2 = persister.save(resource: resource_class.new(member_ids: [child1.id]))
119
+
120
+ expect(query_service.find_parents(resource: child1).map(&:id).to_a).to contain_exactly parent.id, parent2.id
121
+ end
122
+ it "returns an empty array if there are none" do
123
+ child1 = persister.save(resource: resource_class.new)
124
+
125
+ expect(query_service.find_parents(resource: child1).to_a).to eq []
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+ RSpec.shared_examples 'a Valkyrie::Resource' do
3
+ before do
4
+ raise 'resource_klass must be set with `let(:resource_klass)`' unless
5
+ defined? resource_klass
6
+ end
7
+ describe "#id" do
8
+ it "can be set via instantiation and casts to a Valkyrie::ID" do
9
+ resource = resource_klass.new(id: "test")
10
+ expect(resource.id).to eq Valkyrie::ID.new("test")
11
+ end
12
+
13
+ it "is nil when not set" do
14
+ resource = resource_klass.new
15
+ expect(resource.id).to be_nil
16
+ end
17
+
18
+ it { is_expected.to respond_to(:persisted?).with(0).arguments }
19
+ it { is_expected.to respond_to(:to_param).with(0).arguments }
20
+ it { is_expected.to respond_to(:to_model).with(0).arguments }
21
+ it { is_expected.to respond_to(:model_name).with(0).arguments }
22
+ it { is_expected.to respond_to(:column_for_attribute).with(1).arguments }
23
+
24
+ describe "#has_attribute?" do
25
+ it "returns true when it has a given attribute" do
26
+ resource = resource_klass.new
27
+ expect(resource.has_attribute?(:id)).to eq true
28
+ end
29
+ end
30
+
31
+ describe "#fields" do
32
+ it "returns a set of fields" do
33
+ expect(resource_klass).to respond_to(:fields).with(0).arguments
34
+ expect(resource_klass.fields).to include(:id)
35
+ end
36
+ end
37
+
38
+ describe "#attributes" do
39
+ it "returns a list of all set attributes" do
40
+ resource = resource_klass.new(id: "test")
41
+ expect(resource.attributes[:id].to_s).to eq "test"
42
+ end
43
+ end
44
+ end
45
+
46
+ describe "#internal_resource" do
47
+ it "is set to the resource's class on instantiation" do
48
+ resource = resource_klass.new
49
+ expect(resource.internal_resource).to eq resource_klass.to_s
50
+ end
51
+ end
52
+
53
+ describe "#human_readable_type" do
54
+ before do
55
+ class MyCustomResource < Valkyrie::Resource
56
+ attribute :id, Valkyrie::Types::ID.optional
57
+ attribute :title, Valkyrie::Types::Set
58
+ end
59
+ end
60
+
61
+ after do
62
+ Object.send(:remove_const, :MyCustomResource)
63
+ end
64
+
65
+ subject(:my_custom_resource) { MyCustomResource.new }
66
+
67
+ it "returns a human readable rendering of the resource class" do
68
+ expect(my_custom_resource.human_readable_type).to eq "My Custom Resource"
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+ RSpec.shared_examples 'a Valkyrie::StorageAdapter' do
3
+ before do
4
+ raise 'storage_adapter must be set with `let(:storage_adapter)`' unless
5
+ defined? storage_adapter
6
+ raise 'file must be set with `let(:file)`' unless
7
+ defined? file
8
+ class CustomResource < Valkyrie::Resource
9
+ attribute :id, Valkyrie::Types::ID.optional
10
+ end
11
+ end
12
+ after do
13
+ Object.send(:remove_const, :CustomResource)
14
+ end
15
+ subject { storage_adapter }
16
+ it { is_expected.to respond_to(:handles?).with_keywords(:id) }
17
+ it { is_expected.to respond_to(:find_by).with_keywords(:id) }
18
+ it { is_expected.to respond_to(:upload).with_keywords(:file, :resource) }
19
+
20
+ it "can upload, validate, and re-fetch a file" do
21
+ resource = CustomResource.new(id: "test")
22
+ sha1 = Digest::SHA1.file(file).to_s
23
+ size = file.size
24
+ expect(uploaded_file = storage_adapter.upload(file: file, resource: resource)).to be_kind_of Valkyrie::StorageAdapter::File
25
+
26
+ expect(uploaded_file).to respond_to(:checksum).with_keywords(:digests)
27
+ expect(uploaded_file).to respond_to(:valid?).with_keywords(:size, :digests)
28
+ expect(uploaded_file.checksum(digests: [Digest::SHA1.new])).to eq([sha1])
29
+ expect(uploaded_file.valid?(digests: { sha1: sha1 })).to be true
30
+ expect(uploaded_file.valid?(size: size, digests: { sha1: sha1 })).to be true
31
+ expect(uploaded_file.valid?(size: (size + 1), digests: { sha1: sha1 })).to be false
32
+ expect(uploaded_file.valid?(size: size, digests: { sha1: 'bogus' })).to be false
33
+
34
+ expect(storage_adapter.handles?(id: uploaded_file.id)).to eq true
35
+ file = storage_adapter.find_by(id: uploaded_file.id)
36
+ expect(file.id).to eq uploaded_file.id
37
+ expect(file).to respond_to(:stream).with(0).arguments
38
+ expect(file).to respond_to(:read).with(0).arguments
39
+ expect(file).to respond_to(:rewind).with(0).arguments
40
+ expect(file.stream).to respond_to(:read)
41
+ new_file = Tempfile.new
42
+ expect { IO.copy_stream(file, new_file) }.not_to raise_error
43
+ end
44
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ module Valkyrie
3
+ module Storage
4
+ require 'valkyrie/storage/disk'
5
+ require 'valkyrie/storage/fedora'
6
+ require 'valkyrie/storage/memory'
7
+ end
8
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+ module Valkyrie::Storage
3
+ class Disk
4
+ attr_reader :base_path, :path_generator, :file_mover
5
+ def initialize(base_path:, path_generator: BucketedStorage, file_mover: FileUtils.method(:mv))
6
+ @base_path = Pathname.new(base_path.to_s)
7
+ @path_generator = path_generator.new(base_path: base_path)
8
+ @file_mover = file_mover
9
+ end
10
+
11
+ # @param file [IO]
12
+ # @param resource [Valkyrie::Resource]
13
+ # @return [Valkyrie::StorageAdapter::File]
14
+ def upload(file:, resource: nil)
15
+ new_path = path_generator.generate(resource: resource, file: file)
16
+ FileUtils.mkdir_p(new_path.parent)
17
+ file_mover.call(file.path, new_path)
18
+ find_by(id: Valkyrie::ID.new("disk://#{new_path}"))
19
+ end
20
+
21
+ # @param id [Valkyrie::ID]
22
+ # @return [Boolean] true if this adapter can handle this type of identifer
23
+ def handles?(id:)
24
+ id.to_s.start_with?("disk://")
25
+ end
26
+
27
+ def file_path(id)
28
+ id.to_s.gsub(/^disk:\/\//, '')
29
+ end
30
+
31
+ # Return the file associated with the given identifier
32
+ # @param id [Valkyrie::ID]
33
+ # @return [Valkyrie::StorageAdapter::File]
34
+ def find_by(id:)
35
+ return unless handles?(id: id)
36
+ Valkyrie::StorageAdapter::File.new(id: Valkyrie::ID.new(id.to_s), io: ::File.open(file_path(id), 'rb'))
37
+ end
38
+
39
+ class BucketedStorage
40
+ attr_reader :base_path
41
+ def initialize(base_path:)
42
+ @base_path = base_path
43
+ end
44
+
45
+ def generate(resource:, file:)
46
+ Pathname.new(base_path).join(*bucketed_path(resource.id)).join(file.original_filename)
47
+ end
48
+
49
+ def bucketed_path(id)
50
+ cleaned_id = id.to_s.delete("-")
51
+ cleaned_id[0..5].chars.each_slice(2).map(&:join) + [cleaned_id]
52
+ end
53
+ end
54
+ end
55
+ end