Skip to content

Commit

Permalink
Support unless_exist
Browse files Browse the repository at this point in the history
Add support for the unless_exist option to the write method. This will
not overwrite the value and return false if the key already exists
in the cache.
  • Loading branch information
djmb committed Jul 22, 2024
1 parent 6db79fe commit 01fb845
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 26 deletions.
45 changes: 29 additions & 16 deletions app/models/solid_cache/entry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,25 @@ class Entry < Record
KEY_HASH_ID_RANGE = -(2**63)..(2**63 - 1)

class << self
def write(key, value)
upsert_all_no_query_cache([ { key: key, value: value } ])
def write(key, value, unless_exist: false)
without_query_cache do
payload = add_key_hash_and_byte_size_to_payload(key: key, value: value)
if unless_exist
insert!(payload)
else
upsert(payload, **upsert_options)
end
end

true
rescue ActiveRecord::RecordNotUnique
false
end

def write_multi(payloads)
upsert_all_no_query_cache(payloads)
without_query_cache do
upsert_all add_key_hash_and_byte_size_to_payloads(payloads), **upsert_options
end
end

def read(key)
Expand Down Expand Up @@ -65,29 +78,29 @@ def id_range
end

private
def upsert_all_no_query_cache(payloads)
without_query_cache do
upsert_all \
add_key_hash_and_byte_size(payloads),
unique_by: upsert_unique_by, on_duplicate: :update, update_only: [ :key, :value, :byte_size ]
end
end

def delete_no_query_cache(attribute, values)
without_query_cache do
where(attribute => values).delete_all
end
end

def add_key_hash_and_byte_size(payloads)
def add_key_hash_and_byte_size_to_payloads(payloads)
payloads.map do |payload|
payload.dup.tap do |payload|
payload[:key_hash] = key_hash_for(payload[:key])
payload[:byte_size] = byte_size_for(payload)
end
add_key_hash_and_byte_size_to_payload(payload.dup)
end
end

def add_key_hash_and_byte_size_to_payload(payload)
payload.tap do |payload|
payload[:key_hash] = key_hash_for(payload[:key])
payload[:byte_size] = byte_size_for(payload)
end
end

def upsert_options
{ unique_by: upsert_unique_by, on_duplicate: :update, update_only: [ :key, :value, :byte_size ] }
end

def upsert_unique_by
connection.supports_insert_conflict_target? ? :key_hash : nil
end
Expand Down
13 changes: 7 additions & 6 deletions lib/solid_cache/store/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,17 @@ def read_serialized_entry(key, **options)
entry_read(key)
end

def write_entry(key, entry, raw: false, **options)
def write_entry(key, entry, raw: false, unless_exist: false, **options)
payload = serialize_entry(entry, raw: raw, **options)
# No-op for us, but this writes it to the local cache
write_serialized_entry(key, payload, raw: raw, **options)

entry_write(key, payload)
entry_write(key, payload, unless_exist: unless_exist).tap do |result|
# No-op for us, but this writes it to the remote cache
write_serialized_entry(key, payload, raw: raw, unless_exist: unless_exist, returning: result, **options)
end
end

def write_serialized_entry(key, payload, raw: false, unless_exist: false, expires_in: nil, race_condition_ttl: nil, **options)
true
def write_serialized_entry(key, payload, raw: false, unless_exist: false, expires_in: nil, race_condition_ttl: nil, returning: true, **options)
returning
end

def read_serialized_entries(keys)
Expand Down
6 changes: 2 additions & 4 deletions lib/solid_cache/store/entries.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,9 @@ def entry_read_multi(keys)
end
end

def entry_write(key, payload)
def entry_write(key, payload, unless_exist:)
writing_key(key, failsafe: :write_entry, failsafe_returning: nil) do
Entry.write(key, payload)
track_writes(1)
true
Entry.write(key, payload, unless_exist: unless_exist).tap { |result| track_writes(1) if result }
end
end

Expand Down
5 changes: 5 additions & 0 deletions test/unit/solid_cache_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ class SolidCacheTest < ActiveSupport::TestCase
cache = lookup_store(max_age: 7200)
assert_equal 7200, cache.max_age
end

def test_write_with_unless_exist
assert_equal true, @cache.write("foo", 1)
assert_equal false, @cache.write("foo", 1, unless_exist: true)
end
end

class SolidCacheFailsafeTest < ActiveSupport::TestCase
Expand Down

0 comments on commit 01fb845

Please sign in to comment.