valkyrie 1.2.0.rc1 → 1.2.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +12 -4
  4. data/lib/valkyrie/persistence/composite_persister.rb +1 -1
  5. data/lib/valkyrie/persistence/fedora/list_node.rb +42 -3
  6. data/lib/valkyrie/persistence/fedora/metadata_adapter.rb +26 -0
  7. data/lib/valkyrie/persistence/fedora/ordered_list.rb +36 -5
  8. data/lib/valkyrie/persistence/fedora/ordered_reader.rb +6 -0
  9. data/lib/valkyrie/persistence/fedora/permissive_schema.rb +20 -1
  10. data/lib/valkyrie/persistence/fedora/persister.rb +33 -4
  11. data/lib/valkyrie/persistence/fedora/persister/alternate_identifier.rb +6 -0
  12. data/lib/valkyrie/persistence/fedora/persister/model_converter.rb +254 -4
  13. data/lib/valkyrie/persistence/fedora/persister/orm_converter.rb +250 -3
  14. data/lib/valkyrie/persistence/fedora/persister/resource_factory.rb +6 -0
  15. data/lib/valkyrie/persistence/fedora/query_service.rb +22 -4
  16. data/lib/valkyrie/persistence/memory/metadata_adapter.rb +2 -0
  17. data/lib/valkyrie/persistence/memory/persister.rb +11 -3
  18. data/lib/valkyrie/persistence/memory/query_service.rb +11 -0
  19. data/lib/valkyrie/persistence/postgres/metadata_adapter.rb +2 -0
  20. data/lib/valkyrie/persistence/postgres/orm.rb +4 -0
  21. data/lib/valkyrie/persistence/postgres/orm_converter.rb +62 -2
  22. data/lib/valkyrie/persistence/postgres/persister.rb +18 -7
  23. data/lib/valkyrie/persistence/postgres/query_service.rb +103 -11
  24. data/lib/valkyrie/persistence/postgres/resource_converter.rb +10 -0
  25. data/lib/valkyrie/persistence/postgres/resource_factory.rb +3 -0
  26. data/lib/valkyrie/persistence/solr/composite_indexer.rb +10 -0
  27. data/lib/valkyrie/persistence/solr/metadata_adapter.rb +7 -0
  28. data/lib/valkyrie/persistence/solr/model_converter.rb +137 -0
  29. data/lib/valkyrie/persistence/solr/orm_converter.rb +168 -0
  30. data/lib/valkyrie/persistence/solr/persister.rb +13 -5
  31. data/lib/valkyrie/persistence/solr/queries.rb +1 -0
  32. data/lib/valkyrie/persistence/solr/queries/default_paginator.rb +11 -1
  33. data/lib/valkyrie/persistence/solr/queries/find_all_query.rb +12 -0
  34. data/lib/valkyrie/persistence/solr/queries/find_by_alternate_identifier_query.rb +12 -0
  35. data/lib/valkyrie/persistence/solr/queries/find_by_id_query.rb +11 -0
  36. data/lib/valkyrie/persistence/solr/queries/find_inverse_references_query.rb +13 -0
  37. data/lib/valkyrie/persistence/solr/queries/find_many_by_ids_query.rb +9 -0
  38. data/lib/valkyrie/persistence/solr/queries/find_members_query.rb +23 -0
  39. data/lib/valkyrie/persistence/solr/queries/find_ordered_references_query.rb +50 -0
  40. data/lib/valkyrie/persistence/solr/queries/find_references_query.rb +15 -0
  41. data/lib/valkyrie/persistence/solr/query_service.rb +47 -14
  42. data/lib/valkyrie/persistence/solr/repository.rb +21 -4
  43. data/lib/valkyrie/persistence/solr/resource_factory.rb +2 -0
  44. data/lib/valkyrie/resource.rb +1 -0
  45. data/lib/valkyrie/specs/shared_specs.rb +1 -0
  46. data/lib/valkyrie/specs/shared_specs/persister.rb +92 -2
  47. data/lib/valkyrie/specs/shared_specs/queries.rb +12 -0
  48. data/lib/valkyrie/specs/shared_specs/solr_indexer.rb +40 -0
  49. data/lib/valkyrie/storage/fedora.rb +0 -2
  50. data/lib/valkyrie/version.rb +1 -1
  51. metadata +4 -2
