defmodule Cachex.Actions do
@moduledoc """
Parent actions module for cache interactions.
This module contains foundation actions required to implement cache actions,
such as typical CRUD style operations on cache entries. It also provides the
`defaction/2` macro which enables command definition which injects notifications
for cache hooks.
"""
import Cachex.Spec
# add some aliases
alias Cachex.Services.Janitor
##############
# Public API #
##############
@doc """
Formats a fetched value into a Courier compatible tuple.
If the value is tagged with `:commit`, `:ignore` or `:error`,
it will be left alone; otherwise it will be wrapped and treated
as a `:commit` Tuple.
"""
def format_fetch_value(value) do
case value do
{:error, _value} ->
value
{:commit, _value} ->
value
{:commit, _value, _options} ->
value
{:ignore, _value} ->
value
raw_value ->
{:commit, raw_value}
end
end
@doc """
Normalizes a commit formatted fetch value.
This is simply compatibility for the options addition in v3.5, without
breaking the previous versions of this library.
"""
def normalize_commit({:commit, value}),
do: {:commit, value, []}
def normalize_commit(value),
do: value
@doc """
Retrieves an entry from a cache.
If the entry does not exist, a `nil` value will be returned. Likewise
if the entry has expired, we lazily remove it (if enabled) and return
a `nil` value.
This will return an instance of an entry record as defined in the main
`Cachex.Spec` module, rather than just the raw value.
"""
@spec read(Spec.cache(), any) :: Spec.entry() | nil
def read(cache(name: name) = cache, key) do
case :ets.lookup(name, key) do
[] ->
nil
[entry] ->
case Janitor.expired?(cache, entry) do
false ->
entry
true ->
Cachex.del(cache, key, const(:purge_override))
nil
end
end
end
@doc """
Updates a collection of fields inside a cache entry.
This is done in a single call due to the use of `:ets.update_element/3` which
allows multiple changes in a group. This will return a boolean to represent
whether the update was successful or not.
Note that updates are atomic; either all updates will take place, or none will.
"""
@spec update(Spec.cache(), any, [tuple]) :: {:ok, boolean}
def update(cache(name: name), key, changes),
do: {:ok, :ets.update_element(name, key, changes)}
@doc """
Writes a new entry into a cache.
"""
@spec write(Spec.cache(), [Spec.entry()]) :: {:ok, boolean}
def write(cache(name: name), entries),
do: {:ok, :ets.insert(name, entries)}
@doc """
Returns the operation used for a write based on a prior value.
"""
@spec write_op(atom) :: atom
def write_op(nil),
do: :put
def write_op(_tag),
do: :update
end