valkyrie 2.1.0 → 3.0.0.pre.beta.1

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +71 -36
  3. data/.lando.yml +58 -0
  4. data/.rubocop.yml +11 -1
  5. data/.tool-versions +1 -1
  6. data/CHANGELOG.md +94 -13
  7. data/CONTRIBUTING.md +30 -8
  8. data/README.md +24 -48
  9. data/Rakefile +26 -20
  10. data/db/config.yml +3 -10
  11. data/lib/generators/valkyrie/resource_generator.rb +3 -3
  12. data/lib/valkyrie/change_set.rb +3 -3
  13. data/lib/valkyrie/id.rb +12 -19
  14. data/lib/valkyrie/indexers/access_controls_indexer.rb +17 -17
  15. data/lib/valkyrie/persistence/buffered_persister.rb +2 -2
  16. data/lib/valkyrie/persistence/composite_persister.rb +3 -3
  17. data/lib/valkyrie/persistence/custom_query_container.rb +8 -16
  18. data/lib/valkyrie/persistence/fedora/list_node.rb +43 -43
  19. data/lib/valkyrie/persistence/fedora/metadata_adapter.rb +5 -1
  20. data/lib/valkyrie/persistence/fedora/ordered_list.rb +90 -90
  21. data/lib/valkyrie/persistence/fedora/ordered_reader.rb +5 -5
  22. data/lib/valkyrie/persistence/fedora/permissive_schema.rb +1 -1
  23. data/lib/valkyrie/persistence/fedora/persister/model_converter.rb +15 -16
  24. data/lib/valkyrie/persistence/fedora/persister/orm_converter.rb +14 -19
  25. data/lib/valkyrie/persistence/fedora/persister.rb +83 -83
  26. data/lib/valkyrie/persistence/fedora/query_service.rb +39 -41
  27. data/lib/valkyrie/persistence/memory/persister.rb +51 -35
  28. data/lib/valkyrie/persistence/memory/query_service.rb +26 -30
  29. data/lib/valkyrie/persistence/postgres/orm_converter.rb +52 -52
  30. data/lib/valkyrie/persistence/postgres/persister.rb +4 -1
  31. data/lib/valkyrie/persistence/postgres/query_service.rb +34 -34
  32. data/lib/valkyrie/persistence/shared/json_value_mapper.rb +1 -1
  33. data/lib/valkyrie/persistence/solr/metadata_adapter.rb +15 -3
  34. data/lib/valkyrie/persistence/solr/model_converter.rb +323 -340
  35. data/lib/valkyrie/persistence/solr/orm_converter.rb +4 -4
  36. data/lib/valkyrie/persistence/solr/persister.rb +16 -4
  37. data/lib/valkyrie/persistence/solr/queries/find_by_alternate_identifier_query.rb +1 -1
  38. data/lib/valkyrie/persistence/solr/queries/find_by_id_query.rb +1 -1
  39. data/lib/valkyrie/persistence/solr/queries/find_members_query.rb +1 -1
  40. data/lib/valkyrie/persistence/solr/query_service.rb +12 -12
  41. data/lib/valkyrie/persistence/solr/repository.rb +17 -7
  42. data/lib/valkyrie/resource/access_controls.rb +1 -1
  43. data/lib/valkyrie/resource.rb +0 -1
  44. data/lib/valkyrie/specs/shared_specs/change_set.rb +1 -1
  45. data/lib/valkyrie/specs/shared_specs/file.rb +1 -0
  46. data/lib/valkyrie/specs/shared_specs/persister.rb +22 -4
  47. data/lib/valkyrie/specs/shared_specs/queries.rb +7 -0
  48. data/lib/valkyrie/specs/shared_specs/resource.rb +1 -1
  49. data/lib/valkyrie/specs/shared_specs/storage_adapter.rb +19 -0
  50. data/lib/valkyrie/specs/shared_specs/write_only/metadata_adapter.rb +62 -0
  51. data/lib/valkyrie/specs/shared_specs.rb +2 -0
  52. data/lib/valkyrie/storage/disk.rb +24 -1
  53. data/lib/valkyrie/storage/fedora.rb +17 -17
  54. data/lib/valkyrie/storage_adapter.rb +12 -12
  55. data/lib/valkyrie/types.rb +1 -1
  56. data/lib/valkyrie/version.rb +1 -1
  57. data/lib/valkyrie/vocab/pcdm_use.rb +12 -0
  58. data/lib/valkyrie.rb +13 -27
  59. data/tasks/dev.rake +14 -51
  60. data/valkyrie.gemspec +3 -6
  61. metadata +25 -63
  62. data/.docker-stack/valkyrie-development/docker-compose.yml +0 -53
  63. data/.docker-stack/valkyrie-test/docker-compose.yml +0 -53
  64. data/tasks/docker.rake +0 -31
