Skip to content

Commit

Permalink
Adding optimistic locking to the memory persister
Browse files Browse the repository at this point in the history
fixes #455

Co-authored-by: Mike Tribone <mtribone@psu.edu>
Co-authored-by: Anna Headley <anna.headley@gmail.com>
Co-authored-by: Adam Wead <amsterdamos@gmail.com>
  • Loading branch information
4 people committed Aug 3, 2018
1 parent 77c77c9 commit 4695f70
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 6 deletions.
39 changes: 33 additions & 6 deletions lib/valkyrie/persistence/memory/persister.rb
Expand Up @@ -16,18 +16,25 @@ def initialize(adapter)
# @return [Valkyrie::Resource] The resource with an `#id` value generated by the
# persistence backend.
def save(resource:)
resource = generate_id(resource) if resource.id.blank?
resource.created_at ||= Time.current
resource.updated_at = Time.current
resource.new_record = false
normalize_dates!(resource)
cache[resource.id] = resource
raise Valkyrie::Persistence::StaleObjectError, resource.id unless valid_lock?(resource)

# duplicate the resource so we are not creating side effects on the caller's resource
internal_resource = resource.dup

internal_resource = generate_id(internal_resource) if internal_resource.id.blank?
internal_resource.created_at ||= Time.current
internal_resource.updated_at = Time.current
internal_resource.new_record = false
generate_lock_token(internal_resource)
normalize_dates!(internal_resource)
cache[internal_resource.id] = internal_resource
end

# @param resources [Array<Valkyrie::Resource>] List of resources to save.
# @return [Array<Valkyrie::Resource>] List of resources with an `#id` value
# generated by the persistence backend.
def save_all(resources:)
raise Valkyrie::Persistence::StaleObjectError, resources.map(&:id).join(', ') unless resources.reject { |resource| valid_lock?(resource) }.blank?
resources.map do |resource|
save(resource: resource)
end
Expand Down Expand Up @@ -64,5 +71,25 @@ def normalize_date_value(value)
return value.to_datetime.utc if value.is_a?(Time)
value
end

def generate_lock_token(resource)
return unless resource.optimistic_locking_enabled?
token = Valkyrie::Persistence::OptimisticLockToken.new(adapter_id: adapter.id, token: Time.now.to_r)
resource.send("#{Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK}=", token)
end

def valid_lock?(resource)
return true unless resource.optimistic_locking_enabled?

cached_resource = cache[resource.id]
return true if cached_resource.blank?

resource_lock_tokens = resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK]
resource_value = resource_lock_tokens.find { |lock_token| lock_token.adapter_id == adapter.id }
return true if resource_value.blank?

cached_value = cached_resource[Valkyrie::Persistence::Attributes::OPTIMISTIC_LOCK].first
cached_value == resource_value
end
end
end
2 changes: 2 additions & 0 deletions spec/valkyrie/persistence/memory/persister_spec.rb
@@ -1,10 +1,12 @@
# frozen_string_literal: true
require 'spec_helper'
require 'valkyrie/specs/shared_specs'
require 'valkyrie/specs/shared_specs/locking_persister'

RSpec.describe Valkyrie::Persistence::Memory::Persister do
let(:adapter) { Valkyrie::Persistence::Memory::MetadataAdapter.new }
let(:query_service) { adapter.query_service }
let(:persister) { adapter.persister }
it_behaves_like "a Valkyrie::Persister"
it_behaves_like "a Valkyrie locking persister"
end
2 changes: 2 additions & 0 deletions spec/valkyrie/persistence/memory/query_service_spec.rb
@@ -1,8 +1,10 @@
# frozen_string_literal: true
require 'spec_helper'
require 'valkyrie/specs/shared_specs'
require 'valkyrie/specs/shared_specs/locking_query'

RSpec.describe Valkyrie::Persistence::Memory::QueryService do
let(:adapter) { Valkyrie::Persistence::Memory::MetadataAdapter.new }
it_behaves_like "a Valkyrie query provider"
it_behaves_like "a Valkyrie locking query provider"
end

0 comments on commit 4695f70

Please sign in to comment.