Current section
Files
Jump to
Current section
Files
lib/nebulex/object/version.ex
defmodule Nebulex.Object.Version do
@moduledoc ~S"""
Version handler for cached objects.
The purpose of this module is to allow users implement custom
version generator and validator. This interface is used by the
adapters to generate and/or validate object versions.
## Example
defmodule MyApp.Versioner do
@behaviour Nebulex.Object.Version
def generate(nil), do: 0
def generate(%Object{version: version}) do
version + 1
end
end
If you are going to rely on this feature, it is recommended to use a good
version generation algorithm (e.g.: **Vector Clocks**).
"""
@doc """
Generates a new version for the current cached object.
"""
@callback generate(cached_object :: Nebulex.Object.t()) :: any
alias Nebulex.Object
@doc """
Validates version conflicts for the given cached `object_or_key`.
This function is used by cache's adapters which implement
optimistic locking via object's version.
For more information, check out adapters' implementation.
"""
@spec validate(
object_or_key :: object | Nebulex.Cache.key(),
cache :: Nebulex.Cache.t(),
opts :: Nebulex.Cache.opts()
) :: {:override | :nothing, object | nil}
when object: Nebulex.Object.t()
def validate(nil, _cache, _opts), do: {:override, nil}
def validate(object_or_key, cache, opts) do
opts
|> Keyword.get(:version)
|> validate(object_or_key, cache, opts)
end
## Helpers
defp validate(nil, %Object{} = object, _cache, _opts) do
{:override, object}
end
defp validate(nil, key, _cache, _opts) do
{:override, %Object{key: key}}
end
defp validate(vsn, object_or_key, cache, opts) do
cache
|> maybe_get(object_or_key)
|> on_conflict(vsn, Keyword.get(opts, :on_conflict, :raise))
end
defp maybe_get(_cache, %Object{} = object),
do: object
defp maybe_get(cache, key),
do: cache.__adapter__.get(cache, key, [])
defp on_conflict(%Object{version: version} = cached, version, _on_conflict),
do: {:override, cached}
defp on_conflict(cached, _version, on_conflict) when on_conflict in [:override, :nothing],
do: {on_conflict, cached}
defp on_conflict(cached, version, :raise),
do: raise(Nebulex.VersionConflictError, cached: cached, version: version)
defp on_conflict(_, _, other),
do: raise(ArgumentError, "unknown value for :on_conflict, got: #{inspect(other)}")
end