@@ -313,7 +313,6 @@ module Valkyrie::Persistence::Fedora
313
313
  end
314
314
 
315
315
  # Generate a new RDF hash URI for the "child" graph for the ModelConverter::Property
316
- # @see https://github.com/fcrepo4/fcrepo4/blob/master/fcrepo-kernel-modeshape/src/main/java/org/fcrepo/kernel/modeshape/rdf/JcrRdfTools.java#L455
317
316
  # @return [RDF::Graph]
318
317
  def subject_uri
319
318
  @subject_uri ||= ::RDF::URI(RDF::Node.new.to_s.gsub("_:", "#"))
@@ -325,21 +324,21 @@ module Valkyrie::Persistence::Fedora
325
324
  class MappedFedoraValue < ::Valkyrie::ValueMapper
326
325
  private
327
326
 
328
- # Map a default Ruby data type
329
- # (This maps the existing Property to a FedoraValue first)
330
- # @param [Object] converted_value
331
- # @return [Valkyrie::Persistence::Fedora::Persister::ModelConverter::Property]
332
- def map_value(converted_value:)
333
- calling_mapper.for(
334
- Property.new(
335
- value.subject,
336
- value.key,
337
- converted_value,
338
- value.adapter,
339
- value.resource
340
- )
341
- ).result
342
- end
327
+ # Map a default Ruby data type
328
+ # (This maps the existing Property to a FedoraValue first)
329
+ # @param [Object] converted_value
330
+ # @return [Valkyrie::Persistence::Fedora::Persister::ModelConverter::Property]
331
+ def map_value(converted_value:)
332
+ calling_mapper.for(
333
+ Property.new(
334
+ value.subject,
335
+ value.key,
336
+ converted_value,
337
+ value.adapter,
338
+ value.resource
339
+ )
340
+ ).result
341
+ end
343
342
  end
344
343
 
345
344
  # Class mapping Property objects for Valkyrie IDs using typed RDF literals
@@ -97,11 +97,10 @@ module Valkyrie::Persistence::Fedora
97
97
  end
98
98
  end
99
99
 
100
- # Class for handling cases where blacklisted values should not be mapped
101
- class BlacklistedValue < ::Valkyrie::ValueMapper
100
+ # Class for handling cases where deny listed values should not be mapped
101
+ class DenylistedValue < ::Valkyrie::ValueMapper
102
102
  FedoraValue.register(self)
103
-
104
- # Determines whether or not the value has a blacklisted namespace for the RDF statement object
103
+ # Determines whether or not the value has a denied namespace for the RDF statement object
105
104
  # (i. e. avoid attempting to map any RDF statements making assertions about LDP containers or resource internal to Fedora)
106
105
  # @param [Property] value
107
106
  # @return [Boolean]
@@ -109,7 +108,7 @@ module Valkyrie::Persistence::Fedora
109
108
  value.statement.object.to_s.start_with?("http://www.w3.org/ns/ldp", "http://fedora.info")
110
109
  end
111
110
 
112
- # Provide the NullApplicator Class for any Property in a blacklisted namespace
111
+ # Provide the NullApplicator Class for any Property in a deny listed namespace
113
112
  def result
114
113
  NullApplicator
115
114
  end
@@ -318,7 +317,7 @@ module Valkyrie::Persistence::Fedora
318
317
  # Casts the value of the RDF literal into an Applicator for DateTime values
319
318
  # @return [Applicator]
320
319
  def result
321
- value.statement.object = ::DateTime.iso8601(value.statement.object.to_s).utc
320
+ value.statement.object = ::DateTime.iso8601(value.statement.object.to_s).new_offset(0)
322
321
  calling_mapper.for(Property.new(statement: value.statement, scope: value.scope, adapter: value.adapter)).result
323
322
  end
324
323
  end
@@ -394,7 +393,7 @@ module Valkyrie::Persistence::Fedora
394
393
  # Casts the value of the RDF literal into an Applicator for DateTime values
395
394
  # @return [Applicator]
396
395
  def result
397
- value.statement.object = Time.parse(value.statement.object.to_s).utc
396
+ value.statement.object = DateTime.parse(value.statement.object.to_s).new_offset(0)
398
397
  calling_mapper.for(Property.new(statement: value.statement, scope: value.scope, adapter: value.adapter)).result
399
398
  end
400
399
  end
@@ -522,7 +521,7 @@ module Valkyrie::Persistence::Fedora
522
521
  # @param [Hash] hsh a new or existing Hash of attribute for Valkyrie resource attributes
523
522
  # @return [Hash]
524
523
  def apply_to(hsh)
525
- return if blacklist?(key)
524
+ return if deny?(key)
526
525
  hsh[key.to_sym] = if hsh.key?(key.to_sym)
527
526
  Array.wrap(hsh[key.to_sym]) + cast_array(values)
