valkyrie 0.1.0 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +5 -5
  2. data/.ctags +2 -0
  3. data/.rubocop.yml +5 -0
  4. data/.rubocop_todo.yml +3 -0
  5. data/CHANGELOG.md +43 -0
  6. data/Gemfile +0 -4
  7. data/LICENSE +15 -0
  8. data/README.md +13 -8
  9. data/Rakefile +15 -1
  10. data/bin/jetty_wait +14 -0
  11. data/bin/rspec +29 -0
  12. data/browserslist +3 -0
  13. data/circle.yml +17 -0
  14. data/config/fedora.yml +10 -0
  15. data/db/migrate/20161007101725_create_orm_resources.rb +9 -1
  16. data/db/migrate/20171011224121_create_path_gin_index.rb +6 -0
  17. data/db/migrate/20171204224121_create_internal_resource_index.rb +6 -0
  18. data/db/migrate/20180212092225_create_updated_at_index.rb +6 -0
  19. data/lib/generators/valkyrie/templates/resource_spec.rb.erb +1 -1
  20. data/lib/valkyrie.rb +1 -7
  21. data/lib/valkyrie/change_set.rb +21 -7
  22. data/lib/valkyrie/engine.rb +2 -0
  23. data/lib/valkyrie/id.rb +1 -0
  24. data/lib/valkyrie/indexers/access_controls_indexer.rb +50 -6
  25. data/lib/valkyrie/metadata_adapter.rb +29 -1
  26. data/lib/valkyrie/persistence.rb +27 -0
  27. data/lib/valkyrie/persistence/buffered_persister.rb +17 -1
  28. data/lib/valkyrie/persistence/composite_persister.rb +14 -2
  29. data/lib/valkyrie/persistence/custom_query_container.rb +63 -0
  30. data/lib/valkyrie/persistence/delete_tracking_buffer.rb +8 -0
  31. data/lib/valkyrie/persistence/fedora.rb +3 -0
  32. data/lib/valkyrie/persistence/fedora/list_node.rb +5 -2
  33. data/lib/valkyrie/persistence/fedora/metadata_adapter.rb +13 -11
  34. data/lib/valkyrie/persistence/fedora/ordered_list.rb +2 -2
  35. data/lib/valkyrie/persistence/fedora/ordered_reader.rb +3 -2
  36. data/lib/valkyrie/persistence/fedora/permissive_schema.rb +75 -0
  37. data/lib/valkyrie/persistence/fedora/persister.rb +22 -0
  38. data/lib/valkyrie/persistence/fedora/persister/model_converter.rb +110 -25
  39. data/lib/valkyrie/persistence/fedora/persister/orm_converter.rb +49 -17
  40. data/lib/valkyrie/persistence/fedora/persister/resource_factory.rb +2 -0
  41. data/lib/valkyrie/persistence/fedora/query_service.rb +46 -3
  42. data/lib/valkyrie/persistence/memory.rb +5 -0
  43. data/lib/valkyrie/persistence/memory/metadata_adapter.rb +6 -1
  44. data/lib/valkyrie/persistence/memory/persister.rb +19 -1
  45. data/lib/valkyrie/persistence/memory/query_service.rb +49 -8
  46. data/lib/valkyrie/persistence/postgres.rb +2 -0
  47. data/lib/valkyrie/persistence/postgres/metadata_adapter.rb +8 -1
  48. data/lib/valkyrie/persistence/postgres/orm.rb +1 -0
  49. data/lib/valkyrie/persistence/postgres/orm/resource.rb +12 -0
  50. data/lib/valkyrie/persistence/postgres/orm_converter.rb +99 -74
  51. data/lib/valkyrie/persistence/postgres/persister.rb +16 -0
  52. data/lib/valkyrie/persistence/postgres/query_service.rb +94 -6
  53. data/lib/valkyrie/persistence/postgres/resource_converter.rb +3 -1
  54. data/lib/valkyrie/persistence/postgres/resource_factory.rb +5 -5
  55. data/lib/valkyrie/persistence/solr.rb +2 -0
  56. data/lib/valkyrie/persistence/solr/composite_indexer.rb +29 -0
  57. data/lib/valkyrie/persistence/solr/metadata_adapter.rb +26 -1
  58. data/lib/valkyrie/persistence/solr/model_converter.rb +38 -8
  59. data/lib/valkyrie/persistence/solr/orm_converter.rb +43 -20
  60. data/lib/valkyrie/persistence/solr/persister.rb +16 -0
  61. data/lib/valkyrie/persistence/solr/queries.rb +3 -0
  62. data/lib/valkyrie/persistence/solr/queries/default_paginator.rb +2 -0
  63. data/lib/valkyrie/persistence/solr/queries/find_all_query.rb +3 -1
  64. data/lib/valkyrie/persistence/solr/queries/find_by_id_query.rb +4 -2
  65. data/lib/valkyrie/persistence/solr/queries/find_inverse_references_query.rb +2 -0
  66. data/lib/valkyrie/persistence/solr/queries/find_many_by_ids_query.rb +21 -0
  67. data/lib/valkyrie/persistence/solr/queries/find_members_query.rb +13 -5
  68. data/lib/valkyrie/persistence/solr/queries/find_references_query.rb +7 -3
  69. data/lib/valkyrie/persistence/solr/query_service.rb +30 -2
  70. data/lib/valkyrie/persistence/solr/repository.rb +14 -2
  71. data/lib/valkyrie/persistence/solr/resource_factory.rb +3 -1
  72. data/lib/valkyrie/resource.rb +11 -4
  73. data/lib/valkyrie/resource/access_controls.rb +13 -0
  74. data/lib/valkyrie/specs/shared_specs.rb +1 -2
  75. data/lib/valkyrie/specs/shared_specs/change_set.rb +75 -0
  76. data/lib/valkyrie/specs/shared_specs/metadata_adapter.rb +3 -0
  77. data/lib/valkyrie/specs/shared_specs/persister.rb +145 -15
  78. data/lib/valkyrie/specs/shared_specs/queries.rb +153 -27
  79. data/lib/valkyrie/specs/shared_specs/storage_adapter.rb +8 -3
  80. data/lib/valkyrie/storage.rb +29 -0
  81. data/lib/valkyrie/storage/disk.rb +17 -5
  82. data/lib/valkyrie/storage/fedora.rb +14 -1
  83. data/lib/valkyrie/storage/memory.rb +15 -2
  84. data/lib/valkyrie/storage_adapter.rb +26 -4
  85. data/lib/valkyrie/types.rb +65 -7
  86. data/lib/valkyrie/version.rb +1 -1
  87. data/solr/config/_rest_managed.json +3 -0
  88. data/solr/config/admin-extra.html +31 -0
  89. data/solr/config/elevate.xml +36 -0
  90. data/solr/config/mapping-ISOLatin1Accent.txt +246 -0
  91. data/solr/config/protwords.txt +21 -0
  92. data/solr/config/schema.xml +366 -0
  93. data/solr/config/scripts.conf +24 -0
  94. data/solr/config/solrconfig.xml +322 -0
  95. data/solr/config/spellings.txt +2 -0
  96. data/solr/config/stopwords.txt +58 -0
  97. data/solr/config/stopwords_en.txt +58 -0
  98. data/solr/config/synonyms.txt +31 -0
  99. data/solr/config/xslt/example.xsl +132 -0
  100. data/solr/config/xslt/example_atom.xsl +67 -0
  101. data/solr/config/xslt/example_rss.xsl +66 -0
  102. data/solr/config/xslt/luke.xsl +337 -0
  103. data/solr/solr.xml +35 -0
  104. data/tasks/dev.rake +66 -0
  105. data/valkyrie.gemspec +6 -6
  106. metadata +58 -63
  107. data/lib/valkyrie/decorators/decorator_list.rb +0 -15
  108. data/lib/valkyrie/decorators/decorator_with_arguments.rb +0 -14
  109. data/lib/valkyrie/derivative_service.rb +0 -42
  110. data/lib/valkyrie/file_characterization_service.rb +0 -42
  111. data/lib/valkyrie/local_file_service.rb +0 -11
  112. data/lib/valkyrie/persist_derivatives.rb +0 -29
  113. data/lib/valkyrie/persistence/postgres/queries.rb +0 -8
  114. data/lib/valkyrie/persistence/postgres/queries/find_inverse_references_query.rb +0 -31
  115. data/lib/valkyrie/persistence/postgres/queries/find_members_query.rb +0 -33
  116. data/lib/valkyrie/persistence/postgres/queries/find_references_query.rb +0 -33
  117. data/lib/valkyrie/specs/shared_specs/derivative_service.rb +0 -30
  118. data/lib/valkyrie/specs/shared_specs/file_characterization_service.rb +0 -33