@@ -3,27 +3,40 @@ module Valkyrie::Persistence::Solr
3
3
  # Responsible for converting hashes from Solr into a {Valkyrie::Resource}
4
4
  class ORMConverter
5
5
  attr_reader :solr_document, :resource_factory
6
+
7
+ # @param [Hash] solr_document
8
+ # @param [ResourceFactory] resource_factory
6
9
  def initialize(solr_document, resource_factory:)
7
10
  @solr_document = solr_document
8
11
  @resource_factory = resource_factory
9
12
  end
10
13
 
14
+ # Converts the Solr Document into a Valkyrie Resource
15
+ # @return [Valkyrie::Resource]
11
16
  def convert!
12
17
  resource
13
18
  end
14
19
 
20
+ # Construct the Valkyrie Resource using attributes derived from the Solr Document
21
+ # @return [Valkyrie::Resource]
15
22
  def resource
16
23
  resource_klass.new(attributes.symbolize_keys.merge(new_record: false))
17
24
  end
18
25
 
26
+ # Access the Class for the Valkyrie Resource
27
+ # @return [Class]
19
28
  def resource_klass
20
29
  internal_resource.constantize
21
30
  end
22
31
 
32
+ # Access the String specifying the Valkyrie Resource type in the Solr Document
33
+ # @return [String]
23
34
  def internal_resource
24
35
  solr_document.fetch(Valkyrie::Persistence::Solr::Queries::MODEL).first
25
36
  end
26
37
 
38
+ # Derive the Valkyrie attributes from the Solr Document
39
+ # @return [Hash]
27
40
  def attributes
28
41
  attribute_hash.merge("id" => id,
29
42
  internal_resource: internal_resource,
@@ -32,32 +45,54 @@ module Valkyrie::Persistence::Solr
32
45
  Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK => token)
33
46
  end
34
47
 
48
+ # Construct a Time object from the datestamp for the resource creation date indexed in Solr
49
+ # @return [Time]
35
50
  def created_at
36
51
  DateTime.parse(solr_document.fetch("created_at_dtsi").to_s).utc
37
52
  end
38
53
 
54
+ # Construct a Time object from the datestamp for the date of the last resource update indexed in Solr
55
+ # @return [Time]
39
56
  def updated_at
40
57
  DateTime.parse(solr_document["timestamp"] || solr_document.fetch("created_at_dtsi").to_s).utc
41
58
  end
42
59
 
60
+ # Construct the OptimisticLockToken object using the "_version_" field value in the Solr Document
61
+ # @see https://lucene.apache.org/solr/guide/updating-parts-of-documents.html#optimistic-concurrency
62
+ # @return [Valkyrie::Persistence::OptimisticLockToken]
43
63
  def token
44
64
  Valkyrie::Persistence::OptimisticLockToken.new(adapter_id: resource_factory.adapter_id, token: version)
45
65
  end
46
66
 
67
+ # Access the "_version_" field value within the Solr Document
68
+ # @return [String]
47
69
  def version
48
70
  solr_document.fetch('_version_', nil)
49
71
  end
50
72
 
73
+ # Retrieve the ID for the Valkyrie Resource in the Solr Document
74
+ # @note this assumes that the ID has been prepended with the string "id-"
75
+ # @return [String]
51
76
  def id
52
77
  solr_document.fetch('id').sub(/^id-/, '')
53
78
  end
54
79
 
80
+ # Construct the Hash containing the Valkyrie Resource attributes using the Solr Document
81
+ # @note this filters for attributes which have been indexed as stored multivalued texts (tsim)
82
+ # @see https://github.com/samvera-labs/valkyrie/blob/master/solr/config/schema.xml
83
+ # @see https://lucene.apache.org/solr/guide/defining-fields.html#defining-fields
84
+ # @return [Hash]
55
85
  def attribute_hash
56
86
  build_literals(strip_tsim(solr_document.select do |k, _v|
57
87
  k.end_with?("tsim")
58
88
  end))
59
89
  end
60
90
 
91
+ # Removes the substring "_tsim" within Hash keys
92
+ # This is used when mapping Solr Document Hashes into Valkyrie Resource attributes
93
+ # @see #attribute_hash
94
+ # @param [Hash] hsh
95
+ # @return [Hash]
61
96
  def strip_tsim(hsh)