528
527
  else
@@ -541,13 +540,13 @@ module Valkyrie::Persistence::Fedora
541
540
  key
542
541
  end
543
542
 
544
- # Determines whether or not a key is blacklisted for mapping
543
+ # Determines whether or not a key is on the deny list for mapping
545
544
  # (For example <http://fedora.info/definitions> assertions are not mapped to Valkyrie attributes)
546
545
  # @param [Symbol] key
547
546
  # @return [Boolean]
548
- def blacklist?(key)
549
- blacklist.each do |blacklist_item|
550
- return true if key.start_with?(blacklist_item)
547
+ def deny?(key)
548
+ denylist.each do |denylist_item|
549
+ return true if key.start_with?(denylist_item)
551
550
  end
552
551
  false
553
552
  end
@@ -556,16 +555,12 @@ module Valkyrie::Persistence::Fedora
556
555
  # @param [Object] values
557
556
  # @return [Array<Object>]
558
557
  def cast_array(values)
559
- if values.is_a?(Time)
560
- [values]
561
- else
562
- Array(values)
563
- end
558
+ Array(values)
564
559
  end
565
560
 
566
- # Retrieve a list of blacklisted URIs for predicates
561
+ # Retrieve a list of denied URIs for predicates
567
562
  # @return [Array<String>]