@@ -7,4 +7,7 @@ RSpec.shared_examples 'a Valkyrie::MetadataAdapter' do |passed_adapter|
7
7
  subject { passed_adapter || adapter }
8
8
  it { is_expected.to respond_to(:persister).with(0).arguments }
9
9
  it { is_expected.to respond_to(:query_service).with(0).arguments }
10
+ it "caches query_service so it can register custom queries" do
11
+ expect(subject.query_service.custom_queries.query_handlers.object_id).to eq subject.query_service.custom_queries.query_handlers.object_id
12
+ end
10
13
  end
@@ -9,6 +9,7 @@ RSpec.shared_examples 'a Valkyrie::Persister' do |*flags|
9
9
  attribute :author
10
10
  attribute :member_ids
11
11
  attribute :nested_resource
12
+ attribute :single_value, Valkyrie::Types::String
12
13
  end
13
14
  end
14
15
  after do
@@ -24,7 +25,15 @@ RSpec.shared_examples 'a Valkyrie::Persister' do |*flags|
24
25
  it { is_expected.to respond_to(:delete).with_keywords(:resource) }
25
26
 
26
27
  it "can save a resource" do
27
- expect(persister.save(resource: resource).id).not_to be_blank
28
+ expect(resource).not_to be_persisted
29
+ saved = persister.save(resource: resource)
30
+ expect(saved).to be_persisted
31
+ expect(saved.id).not_to be_blank
32
+ end
33
+
34
+ it "does not save non-array properties" do
35
+ resource.single_value = "A Single Value"
36
+ expect { persister.save(resource: resource) }.to raise_error ::Valkyrie::Persistence::UnsupportedDatatype
28
37
  end