62
97
  Hash[
63
98
  hsh.map do |k, v|
@@ -66,8 +101,13 @@ module Valkyrie::Persistence::Solr
66
101
  ]
67
102
  end
68
103
 
104
+ # Class modeling a key/value pair within a Solr Document
69
105
  class Property
70
106
  attr_reader :key, :value, :document
107
+
108
+ # @param [String] key
109
+ # @param [String] value
110
+ # @param [Hash] document
71
111
  def initialize(key, value, document)
72
112
  @key = key
73
113
  @value = value
@@ -75,6 +115,9 @@ module Valkyrie::Persistence::Solr
75
115
  end
76
116
  end
77
117
 
118
+ # Populates an existing Hash with SolrValue objects keyed to the existing Hash keys
119
+ # @param [Hash] hsh
120
+ # @return [Hash]
78
121
  def build_literals(hsh)
79
122
  hsh.each_with_object({}) do |(key, value), output|
80
123
  next if key.end_with?("_lang")
@@ -83,6 +126,7 @@ module Valkyrie::Persistence::Solr
83
126
  end
84
127
  end
85
128
 
129
+ # Abstract base class for values persisted in Solr
86
130
  class SolrValue < ::Valkyrie::ValueMapper
87
131
  end
88
132
 
@@ -90,11 +134,18 @@ module Valkyrie::Persistence::Solr
90
134
  # {RDF::Literal}
91
135
  class RDFLiteralPropertyValue < ::Valkyrie::ValueMapper
92
136
  SolrValue.register(self)
137
+
138
+ # Determines whether or not a Property has a Solr Document specifying the language tag or type for the value
139
+ # @param [Property] value
140
+ # @return [Boolean]
93
141
  def self.handles?(value)
94
142
  value.is_a?(Property) &&
95
143
  (value.document["#{value.key}_lang"] || value.document["#{value.key}_type"])
96
144
  end
97
145
 
146
+ # Map the Property value to RDF literals
147
+ # This ensures that, if possible, RDF::Literals are (re)constructed using language tags and datatypes
148
+ # @return [Array<RDF::Literal>]
98
149
  def result
99
150
  value.value.each_with_index.map do |literal, idx|
100
151
  language = languages[idx]
@@ -109,31 +160,53 @@ module Valkyrie::Persistence::Solr
109
160
  end
110
161
  end
111
162
 
163
+ # Access the languages within the Solr field value
164
+ # @note this assumes that the substring "_lang" is appended to the Solr field name specifying the supported language tags
165
+ # @return [Array<String>]
112
166
  def languages
113
167
  value.document.fetch("#{value.key}_lang", [])
114
168
  end
115
169
 
170
+ # Access the datatypes within the Solr field value
171
+ # @note this assumes that the substring "_type" is appended to the Solr field name specifying the supported datatypes
172
+ # @return [Array<String>]
116
173
  def datatypes
117
174
  value.document.fetch("#{value.key}_type", [])
118
175
  end
119
176
  end
120
177
 
178
+ # Class for handling general string-serialized values in Solr fields
121
179
  class PropertyValue < ::Valkyrie::ValueMapper
122
180
  SolrValue.register(self)
181
+
182
+ # Determines whether or not an Object is a Property
183
+ # @param [Object] value
184
+ # @return [Boolean]
123
185
  def self.handles?(value)
124
186
  value.is_a?(Property)
125
187
  end
126
188
 
189
+ # Constructs an EnumerableValue and accesses the String value
190
+ # @return [String]
127
191
  def result
128
192
  calling_mapper.for(value.value).result
129
193
  end
130
194
  end
195
+
196
+ # Class for handling multiple values in Solr fields
131
197
  class EnumerableValue < ::Valkyrie::ValueMapper
132
198
  SolrValue.register(self)
199
+
200
+ # Determines whether or not a value behaves like an enumerable value
201
+ # @param [Object] value
202
+ # @return [Boolean]
133
203
  def self.handles?(value)
134
204
  value.respond_to?(:each)
135
205
  end
136
206
 
207
+ # Constructs SolrValue objects for each value and returns the String representation
208
+ # For cases where there is only one value, only a single String is returned
209
+ # @return [String, Array<String>]
137
210
  def result