568
- def blacklist
563
+ def denylist
569
564
  [
570
565
  "http://fedora.info/definitions",
571
566
  "http://www.iana.org/assignments/relation/last"
@@ -13,7 +13,7 @@ module Valkyrie::Persistence::Fedora
13
13
  end
14
14
 
15
15
  # (see Valkyrie::Persistence::Memory::Persister#save)
16
- def save(resource:)
16
+ def save(resource:, external_resource: false)
17
17
  initialize_repository
18
18
  internal_resource = resource.dup
19
19
  internal_resource.created_at ||= Time.current
@@ -35,6 +35,8 @@ module Valkyrie::Persistence::Fedora
35
35
  alternate_resources ? save_reference_to_resource(persisted_resource, alternate_resources) : persisted_resource
36
36
  rescue Ldp::PreconditionFailed
37
37
  raise Valkyrie::Persistence::StaleObjectError, "The object #{internal_resource.id} has been updated by another process."
38
+ rescue Ldp::Gone
39
+ raise Valkyrie::Persistence::ObjectNotFoundError, "The object #{resource.id} is previously persisted but not found at save time."
38
40
  end
39
41
 
40
42
  # (see Valkyrie::Persistence::Memory::Persister#save_all)
@@ -86,97 +88,95 @@ module Valkyrie::Persistence::Fedora
86
88
 
87
89
  private
88
90
 
89
- # Ensure that all alternate IDs for a given resource are persisted
90
- # @param [Valkyrie::Resource] resource
91
- # @return [Array<Valkyrie::Persistence::Fedora::AlternateIdentifier>]
92
- def find_or_create_alternate_ids(resource)
93
- return nil unless resource.try(:alternate_ids)
94
-
95
- resource.alternate_ids.map do |alternate_identifier|
96
- begin
97
- adapter.query_service.find_by(id: alternate_identifier)
98
- rescue ::Valkyrie::Persistence::ObjectNotFoundError
99
- alternate_resource = ::Valkyrie::Persistence::Fedora::AlternateIdentifier.new(id: alternate_identifier)
100
- adapter.persister.save(resource: alternate_resource)
101
- end
102
- end
91
+ # Ensure that all alternate IDs for a given resource are persisted
92
+ # @param [Valkyrie::Resource] resource
93
+ # @return [Array<Valkyrie::Persistence::Fedora::AlternateIdentifier>]
94
+ def find_or_create_alternate_ids(resource)
95
+ return nil unless resource.try(:alternate_ids)
96
+
97
+ resource.alternate_ids.map do |alternate_identifier|
98
+ adapter.query_service.find_by(id: alternate_identifier)
99
+ rescue ::Valkyrie::Persistence::ObjectNotFoundError
100
+ alternate_resource = ::Valkyrie::Persistence::Fedora::AlternateIdentifier.new(id: alternate_identifier)
101
+ adapter.persister.save(resource: alternate_resource)
103
102
  end
103
+ end
104
104
 
105
- # Ensure that any Resources referenced by alternate IDs are deleted when a Resource has these IDs deleted
106
- # @param [Valkyrie::Resource] updated_resource
107
- def cleanup_alternate_resources(updated_resource)
108
- persisted_resource = adapter.query_service.find_by(id: updated_resource.id)
109
- removed_identifiers = persisted_resource.alternate_ids - updated_resource.alternate_ids
105
+ # Ensure that any Resources referenced by alternate IDs are deleted when a Resource has these IDs deleted
106
+ # @param [Valkyrie::Resource] updated_resource
107
+ def cleanup_alternate_resources(updated_resource)
108
+ persisted_resource = adapter.query_service.find_by(id: updated_resource.id)
109
+ removed_identifiers = persisted_resource.alternate_ids - updated_resource.alternate_ids
110
110
 
111
- removed_identifiers.each do |removed_id|
112
- adapter.persister.delete(resource: adapter.query_service.find_by(id: removed_id))
113
- end
111
+ removed_identifiers.each do |removed_id|
112
+ adapter.persister.delete(resource: adapter.query_service.find_by(id: removed_id))
114
113
  end
114
+ end
115
115
 
116
- # Ensure that any Resources referenced by alternate IDs are persisted when a Resource has these IDs added
117
- # @param [Valkyrie::Resource] resource
118
- # @param [Array<Valkyrie::Persistence::Fedora::AlternateIdentifier>] alternate_resources
119
- # @return [Valkyrie::Resource]
120
- def save_reference_to_resource(resource, alternate_resources)
121
- alternate_resources.each do |alternate_resource|
122
- alternate_resource.references = resource.id
123
- adapter.persister.save(resource: alternate_resource)
124
- end
125
-
126
- resource
116
+ # Ensure that any Resources referenced by alternate IDs are persisted when a Resource has these IDs added
117
+ # @param [Valkyrie::Resource] resource
118
+ # @param [Array<Valkyrie::Persistence::Fedora::AlternateIdentifier>] alternate_resources
119
+ # @return [Valkyrie::Resource]
120
+ def save_reference_to_resource(resource, alternate_resources)
121
+ alternate_resources.each do |alternate_resource|
122
+ alternate_resource.references = resource.id
123
+ adapter.persister.save(resource: alternate_resource)
127
124
  end
128
125
 
129
- # Generate the lock token for a Resource, and set it for attribute
130
- # @param [Valkyrie::Resource] resource
131
- # @return [Valkyrie::Persistence::OptimisticLockToken]
132
- # @see https://github.com/samvera-labs/valkyrie/wiki/Optimistic-Locking
133
- # @note Fedora's last modified response is not granular enough to produce an effective lock token
134
- # therefore, we use the same implementation as the memory adapter. This could fail to lock a
135
- # resource if Fedora updated this resource between the time it was saved and Valkyrie created
136
- # the token.
137
- def generate_lock_token(resource)
138
- return unless resource.optimistic_locking_enabled?
139
- token = Valkyrie::Persistence::OptimisticLockToken.new(adapter_id: adapter.id, token: Time.now.to_r)
140
- resource.send("#{Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK}=", token)
141
- end
126
+ resource
127
+ end
142
128
 
143
- # Determine whether or not a lock token is still valid for a persisted Resource
144
- # If the persisted Resource has been updated since it was last read into memory,
145
- # then the resouce in memory has been invalidated and Valkyrie::Persistence::StaleObjectError
146
- # is raised.
147
- # @param [Valkyrie::Resource] resource
148
- # @see https://github.com/samvera-labs/valkyrie/wiki/Optimistic-Locking
149
- # @raise [Valkyrie::Persistence::StaleObjectError]
150
- def validate_lock_token(resource)
151
- return unless resource.optimistic_locking_enabled?
152
- return if resource.id.blank?
153
-
154
- current_lock_token = resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK].find { |lock_token| lock_token.adapter_id == adapter.id }
155
- return if current_lock_token.blank?
156
-
157
- retrieved_lock_tokens = adapter.query_service.find_by(id: resource.id)[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK]
158
- retrieved_lock_token = retrieved_lock_tokens.find { |lock_token| lock_token.adapter_id == adapter.id }
159
- return if retrieved_lock_token.blank?
160
-
161
- raise Valkyrie::Persistence::StaleObjectError, "The object #{resource.id} has been updated by another process." unless current_lock_token == retrieved_lock_token
162
- end
129
+ # Generate the lock token for a Resource, and set it for attribute
130
+ # @param [Valkyrie::Resource] resource
131
+ # @return [Valkyrie::Persistence::OptimisticLockToken]
132
+ # @see https://github.com/samvera-labs/valkyrie/wiki/Optimistic-Locking
133
+ # @note Fedora's last modified response is not granular enough to produce an effective lock token
134
+ # therefore, we use the same implementation as the memory adapter. This could fail to lock a
135
+ # resource if Fedora updated this resource between the time it was saved and Valkyrie created
136
+ # the token.
137
+ def generate_lock_token(resource)
138
+ return unless resource.optimistic_locking_enabled?
139
+ token = Valkyrie::Persistence::OptimisticLockToken.new(adapter_id: adapter.id, token: Time.now.to_r)
140
+ resource.send("#{Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK}=", token)
141
+ end
163
142
 