29
38
 
30
39
  it "can save multiple resources at once" do
@@ -74,33 +83,87 @@ RSpec.shared_examples 'a Valkyrie::Persister' do |*flags|
74
83
  expect(book.updated_at).not_to be_kind_of Array
75
84
  end
76
85
 
86
+ it "can handle Boolean RDF properties" do
87
+ boolean_rdf = RDF::Literal.new(false)
88
+ book = persister.save(resource: resource_class.new(title: [boolean_rdf]))
89
+ reloaded = query_service.find_by(id: book.id)
90
+ expect(reloaded.title).to contain_exactly boolean_rdf
91
+ end
92
+
93
+ it "can handle custom-typed RDF properties" do
94
+ custom_rdf = RDF::Literal.new("Test", datatype: RDF::URI.parse("http://my_made_up_type"))
95
+ book = persister.save(resource: resource_class.new(title: [custom_rdf]))
96
+ reloaded = query_service.find_by(id: book.id)
97
+ expect(reloaded.title).to contain_exactly custom_rdf
98
+ end
99
+
100
+ it "can handle Date RDF properties" do
101
+ date_rdf = RDF::Literal.new(Date.current)
102
+ book = persister.save(resource: resource_class.new(title: [date_rdf]))
103
+ reloaded = query_service.find_by(id: book.id)
104
+ expect(reloaded.title).to contain_exactly date_rdf
105
+ end
106
+
107
+ it "can handle DateTime RDF properties" do
108
+ datetime_rdf = RDF::Literal.new(DateTime.current)
109
+ book = persister.save(resource: resource_class.new(title: [datetime_rdf]))
110
+ reloaded = query_service.find_by(id: book.id)
111
+ expect(reloaded.title).to contain_exactly datetime_rdf
112
+ end
113
+
114
+ it "can handle Decimal RDF properties" do
115
+ decimal_rdf = RDF::Literal.new(BigDecimal(5.5, 10))
116
+ book = persister.save(resource: resource_class.new(title: [decimal_rdf]))
117
+ reloaded = query_service.find_by(id: book.id)
118
+ expect(reloaded.title).to contain_exactly decimal_rdf
119
+ end
120
+
121
+ it "can handle Double RDF properties" do
122
+ double_rdf = RDF::Literal.new(5.5)
123
+ book = persister.save(resource: resource_class.new(title: [double_rdf]))
124
+ reloaded = query_service.find_by(id: book.id)
125
+ expect(reloaded.title).to contain_exactly double_rdf
126
+ end
127
+
128
+ it "can handle Integer RDF properties" do
129
+ int_rdf = RDF::Literal.new(17)
130
+ book = persister.save(resource: resource_class.new(title: [int_rdf]))
131
+ reloaded = query_service.find_by(id: book.id)
132
+ expect(reloaded.title).to contain_exactly int_rdf
133
+ end
134
+
77
135
  it "can handle language-typed RDF properties" do