138
211
  if value.length == 1
139
212
  calling_mapper.for(value.first).result
@@ -148,10 +221,17 @@ module Valkyrie::Persistence::Solr
148
221
  # Converts a stored ID value in solr into a {Valkyrie::ID}
149
222
  class IDValue < ::Valkyrie::ValueMapper
150
223
  SolrValue.register(self)
224
+
225
+ # Determines whether or not a string representation of an Object is prefixed with "id-"
226
+ # @note this determines whether or not this should be mapped to a Valkyrie ID
227
+ # @param [Object] value
228
+ # @return [Boolean]
151
229
  def self.handles?(value)
152
230
  value.to_s.start_with?("id-")
153
231
  end
154
232
 
233
+ # Constructs a new Valkyrie::ID object using the ID parsed from the Solr field
234
+ # @return [Valkyrie::ID]
155
235
  def result
156
236
  Valkyrie::ID.new(value.sub(/^id-/, ''))
157
237
  end
@@ -160,10 +240,17 @@ module Valkyrie::Persistence::Solr
160
240
  # Converts a stored URI value in solr into a {RDF::URI}
161
241
  class URIValue < ::Valkyrie::ValueMapper
162
242
  SolrValue.register(self)
243
+
244
+ # Determines whether or not a string representation of an Object is prefixed with "uri-"
245
+ # @note this determines whether or not this should be mapped to a URI
246
+ # @param [Object] value
247
+ # @return [Boolean]
163
248
  def self.handles?(value)
164
249
  value.to_s.start_with?("uri-")
165
250
  end
166
251
 
252
+ # Constructs a new RDF::URI object using the ID parsed from the Solr field
253
+ # @return [RDF::URI]
167
254
  def result
168
255
  ::RDF::URI.new(value.sub(/^uri-/, ''))
169
256
  end
@@ -172,28 +259,45 @@ module Valkyrie::Persistence::Solr
172
259
  # Converts a nested resource in solr into a {Valkyrie::Resource}
173
260
  class NestedResourceValue < ::Valkyrie::ValueMapper
174
261
  SolrValue.register(self)
262
+
263
+ # Determines whether or not a string representation of an Object is prefixed with "serialized-"
264
+ # @note this determines whether or not this should be mapped to a Hash
265
+ # @param [Object] value
266
+ # @return [Boolean]
175
267
  def self.handles?(value)
176
268
  value.to_s.start_with?("serialized-")
177
269
  end
178
270
 
271
+ # Uses the NestedResourceConverter to parse the JSON-serialized Hash and convert this to a Valkyrie Resource attribute Hash
272
+ # @return [Hash]
179
273
  def result
180
274
  NestedResourceConverter.for(JSON.parse(json, symbolize_names: true)).result
181
275
  end
182
276
 
277
+ # Parses the JSON-serialized Hash value from the Solr field
278
+ # @return [String]
183
279
  def json
184
280
  value.sub(/^serialized-/, '')
185
281
  end
186
282
  end
187
283
 
284
+ # Abstract base class for handling nested Hashes (serializing resources in the RDF)
188
285
  class NestedResourceConverter < ::Valkyrie::ValueMapper
189
286
  end
190
287
 
288
+ # Class for handling multiple serialized Hashes in Solr fields
191
289
  class NestedEnumerable < ::Valkyrie::ValueMapper
192
290
  NestedResourceConverter.register(self)
291
+
292
+ # Determines whether or not an Object is an Array
293
+ # @param [Object] value
294
+ # @return [Boolean]
193
295
  def self.handles?(value)
194
296
  value.is_a?(Array)
195
297
  end
196
298
 
299
+ # Uses the NestedResourceConverter to parse each element in the enumerable value, mapping the converted attributes to each
300
+ # @return [Array<Hash>]
197
301
  def result
198
302
  value.map do |v|
199
303
  calling_mapper.for(v).result
@@ -201,45 +305,75 @@ module Valkyrie::Persistence::Solr
201
305
  end
202
306
  end
203
307
 
308
+ # Class for handling serialized Hashes for Valkyrie IDs in Solr fields
204
309
  class NestedResourceID < ::Valkyrie::ValueMapper
205
310
  NestedResourceConverter.register(self)
