valkyrie 2.1.0 → 3.0.0.pre.beta.1

Sign up to get free protection for your applications and to get access to all the features.
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