78
- book = persister.save(resource: resource_class.new(title: ["Test1", RDF::Literal.new("Test", language: :fr)]))
136
+ language_rdf = RDF::Literal.new("Test", language: :fr)
137
+ book = persister.save(resource: resource_class.new(title: ["Test1", language_rdf]))
79
138
  reloaded = query_service.find_by(id: book.id)
80
- expect(reloaded.title).to contain_exactly "Test1", RDF::Literal.new("Test", language: :fr)
139
+ expect(reloaded.title).to contain_exactly "Test1", language_rdf
81
140
  end
82
141
 
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"]))
142
+ it "can handle Time RDF properties" do
143
+ time_rdf = RDF::Literal.new(Time.current)
144
+ book = persister.save(resource: resource_class.new(title: [time_rdf]))
86
145
  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)
146
+ expect(reloaded.title).to contain_exactly time_rdf
89
147
  end
90
148
 
91
- it "can store ::RDF::URIs" do
92
- book = persister.save(resource: resource_class.new(title: [::RDF::URI("http://example.com")]))
149
+ # https://github.com/samvera-labs/valkyrie/wiki/Supported-Data-Types
150
+ it "can store booleans" do
151
+ boolean = [false, true]
152
+ book = persister.save(resource: resource_class.new(title: boolean))
93
153
  reloaded = query_service.find_by(id: book.id)
94
- expect(reloaded.title).to contain_exactly RDF::URI("http://example.com")
154
+ expect(reloaded.title).to contain_exactly(*boolean)
95
155
  end
96
156
 
97
- it "can store integers" do
98
- book = persister.save(resource: resource_class.new(title: [1]))
157
+ # Pending date support in Valkyrie
158
+ # https://github.com/samvera-labs/valkyrie/wiki/Supported-Data-Types
159
+ xit "can store Dates" do
160
+ date = Date.current
161
+ book = persister.save(resource: resource_class.new(title: [date]))
99
162
  reloaded = query_service.find_by(id: book.id)
100
- expect(reloaded.title).to contain_exactly 1
163
+ expect(reloaded.title).to contain_exactly date
101
164
  end
102
165
 
103
- it "can store datetimes" do
166
+ it "can store DateTimes" do
104
167
  time1 = DateTime.current
105
168
  time2 = Time.current.in_time_zone
106
169
  book = persister.save(resource: resource_class.new(title: [time1], author: [time2]))
@@ -113,6 +176,66 @@ RSpec.shared_examples 'a Valkyrie::Persister' do |*flags|
113
176
  expect(reloaded.author.first.zone).to eq('UTC')
114
177
  end
115
178
 