311
+
312
+ # Determines whether or not an Object is a Property
313
+ # @param [Object] value
314
+ # @return [Boolean]
206
315
  def self.handles?(value)
207
316
  value.is_a?(Hash) && value[:id] && !value[:internal_resource]
208
317
  end
209
318
 
319
+ # Constructs a Valkyrie::ID object using the value keyed to :id in the Property Hash value
320
+ # @return [Valkyrie::ID]
210
321
  def result
211
322
  Valkyrie::ID.new(value[:id])
212
323
  end
213
324
  end
214
325
 
326
+ # Class for handling serialized Hashes for URIs in Solr fields
215
327
  class NestedResourceURI < ::Valkyrie::ValueMapper
216
328
  NestedResourceConverter.register(self)
329
+
330
+ # Determines whether or not an Object is a Hash with a URI string mapped to the "@id" key
331
+ # @note this is used to determine whether or not this is a nested URI for an attribute
332
+ # @param [Object] value
333
+ # @return [Boolean]
217
334
  def self.handles?(value)
218
335
  value.is_a?(Hash) && value[:@id]
219
336
  end
220
337
 
338
+ # Constructs a RDF::URI object using the value keyed to :@id in the Property Hash value
339
+ # @return [RDF::URI]
221
340
  def result
222
341
  RDF::URI(value[:@id])
223
342
  end
224
343
  end
225
344
 
345
+ # Class for handling serialized Hashes for RDF literals in Solr fields
226
346
  class NestedResourceLiteral < ::Valkyrie::ValueMapper
227
347
  NestedResourceConverter.register(self)
348
+
349
+ # Determines whether or not an Object is a Hash with a string mapped to the "@value" key
350
+ # @note this is used to determine whether or not this is a RDF literal for an attribute
351
+ # @param [Object] value
352
+ # @return [Boolean]
228
353
  def self.handles?(value)
229
354
  value.is_a?(Hash) && value[:@value]
230
355
  end
231
356
 
357
+ # Constructs a RDF::Literal object using the value keyed to :@value and language tag keyed to :@language in the Property Hash value
358
+ # @return [RDF::URI]
232
359
  def result
233
360
  RDF::Literal.new(value[:@value], language: value[:@language])
234
361
  end
235
362
  end
236
363
 
364
+ # Class for handling generic serialized Hashes in Solr fields
237
365
  class NestedResourceHash < ::Valkyrie::ValueMapper
238
366
  NestedResourceConverter.register(self)
367
+
368
+ # Determines whether or not an Object is a Hash
369
+ # @param [Object] value
370
+ # @return [Boolean]
239
371
  def self.handles?(value)
240
372
  value.is_a?(Hash)
241
373
  end
242
374
 
375
+ # Constructs a Hash mapping each converted Solr field value to the existing keys in the Property Hash value
376
+ # @return [Hash]
243
377
  def result