164
- # Retrieve the lock token that holds Fedora's system-managed last-modified date
165
- # @param [Valkyrie::Resource] resource
166
- # @return [Valkyrie::Persistence::OptimisticLockToken]
167
- def native_lock_token(resource)
168
- return unless resource.optimistic_locking_enabled?
169
- resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK].find { |lock_token| lock_token.adapter_id.to_s == "native-#{adapter.id}" }
170
- end
143
+ # Determine whether or not a lock token is still valid for a persisted Resource
144
+ # If the persisted Resource has been updated since it was last read into memory,
145
+ # then the resouce in memory has been invalidated and Valkyrie::Persistence::StaleObjectError
146
+ # is raised.
147
+ # @param [Valkyrie::Resource] resource
148
+ # @see https://github.com/samvera-labs/valkyrie/wiki/Optimistic-Locking
149
+ # @raise [Valkyrie::Persistence::StaleObjectError]
150
+ def validate_lock_token(resource)
151
+ return unless resource.optimistic_locking_enabled?
152
+ return if resource.id.blank?
153
+
154
+ current_lock_token = resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK].find { |lock_token| lock_token.adapter_id == adapter.id }
155
+ return if current_lock_token.blank?
156
+
157
+ retrieved_lock_tokens = adapter.query_service.find_by(id: resource.id)[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK]
158
+ retrieved_lock_token = retrieved_lock_tokens.find { |lock_token| lock_token.adapter_id == adapter.id }
159
+ return if retrieved_lock_token.blank?
160
+
161
+ raise Valkyrie::Persistence::StaleObjectError, "The object #{resource.id} has been updated by another process." unless current_lock_token == retrieved_lock_token
162
+ end
171
163
 
172
- # Set Fedora request headers:
173
- # * `Prefer: handling=lenient; received="minimal"` allows us to avoid sending all server-managed triples
174
- # * `If-Unmodified-Since` triggers Fedora's server-side optimistic locking
175
- # @param request [Faraday::Request]
176
- # @param lock_token [Valkyrie::Persistence::OptimisticLockToken]
177
- def update_request_headers(request, lock_token)
178
- request.headers["Prefer"] = "handling=lenient; received=\"minimal\""
179
- request.headers["If-Unmodified-Since"] = lock_token.token if lock_token
180
- end
164
+ # Retrieve the lock token that holds Fedora's system-managed last-modified date
165
+ # @param [Valkyrie::Resource] resource
166
+ # @return [Valkyrie::Persistence::OptimisticLockToken]
167
+ def native_lock_token(resource)
168
+ return unless resource.optimistic_locking_enabled?
169
+ resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK].find { |lock_token| lock_token.adapter_id.to_s == "native-#{adapter.id}" }
170
+ end
171
+
172
+ # Set Fedora request headers:
173
+ # * `Prefer: handling=lenient; received="minimal"` allows us to avoid sending all server-managed triples
174
+ # * `If-Unmodified-Since` triggers Fedora's server-side optimistic locking
175
+ # @param request [Faraday::Request]
176
+ # @param lock_token [Valkyrie::Persistence::OptimisticLockToken]
177
+ def update_request_headers(request, lock_token)
178
+ request.headers["Prefer"] = "handling=lenient; received=\"minimal\""
179
+ request.headers["If-Unmodified-Since"] = lock_token.token if lock_token
180
+ end
181
181
  end
182
182
  end
@@ -31,11 +31,9 @@ module Valkyrie::Persistence::Fedora
31
31
  def find_many_by_ids(ids:)
32
32
  ids = ids.uniq
33
33
  ids.map do |id|
34
- begin
35
- find_by(id: id)
36
- rescue ::Valkyrie::Persistence::ObjectNotFoundError
37
- nil
38
- end
34
+ find_by(id: id)
35
+ rescue ::Valkyrie::Persistence::ObjectNotFoundError
36
+ nil
39
37
  end.reject(&:nil?)
40
38
  end
41
39
 
@@ -54,7 +52,7 @@ module Valkyrie::Persistence::Fedora
54
52
  # @return [Array<RDF::URI>]
55
53
  def include_uris
56
54
  [
57
- adapter.fedora_version == 5 ? "http://fedora.info/definitions/fcrepo#PreferInboundReferences" : ::RDF::Vocab::Fcrepo4.InboundReferences
55
+ [5, 6].include?(adapter.fedora_version) ? "http://fedora.info/definitions/fcrepo#PreferInboundReferences" : ::RDF::Vocab::Fcrepo4.InboundReferences
58
56
  ]
59
57
  end
60
58
 
@@ -137,44 +135,44 @@ module Valkyrie::Persistence::Fedora
137
135
 
138
136
  private
139
137
 