179
+ # Pending decimals support in Valkyrie
180
+ # https://github.com/samvera-labs/valkyrie/wiki/Supported-Data-Types
181
+ xit "can store Decimals" do
182
+ decimal = BigDecimal(5.5, 10)
183
+ book = persister.save(resource: resource_class.new(title: [decimal]))
184
+ reloaded = query_service.find_by(id: book.id)
185
+ expect(reloaded.title).to contain_exactly decimal
186
+ end
187
+
188
+ # Pending doubles support in Valkyrie
189
+ # https://github.com/samvera-labs/valkyrie/wiki/Supported-Data-Types
190
+ xit "can store doubles" do
191
+ book = persister.save(resource: resource_class.new(title: [1.5]))
192
+ reloaded = query_service.find_by(id: book.id)
193
+ expect(reloaded.title).to contain_exactly 1.5
194
+ end
195
+
196
+ it "can store integers" do
197
+ book = persister.save(resource: resource_class.new(title: [1]))
198
+ reloaded = query_service.find_by(id: book.id)
199
+ expect(reloaded.title).to contain_exactly 1
200
+ end
201
+
202
+ # Pending time support in Valkyrie
203
+ # currently millisecond precision is lost is postgres and solr
204
+ #
205
+ # https://github.com/samvera-labs/valkyrie/wiki/Supported-Data-Types
206
+ xit "can store Times" do
207
+ time = Time.current
208
+ book = persister.save(resource: resource_class.new(title: [time]))
209
+ reloaded = query_service.find_by(id: book.id)
210
+ expect(reloaded.title).to contain_exactly time.utc
211
+ end
212
+
213
+ it "can store ::RDF::URIs" do
214
+ book = persister.save(resource: resource_class.new(title: [::RDF::URI("http://example.com")]))
215
+ reloaded = query_service.find_by(id: book.id)
216
+ expect(reloaded.title).to contain_exactly RDF::URI("http://example.com")
217
+ end
218
+
219
+ it "can store Valkyrie::IDs" do
220
+ shared_title = persister.save(resource: resource_class.new(id: "test"))
221
+ book = persister.save(resource: resource_class.new(title: [shared_title.id, Valkyrie::ID.new("adapter://1"), "test"]))
222
+ reloaded = query_service.find_by(id: book.id)
223
+ expect(reloaded.title).to contain_exactly(shared_title.id, Valkyrie::ID.new("adapter://1"), "test")
224
+ expect([shared_title.id, Valkyrie::ID.new("adapter://1"), "test"]).to contain_exactly(*reloaded.title)
225
+ end
226
+
227
+ it "can override default id generation with a provided id" do
228
+ id = SecureRandom.uuid
229
+ book = persister.save(resource: resource_class.new(id: id))
230
+ reloaded = query_service.find_by(id: book.id)
231
+ expect(reloaded.id).to eq Valkyrie::ID.new(id)
232
+ expect(reloaded).to be_persisted
233
+ expect(reloaded.created_at).not_to be_blank
234
+ expect(reloaded.updated_at).not_to be_blank
235
+ expect(reloaded.created_at).not_to be_kind_of Array
236
+ expect(reloaded.updated_at).not_to be_kind_of Array
237
+ end
238
+
116
239
  context "parent tests" do
117
240
  let(:book) { persister.save(resource: resource_class.new) }
118
241
  let(:book2) { persister.save(resource: resource_class.new) }
@@ -151,4 +274,11 @@ RSpec.shared_examples 'a Valkyrie::Persister' do |*flags|
151
274
  persister.delete(resource: persisted)
152
275
  expect { query_service.find_by(id: persisted.id) }.to raise_error ::Valkyrie::Persistence::ObjectNotFoundError
153
276
  end
277
+
278
+ it "can delete all objects" do
279
+ resource2 = resource_class.new
280
+ persister.save_all(resources: [resource, resource2])
281
+ persister.wipe!
282
+ expect(query_service.find_all.to_a.length).to eq 0
283
+ end
154
284
  end
