Current section

Files

Jump to
ex_sider lib redis_map.ex
Raw

lib/redis_map.ex

defmodule RedisMap do
@moduledoc """
Implements Collectable and Access.
Doesn't implement Enumerable, since Redis Key/Value sets should
rather not be enumerated. (Its just one big map)
"""
require Logger
alias RedisMap
@behaviour Access
defstruct __redis_adapter__: nil, __binary_mode__: true
def new(opts \\ []) do
binary_mode = opts[:binary_mode] || true
adapter = Application.get_env(:ex_sider, :redis_adapter)
%RedisMap{
__redis_adapter__: adapter,
__binary_mode__: binary_mode}
end
def drop(%RedisMap{} = container, [_ | _] = keys) do
string_keys = for key <- keys, is_binary(key) or is_atom(key), do: to_string(key)
drop_internal(container, keys, string_keys)
end
defp drop_internal(%RedisMap{__redis_adapter__: adapter} = container, original_keys, string_keys)
when length(original_keys) == length(string_keys) do
case adapter.command(["DEL" | string_keys]) do
{:ok, x} when is_number(x) -> container
other ->
Logger.error "RedisMap failed to call drop/2, got Redis reply: #{inspect other}"
container
end
end
def drop_internal(_container, keys), do: raise ArgumentError, message: "invalid RedisMap keys, need to be List of String or Atom: #{inspect keys}"
def fetch(%RedisMap{__redis_adapter__: adapter, __binary_mode__: binary}, key)
when is_binary(key) or is_atom(key) do
case adapter.command(["GET", to_string(key)]) do
{:ok, nil} -> :error
{:ok, val} when binary -> {:ok, :erlang.binary_to_term(val)}
{:ok, val} -> {:ok, val}
other ->
Logger.error "RedisMap failed to call fetch/2, got Redis reply: #{inspect other}"
:error
end
end
def fetch(_container, key), do: raise ArgumentError, message: "invalid RedisMap key, needs to be String or Atom: #{inspect key}"
def get_and_update(%RedisMap{} = container, key, fun) do
current =
case fetch(container, key) do
{:ok, val} -> val
:error -> nil
end
case fun.(current) do
{get, update} ->
[{key, update}] |> Enum.into(container)
{get, container}
:pop when is_nil(current) -> {current, container}
:pop ->
drop(container, [key])
{current, container}
other ->
raise BadFunctionError, message: "invalid return type in RedisMap get_and_update/3, allowed is tuple and :pop, go: #{inspect other}"
end
end
end
defimpl Collectable, for: RedisMap do
require Logger
def into(%RedisMap{__redis_adapter__: adapter, __binary_mode__: binary} = original) do
{%{}, fn
map, {:cont, {k, v}} when (is_binary(k) or is_atom(k)) and binary ->
Map.put(map, to_string(k), :erlang.term_to_binary(v))
map, {:cont, {k, v}} when (is_binary(k) or is_atom(k)) and (is_binary(v) or is_atom(v)) ->
Map.put(map, to_string(k), to_string(v))
map, {:cont, x} ->
Logger.error "RedisMap failed to call :cont in into/1 (will skip), got unexpected input: #{inspect x}"
map
map, :done ->
vals = map |> Enum.reduce([], fn {k, v}, acc -> [k, v | acc] end)
case adapter.command(["MSET" | vals]) do
{:ok, "OK"} -> original
other ->
Logger.error("RedisMap failed to call into/1, got Redis reply: #{inspect other}")
original
end
_, :halt -> :ok
end}
end
end