140
- def find_inverse_reference_ids_by_unordered(resource:, property:)
141
- content = content_with_inbound(id: resource.id)
142
- property_uri = adapter.schema.predicate_for(property: property, resource: nil)
143
- content.graph.query([nil, property_uri, adapter.id_to_uri(resource.id)]).map(&:subject).map { |x| x.to_s.gsub(/#.*/, '') }.map { |x| adapter.uri_to_id(x) }
144
- end
138
+ def find_inverse_reference_ids_by_unordered(resource:, property:)
139
+ content = content_with_inbound(id: resource.id)
140
+ property_uri = adapter.schema.predicate_for(property: property, resource: nil)
141
+ content.graph.query([nil, property_uri, adapter.id_to_uri(resource.id)]).map(&:subject).map { |x| x.to_s.gsub(/#.*/, '') }.map { |x| adapter.uri_to_id(x) }
142
+ end
145
143
 
146
- def find_inverse_references_by_ordered(resource:, property:, ignore_ids: [])
147
- content = content_with_inbound(id: resource.id)
148
- ids = content.graph.query([nil, ::RDF::Vocab::ORE.proxyFor, adapter.id_to_uri(resource.id)]).map(&:subject).map { |x| x.to_s.gsub(/#.*/, '') }.map { |x| adapter.uri_to_id(x) }
149
- ids.uniq!
150
- ids.delete_if { |id| ignore_ids.include? id }
151
- ids.lazy.map { |id| find_by(id: id) }.select { |o| o[property].include?(resource.id) }
152
- end
144
+ def find_inverse_references_by_ordered(resource:, property:, ignore_ids: [])
145
+ content = content_with_inbound(id: resource.id)
146
+ ids = content.graph.query([nil, ::RDF::Vocab::ORE.proxyFor, adapter.id_to_uri(resource.id)]).map(&:subject).map { |x| x.to_s.gsub(/#.*/, '') }.map { |x| adapter.uri_to_id(x) }
147
+ ids.uniq!
148
+ ids.delete_if { |id| ignore_ids.include? id }
149
+ ids.lazy.map { |id| find_by(id: id) }.select { |o| o[property].include?(resource.id) }
150
+ end
153
151
 
154
- # Ensures that an object is (or can be cast into a) Valkyrie::ID
155
- # @return [Valkyrie::ID]
156
- # @raise [ArgumentError]
157
- def validate_id(id)
158
- id = Valkyrie::ID.new(id.to_s) if id.is_a?(String)
159
- raise ArgumentError, 'id must be a Valkyrie::ID' unless id.is_a? Valkyrie::ID
160
- end
152
+ # Ensures that an object is (or can be cast into a) Valkyrie::ID
153
+ # @return [Valkyrie::ID]
154
+ # @raise [ArgumentError]
155
+ def validate_id(id)
156
+ id = Valkyrie::ID.new(id.to_s) if id.is_a?(String)
157
+ raise ArgumentError, 'id must be a Valkyrie::ID' unless id.is_a? Valkyrie::ID
158
+ end
161
159
 
162
- # Resolve a URI for an LDP resource in Fedora and construct a Valkyrie::Resource
163
- # @param uri [RDF::URI]
164
- # @return [Valkyrie::Resource]
165
- # @raise [Valkyrie::Persistence::ObjectNotFoundError]
166
- def resource_from_uri(uri)
167
- resource = Ldp::Resource.for(connection, uri, connection.get(uri))
168
- resource_factory.to_resource(object: resource)
169
- rescue ::Ldp::Gone, ::Ldp::NotFound
170
- raise ::Valkyrie::Persistence::ObjectNotFoundError
171
- end
160
+ # Resolve a URI for an LDP resource in Fedora and construct a Valkyrie::Resource
161
+ # @param uri [RDF::URI]
162
+ # @return [Valkyrie::Resource]
163
+ # @raise [Valkyrie::Persistence::ObjectNotFoundError]
164
+ def resource_from_uri(uri)
165
+ resource = Ldp::Resource.for(connection, uri, connection.get(uri))
166
+ resource_factory.to_resource(object: resource)
167
+ rescue ::Ldp::Gone, ::Ldp::NotFound
168
+ raise ::Valkyrie::Persistence::ObjectNotFoundError
169
+ end
172
170
 
173
- # Ensures that a Valkyrie::Resource has been persisted
174
- # @param resource [Valkyrie::Resource]
175
- # @raise [ArgumentError]
176
- def ensure_persisted(resource)
177
- raise ArgumentError, 'resource is not saved' unless resource.persisted?
178
- end
171
+ # Ensures that a Valkyrie::Resource has been persisted
172
+ # @param resource [Valkyrie::Resource]
173
+ # @raise [ArgumentError]
174
+ def ensure_persisted(resource)
175
+ raise ArgumentError, 'resource is not saved' unless resource.persisted?
176
+ end
179
177
  end
180
178
  end
@@ -5,7 +5,7 @@ module Valkyrie::Persistence::Memory
5
5
  # @note Documentation for persisters in general is maintained here.
6
6
  class Persister
7
7
  attr_reader :adapter
8
- delegate :cache, to: :adapter
8
+ delegate :cache, :query_service, to: :adapter
9
9
 
10
10
  # @param adapter [Valkyrie::Persistence::Memory::MetadataAdapter] The memory adapter which
11
11
  # holds the cache for this persister.
@@ -16,11 +16,18 @@ module Valkyrie::Persistence::Memory
16
16
 
17
17
  # Save a single resource.
18
18
  # @param resource [Valkyrie::Resource] The resource to save.
19
+ # @param external_resource [Boolean] Whether the resource to be saved comes
20
+ # from a different metadata store. Allows a resource to be saved even if
21
+ # it's not already in the store. For example, if you're indexing a
22
+ # resource into Solr - it's saved in your primary metadata store, but not
23
+ # in Solr, so it's okay if it doesn't exist in Solr but is marked as
24
+ # persisted.
19
25
  # @return [Valkyrie::Resource] The resource with an `#id` value generated by the
20
26
  # persistence backend.
21
27
  # @raise [Valkyrie::Persistence::StaleObjectError]
22
- def save(resource:)
28
+ def save(resource:, external_resource: false)
23
29
  raise Valkyrie::Persistence::StaleObjectError, "The object #{resource.id} has been updated by another process." unless valid_lock?(resource)
30
+ raise Valkyrie::Persistence::ObjectNotFoundError, "The object #{resource.id} is previously persisted but not found at save time." unless external_resource || valid_for_save?(resource)
24
31
 
25
32
  # duplicate the resource so we are not creating side effects on the caller's resource
26
33
  internal_resource = resource.dup
@@ -34,6 +41,15 @@ module Valkyrie::Persistence::Memory
34
41
  cache[internal_resource.id] = internal_resource
35
42
  end
36
43
 
44
+ # return true if resource is
45
+ # persisted and found
46
+ # or
47
+ # not persisted
48
+ def valid_for_save?(resource)
49
+ return true unless resource.persisted? # a new resource
50
+ query_service.find_by(id: resource.id).present? # a persisted resource must be found
51
+ end
52
+
37
53
  # Save a batch of resources.
38
54
  # @param resources [Array<Valkyrie::Resource>] List of resources to save.
39
55
  # @return [Array<Valkyrie::Resource>] List of resources with an `#id` value
@@ -62,46 +78,46 @@ module Valkyrie::Persistence::Memory
62
78
 
63
79
  private
64
80
 
65
- def generate_id(resource)
66
- resource.new(id: SecureRandom.uuid)
67
- end
81
+ def generate_id(resource)
82
+ resource.new(id: SecureRandom.uuid)
83
+ end
68
84
 
69
- # Convert all dates to DateTime in the UTC time zone for consistency.
70
- def normalize_dates!(resource)
71
- resource.attributes.each { |k, v| resource.send("#{k}=", normalize_date_values(v)) }
72
- end
85
+ # Convert all dates to DateTime in the UTC time zone for consistency.
86
+ def normalize_dates!(resource)
87
+ resource.attributes.each { |k, v| resource.send("#{k}=", normalize_date_values(v)) }
88
+ end
73
89
 
74
- def normalize_date_values(v)
75
- return v.map { |val| normalize_date_value(val) } if v.is_a?(Array)
76
- normalize_date_value(v)
77
- end
90
+ def normalize_date_values(v)
91
+ return v.map { |val| normalize_date_value(val) } if v.is_a?(Array)
92
+ normalize_date_value(v)
93
+ end
78
94
 
79
- def normalize_date_value(value)
80
- return value.utc if value.is_a?(DateTime)
81
- return value.to_datetime.utc if value.is_a?(Time)
82
- value
83
- end
95
+ def normalize_date_value(value)
96
+ return value.new_offset(0) if value.is_a?(DateTime)
97
+ return value.to_datetime.new_offset(0) if value.is_a?(Time)
98
+ value
99
+ end
84
100
 
85
- # Create a new lock token based on the current timestamp.
86
- def generate_lock_token(resource)
87
- return unless resource.optimistic_locking_enabled?
88
- token = Valkyrie::Persistence::OptimisticLockToken.new(adapter_id: adapter.id, token: Time.now.to_r)
89
- resource.set_value(Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK, token)
90
- end
101
+ # Create a new lock token based on the current timestamp.
102
+ def generate_lock_token(resource)
103
+ return unless resource.optimistic_locking_enabled?
104
+ token = Valkyrie::Persistence::OptimisticLockToken.new(adapter_id: adapter.id, token: Time.now.to_r)
105
+ resource.set_value(Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK, token)
106
+ end
91
107
 
92
- # Check whether a resource is current.
93
- def valid_lock?(resource)
94
- return true unless resource.optimistic_locking_enabled?
108
+ # Check whether a resource is current.
109
+ def valid_lock?(resource)
110
+ return true unless resource.optimistic_locking_enabled?
95
111
 
96
- cached_resource = cache[resource.id]
97
- return true if cached_resource.blank?
112
+ cached_resource = cache[resource.id]
113
+ return true if cached_resource.blank?
98
114
 
99
- resource_lock_tokens = resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK]
100
- resource_value = resource_lock_tokens.find { |lock_token| lock_token.adapter_id == adapter.id }
101
- return true if resource_value.blank?
115
+ resource_lock_tokens = resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK]
116
+ resource_value = resource_lock_tokens.find { |lock_token| lock_token.adapter_id == adapter.id }
117
+ return true if resource_value.blank?
102
118
 
103
- cached_value = cached_resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK].first
104
- cached_value == resource_value
105
- end
119
+ cached_value = cached_resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK].first
120
+ cached_value == resource_value
121
+ end
106
122
  end
107
123
  end
@@ -51,11 +51,9 @@ module Valkyrie::Persistence::Memory
51
51
  def find_many_by_ids(ids:)
52
52
  ids = ids.uniq
53
53
  ids.map do |id|
54
- begin
55
- find_by(id: id)
56
- rescue ::Valkyrie::Persistence::ObjectNotFoundError
57
- nil
58
- end
54
+ find_by(id: id)
55
+ rescue ::Valkyrie::Persistence::ObjectNotFoundError
56
+ nil
59
57
  end.reject(&:nil?)
60
58
  end
61
59
 
@@ -80,7 +78,7 @@ module Valkyrie::Persistence::Memory
80
78
  # @return integer. Count objects in the persistence backend
81
79
  # with the given class.
82
80
  def count_all_of_model(model:)
83
- cache.values.select { |obj| obj.is_a?(model) }.count
81
+ cache.values.count { |obj| obj.is_a?(model) }
84
82
  end
85
83
 
86
84
  # Get all members of a given resource.
@@ -105,11 +103,9 @@ module Valkyrie::Persistence::Memory
105
103
  # `property` property on `resource`. Not necessarily in order.
106
104
  def find_references_by(resource:, property:, model: nil)
107
105
  refs = Array.wrap(resource[property]).map do |id|
108
- begin
109
- find_by(id: id)
110
- rescue ::Valkyrie::Persistence::ObjectNotFoundError
111
- nil
112
- end
106
+ find_by(id: id)
107
+ rescue ::Valkyrie::Persistence::ObjectNotFoundError
108
+ nil
113
109
  end.reject(&:nil?)
114
110
  refs.uniq! unless ordered_property?(resource: resource, property: property)
115
111
  return refs unless model
@@ -159,27 +155,27 @@ module Valkyrie::Persistence::Memory
159
155
 
160
156
  private
161
157
 
162
- # @return [Array<Valkyrie::ID>] a list of the identifiers of the member objects
163
- def member_ids(resource:)
164
- return [] unless resource.respond_to? :member_ids
165
- resource.member_ids || []
166
- end
158
+ # @return [Array<Valkyrie::ID>] a list of the identifiers of the member objects
159
+ def member_ids(resource:)
160
+ return [] unless resource.respond_to? :member_ids
161
+ resource.member_ids || []
162
+ end
167
163
 
168
- # Determine whether or not a value is a Valkyrie ID
169
- # @param [Object] id
170
- # @return [Boolean]
171
- def validate_id(id)
172
- raise ArgumentError, 'id must be a Valkyrie::ID' unless id.is_a? Valkyrie::ID
173
- end
164
+ # Determine whether or not a value is a Valkyrie ID
165
+ # @param [Object] id
166
+ # @return [Boolean]
167
+ def validate_id(id)
168
+ raise ArgumentError, 'id must be a Valkyrie::ID' unless id.is_a? Valkyrie::ID
169
+ end
174
170
 
175
- # Ensure that a given Valkyrie Resource has been persisted
176
- # @param [Valkyrie::Resource] resource
177
- def ensure_persisted(resource)
178
- raise ArgumentError, 'resource is not saved' unless resource.persisted?
179
- end
171
+ # Ensure that a given Valkyrie Resource has been persisted
172
+ # @param [Valkyrie::Resource] resource
173
+ def ensure_persisted(resource)
174
+ raise ArgumentError, 'resource is not saved' unless resource.persisted?
175
+ end
180
176
 
181
- def ordered_property?(resource:, property:)
182
- resource.ordered_attribute?(property)
183
- end
177
+ def ordered_property?(resource:, property:)
178
+ resource.ordered_attribute?(property)
179
+ end
184
180
  end
185
181
  end