@@ -6,7 +6,7 @@ RSpec.shared_examples 'a Valkyrie query provider' do
6
6
  class CustomResource < Valkyrie::Resource
7
7
  attribute :id, Valkyrie::Types::ID.optional
8
8
  attribute :title
9
- attribute :member_ids
9
+ attribute :member_ids, Valkyrie::Types::Array
10
10
  attribute :a_member_of
11
11
  end
12
12
  class SecondResource < Valkyrie::Resource
@@ -18,14 +18,15 @@ RSpec.shared_examples 'a Valkyrie query provider' do
18
18
  Object.send(:remove_const, :SecondResource)
19
19
  end
20
20
  let(:resource_class) { CustomResource }
21
- let(:query_service) { adapter.query_service }
21
+ let(:query_service) { adapter.query_service } unless defined? query_service
22
22
  let(:persister) { adapter.persister }
23
23
  subject { adapter.query_service }
24
24
 
25
25
  it { is_expected.to respond_to(:find_all).with(0).arguments }
26
26
  it { is_expected.to respond_to(:find_all_of_model).with_keywords(:model) }
27
27
  it { is_expected.to respond_to(:find_by).with_keywords(:id) }
28
- it { is_expected.to respond_to(:find_members).with_keywords(:resource) }
28
+ it { is_expected.to respond_to(:find_many_by_ids).with_keywords(:ids) }
29
+ it { is_expected.to respond_to(:find_members).with_keywords(:resource, :model) }
29
30
  it { is_expected.to respond_to(:find_references_by).with_keywords(:resource, :property) }
30
31
  it { is_expected.to respond_to(:find_inverse_references_by).with_keywords(:resource, :property) }
31
32
  it { is_expected.to respond_to(:find_parents).with_keywords(:resource) }
@@ -52,31 +53,117 @@ RSpec.shared_examples 'a Valkyrie query provider' do
52
53
  end
53
54
 
54
55
  describe ".find_by" do
55
- it "returns a resource by id" do
56
+ it "returns a resource by id or string representation of an id" do
56
57
  resource = persister.save(resource: resource_class.new)
57
58
 
58
- expect(query_service.find_by(id: resource.id).id).to eq resource.id
59
+ found = query_service.find_by(id: resource.id)
60
+ expect(found.id).to eq resource.id
61
+ expect(found).to be_persisted
62
+
63
+ found = query_service.find_by(id: resource.id.to_s)
64
+ expect(found.id).to eq resource.id
65
+ expect(found).to be_persisted
59
66
  end
67
+
60
68
  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
69
+ expect { query_service.find_by(id: Valkyrie::ID.new("123123123")) }.to raise_error ::Valkyrie::Persistence::ObjectNotFoundError
70
+ end
71
+
72
+ it 'raises an error if the id is not a Valkyrie::ID or a string' do
73
+ expect { query_service.find_by(id: 123) }.to raise_error ArgumentError
62
74
  end
63
75
  end
64
76
 
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]))
77
+ describe ".find_many_by_ids" do
78
+ let!(:resource) { persister.save(resource: resource_class.new) }
79
+ let!(:resource2) { persister.save(resource: resource_class.new) }
80
+ let!(:resource3) { persister.save(resource: resource_class.new) }
81
+
82
+ it "returns an array of resources by ids or string representation ids" do
83
+ found = query_service.find_many_by_ids(ids: [resource.id, resource2.id])
84
+ expect(found.map(&:id)).to contain_exactly resource.id, resource2.id
70
85
 
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]
86
+ found = query_service.find_many_by_ids(ids: [resource.id.to_s, resource2.id.to_s])
87
+ expect(found.map(&:id)).to contain_exactly resource.id, resource2.id
73
88
  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 []
89
+
90
+ it "returns a partial list for a non-found ID" do
91
+ found = query_service.find_many_by_ids(ids: [resource.id, Valkyrie::ID.new("123123123")])
92
+ expect(found.map(&:id)).to contain_exactly resource.id
77
93
  end
