lib/unleash/cache.ex

defmodule Unleash.Cache do
  @moduledoc """
  This module is a cache backed by an ETS table. We use it to allow for multiple
  threads to read the feature flag values concurrently on top of minimizing
  network calls
  """

  @cache_table_name :unleash_cache

  def cache_table_name, do: @cache_table_name

  @doc """
  Will create a new ETS table named `:unleash_cache`
  """
  def init(existing_features \\ [], table_name \\ @cache_table_name) do
    :ets.new(table_name, [:named_table, read_concurrency: true])

    upsert_features(existing_features, table_name)
  end

  @doc """
  Will return all values currently stored in the cache
  """
  def get_all_feature_names(table_name \\ @cache_table_name) do
    features = :ets.tab2list(table_name)

    Enum.map(features, fn {name, _feature} ->
      name
    end)
  end

  @doc """
  Will return all features stored in the cache
  """
  def get_features(table_name \\ @cache_table_name) do
    features = :ets.tab2list(table_name)

    Enum.map(features, fn {_name, feature} ->
      feature
    end)
  end

  @doc """
  Will return the feature for the given name stored in the cache
  """
  def get_feature(name, table_name \\ @cache_table_name)

  def get_feature(name, table_name) when is_binary(name) do
    case :ets.lookup(table_name, name) do
      [{^name, feature}] -> feature
      [] -> nil
    end
  end

  def get_feature(name, table_name) when is_atom(name),
    do: get_feature(Atom.to_string(name), table_name)

  @doc """
  Will upsert (create or update) the given features in the cache

  This will clear the existing peristed features to prevent stale reads
  """
  def upsert_features(features, table_name \\ @cache_table_name) do
    :ets.delete_all_objects(table_name)

    Enum.each(features, fn feature ->
      upsert_feature(feature.name, feature, table_name)
    end)
  end

  defp upsert_feature(name, value, table_name) when is_binary(name) do
    :ets.insert(table_name, {name, value})
  end

  defp upsert_feature(name, value, table_name) when is_atom(name) do
    upsert_feature(Atom.to_string(name), value, table_name)
  end
end