244
378
  Hash[
245
379
  value.map do |k, v|
@@ -252,10 +386,18 @@ module Valkyrie::Persistence::Solr
252
386
  # Converts an boolean in solr into an {Boolean}
253
387
  class BooleanValue < ::Valkyrie::ValueMapper
254
388
  SolrValue.register(self)
389
+
390
+ # Determines whether or not a string representation of an Object is prefixed with "boolean-"
391
+ # @note this determines whether or not this should be mapped to a boolean value
392
+ # @param [Object] value
393
+ # @return [Boolean]
255
394
  def self.handles?(value)
256
395
  value.to_s.start_with?("boolean-")
257
396
  end
258
397
 
398
+ # Parses the Solr field value for the substring "true"
399
+ # Should "true" be present, True is returned
400
+ # @return [Boolean]
259
401
  def result
260
402
  val = value.sub(/^boolean-/, '')
261
403
  val.casecmp("true").zero?
@@ -265,18 +407,42 @@ module Valkyrie::Persistence::Solr
265
407
  # Converts an integer in solr into an {Integer}
266
408
  class IntegerValue < ::Valkyrie::ValueMapper
267
409
  SolrValue.register(self)
410
+
411
+ # Determines whether or not a string representation of an Object is prefixed with "integer-"
412
+ # @note this determines whether or not this should be mapped to an integer
413
+ # @param [Object] value
414
+ # @return [Boolean]
268
415
  def self.handles?(value)
269
416
  value.to_s.start_with?("integer-")
270
417
  end
271
418
 
419
+ # Parses and casts the Solr field value into the integer value
420
+ # @return [Integer]
272
421
  def result
273
422
  value.sub(/^integer-/, '').to_i
274
423
  end
275
424
  end
276
425
 
426
+ # Converts a float in solr into a {Float}
427
+ class FloatValue < ::Valkyrie::ValueMapper
428
+ SolrValue.register(self)
429
+ def self.handles?(value)
430
+ value.to_s.start_with?("float-")
431
+ end
432
+
433
+ def result
434
+ value.sub(/^float-/, '').to_f
435
+ end
436
+ end
437
+
277
438
  # Converts a datetime in Solr into a {DateTime}
278
439
  class DateTimeValue < ::Valkyrie::ValueMapper
279
440
  SolrValue.register(self)
441
+
442
+ # Determines whether or not a string representation of an Object is prefixed with "datetime-"
443
+ # @note this determines whether or not this should be mapped to a DateTime object
444
+ # @param [Object] value
445
+ # @return [Boolean]
280
446
  def self.handles?(value)
281
447
  return false unless value.to_s.start_with?("datetime-")
282
448
  DateTime.iso8601(value.sub(/^datetime-/, '')).utc
@@ -284,6 +450,8 @@ module Valkyrie::Persistence::Solr
284
450
  false
285
451
  end
286
452
 
453
+ # Parses and casts the Solr field value into a UTC DateTime value
454
+ # @return [Time]
287
455
  def result
288
456
  DateTime.parse(value.sub(/^datetime-/, '')).utc
289
457
  end
@@ -10,13 +10,11 @@ module Valkyrie::Persistence::Solr
10
10
 
11
11
  # @param adapter [Valkyrie::Persistence::Solr::MetadataAdapter] The adapter with the
12
12
  # configured solr connection.
13
- # @note (see Valkyrie::Persistence::Memory::Persister#initialize)
14
13
  def initialize(adapter:)
15
14
  @adapter = adapter
16
15
  end
17
16
 
18
- # (see Valkyrie::Persistence::Memory::Persister#save)
19
- #
17
+ # Persists a Valkyrie Resource into a Solr index
20
18
  # @note Fields are saved using Solr's dynamic fields functionality.
21
19
  # If the text has length > 1000, it is stored as *_tsim
22
20
  # otherwise it's stored as *_tsim, *_ssim, and *_tesim
@@ -24,25 +22,35 @@ module Valkyrie::Persistence::Solr
24
22
  # 'title_tsim'
25
23
  # 'title_ssim'
26
24
  # 'title_tesim'
25
+ # @param [Valkyrie::Resource] resource
26
+ # @return [Valkyrie::Resource] the persisted resource
27
27
  def save(resource:)
28
28
  repository([resource]).persist.first
29
29
  end
30
30
 
31
- # (see Valkyrie::Persistence::Memory::Persister#save_all)
31
+ # Persists a set of Valkyrie Resources into a Solr index
32
+ # @param [Array<Valkyrie::Resource>] resources
33
+ # @return [Valkyrie::Resource] the set of persisted resources
32
34
  def save_all(resources:)
33
35
  repository(resources).persist
34
36
  end
35
37
 
36
- # (see Valkyrie::Persistence::Memory::Persister#delete)
38
+ # Deletes a Valkyrie Resource persisted into a Solr index
39
+ # @param [Valkyrie::Resource] resource
40
+ # @return [Valkyrie::Resource] the deleted resource
37
41
  def delete(resource:)
38
42
  repository([resource]).delete.first
39
43
  end
40
44
 
45
+ # Delete the Solr index of all Documents
41
46
  def wipe!
42
47
  connection.delete_by_query("*:*")
43
48
  connection.commit
44
49
  end
45
50
 
51
+ # Constructs a Solr::Repository object for a set of Valkyrie Resources
52
+ # @param [Array<Valkyrie::Resource>] resources
53
+ # @return [Valkyrie::Persistence::Solr::Repository]
46
54
  def repository(resources)
47
55
  Valkyrie::Persistence::Solr::Repository.new(resources: resources, connection: connection, resource_factory: resource_factory)
48
56
  end