78
- it "returns an empty array if there are none" do
79
- expect(query_service.find_all.to_a).to eq []
94
+
95
+ it "returns an empty list if no ids were found" do
96
+ found = query_service.find_many_by_ids(ids: [Valkyrie::ID.new("you-cannot-find-me"), Valkyrie::ID.new("123123123")])
97
+ expect(found.map(&:id)).to eq []
98
+ end
99
+
100
+ it 'raises an error if any id is not a Valkyrie::ID or a string' do
101
+ expect { query_service.find_many_by_ids(ids: [resource.id, 123]) }.to raise_error ArgumentError
102
+ end
103
+ end
104
+
105
+ describe ".find_members" do
106
+ context "without filtering by model" do
107
+ subject { query_service.find_members(resource: parent) }
108
+
109
+ context "when the object has members" do
110
+ let!(:child1) { persister.save(resource: resource_class.new) }
111
+ let!(:child2) { persister.save(resource: resource_class.new) }
112
+ let(:parent) { persister.save(resource: resource_class.new(member_ids: [child2.id, child1.id])) }
113
+
114
+ it "returns all a resource's members in order" do
115
+ expect(subject.map(&:id).to_a).to eq [child2.id, child1.id]
116
+ end
117
+ end
118
+
119
+ context "when there's no resource ID" do
120
+ let(:parent) { resource_class.new }
121
+
122
+ it "doesn't error" do
123
+ expect(subject).not_to eq nil
124
+ expect(subject.to_a).to eq []
125
+ end
126
+ end
127
+
128
+ context "when there are no members" do
129
+ let(:parent) { persister.save(resource: resource_class.new) }
130
+
131
+ it "returns an empty array" do
132
+ expect(subject.to_a).to eq []
133
+ end
134
+ end
135
+
136
+ context "when the model doesn't have member_ids" do
137
+ let(:parent) { persister.save(resource: SecondResource.new) }
138
+
139
+ it "returns an empty array" do
140
+ expect(subject.to_a).to eq []
141
+ end
142
+ end
143
+ end
144
+
145
+ context "filtering by model" do
146
+ subject { query_service.find_members(resource: parent, model: SecondResource) }
147
+
148
+ context "when the object has members" do
149
+ let(:child1) { persister.save(resource: SecondResource.new) }
150
+ let(:child2) { persister.save(resource: resource_class.new) }
151
+ let(:child3) { persister.save(resource: SecondResource.new) }
152
+ let(:parent) { persister.save(resource: resource_class.new(member_ids: [child3.id, child2.id, child1.id])) }
153
+
154
+ it "returns all a resource's members in order" do
155
+ expect(subject.map(&:id).to_a).to eq [child3.id, child1.id]
156
+ end
157
+ end
158
+
159
+ context "when there are no members that match the filter" do
160
+ let(:child1) { persister.save(resource: resource_class.new) }
161
+ let(:parent) { persister.save(resource: resource_class.new(member_ids: [child1.id])) }
162
+
163
+ it "returns an empty array" do
164
+ expect(subject.to_a).to eq []
165
+ end
166
+ end
80
167
  end
81
168
  end
82
169
 
@@ -95,18 +182,27 @@ RSpec.shared_examples 'a Valkyrie query provider' do
95
182
  end
96
183
 
97
184
  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)
185
+ context "when the resource is saved" do
186
+ it "returns everything which references the given resource by the given property" do
187
+ parent = persister.save(resource: resource_class.new)
188
+ child = persister.save(resource: resource_class.new(a_member_of: [parent.id]))
189
+ persister.save(resource: resource_class.new)
190
+ persister.save(resource: SecondResource.new)
191
+
192
+ expect(query_service.find_inverse_references_by(resource: parent, property: :a_member_of).map(&:id).to_a).to eq [child.id]
193
+ end
194
+ it "returns an empty array if there are none" do
195
+ parent = persister.save(resource: resource_class.new)
103
196
 
104
- expect(query_service.find_inverse_references_by(resource: parent, property: :a_member_of).map(&:id).to_a).to eq [child.id]
197
+ expect(query_service.find_inverse_references_by(resource: parent, property: :a_member_of).to_a).to eq []
198
+ end
105
199
  end
106
- it "returns an empty array if there are none" do
107
- parent = persister.save(resource: resource_class.new)
200
+ context "when the resource is not saved" do
201
+ it "raises an error" do
202
+ parent = resource_class.new
108
203
 
109
- expect(query_service.find_inverse_references_by(resource: parent, property: :a_member_of).to_a).to eq []
204
+ expect { query_service.find_inverse_references_by(resource: parent, property: :a_member_of).to_a }.to raise_error ArgumentError
205
+ end
110
206
  end
111
207
  end
112
208
 
@@ -124,5 +220,35 @@ RSpec.shared_examples 'a Valkyrie query provider' do
124
220
 
125
221
  expect(query_service.find_parents(resource: child1).to_a).to eq []
126
222
  end
223
+
224
+ context "when the model doesn't have member_ids" do
225
+ let(:child1) { persister.save(resource: SecondResource.new) }
226
+
227
+ it "returns an empty array if there are none" do
228
+ expect(query_service.find_parents(resource: child1).to_a).to eq []
229
+ end
230
+ end
231
+ end
232
+
233
+ describe ".register_query_handler" do
234
+ it "can register a query handler" do
235
+ class QueryHandler
236
+ def self.queries
237
+ [:find_by_user_id]
238
+ end
239
+
240
+ attr_reader :query_service
241
+ def initialize(query_service:)
242
+ @query_service = query_service
243
+ end
244
+
245
+ def find_by_user_id
246
+ 1
247
+ end
248
+ end
249
+ query_service.custom_queries.register_query_handler(QueryHandler)
250
+ expect(query_service.custom_queries).to respond_to :find_by_user_id
251
+ expect(query_service.custom_queries.find_by_user_id).to eq 1
252
+ end
127
253
  end
128
254
  end
@@ -15,13 +15,14 @@ RSpec.shared_examples 'a Valkyrie::StorageAdapter' do
15
15
  subject { storage_adapter }
16
16
  it { is_expected.to respond_to(:handles?).with_keywords(:id) }
17
17
  it { is_expected.to respond_to(:find_by).with_keywords(:id) }
18
- it { is_expected.to respond_to(:upload).with_keywords(:file, :resource) }
18
+ it { is_expected.to respond_to(:delete).with_keywords(:id) }
19
+ it { is_expected.to respond_to(:upload).with_keywords(:file, :resource, :original_filename) }
19
20
 
20
- it "can upload, validate, and re-fetch a file" do
21
+ it "can upload, validate, re-fetch, and delete a file" do
21
22
  resource = CustomResource.new(id: "test")
22
23
  sha1 = Digest::SHA1.file(file).to_s
23
24
  size = file.size
24
- expect(uploaded_file = storage_adapter.upload(file: file, resource: resource)).to be_kind_of Valkyrie::StorageAdapter::File
25
+ expect(uploaded_file = storage_adapter.upload(file: file, original_filename: 'foo.jpg', resource: resource)).to be_kind_of Valkyrie::StorageAdapter::File
25
26
 
26
27
  expect(uploaded_file).to respond_to(:checksum).with_keywords(:digests)
27
28
  expect(uploaded_file).to respond_to(:valid?).with_keywords(:size, :digests)
@@ -40,5 +41,9 @@ RSpec.shared_examples 'a Valkyrie::StorageAdapter' do
40
41
  expect(file.stream).to respond_to(:read)
41
42
  new_file = Tempfile.new
42
43
  expect { IO.copy_stream(file, new_file) }.not_to raise_error
44
+
45
+ storage_adapter.delete(id: uploaded_file.id)
46
+ expect { storage_adapter.find_by(id: uploaded_file.id) }.to raise_error Valkyrie::StorageAdapter::FileNotFound
47
+ expect { storage_adapter.find_by(id: Valkyrie::ID.new("noexist")) }.to raise_error Valkyrie::StorageAdapter::FileNotFound
43
48
  end
44
49
  end