I'm learning the Elixir language and the Phoenix web framework. And I had an idea. What if, in a web wizard (multi-step form) scenario, I stored the intermediate data in a ETS table instead of the relational database?

So I wrote this controller:

defmodule PlayPhoenixWeb.ThingsController do
  use PlayPhoenixWeb, :controller
  require Logger
  @moduledoc false

  @form_ets :things_forms

  alias PlayPhoenixWeb.Router.Helpers, as: Routes
  alias PlayPhoenixWeb.Endpoint

  def step_1_display(conn, _params) do
    render(conn, "form_1.html")

  def step_1_process(conn, params) do
    data = Map.take(params, ["name", "vcode"])
    key = make_key()
    write_data(key, data)
    Logger.debug inspect(data)
    redirect(conn, to: Routes.thing_form_2_path(Endpoint, :step_2_display, key))

  def step_2_display(conn, %{"key" => key}) do
    render(conn, "form_2.html", key: key)

  def step_2_process(conn, %{"key" => key} = params) do
    {:ok, data} = get_data(key)
    full_data = Map.take(params, ["description", "value"]) |> Map.merge(data)
    # In production, processing full data would happen here
    Logger.debug inspect(full_data)
    redirect(conn, to: Routes.page_path(Endpoint, :index))

  # This method is called by the Application on startup
  def init_ets(), do: :ets.new(@form_ets, [:public, :named_table])

  defp make_key(), do: :crypto.hash(:md5, DateTime.utc_now() |> DateTime.to_iso8601) |> :base64.encode

  defp get_data(key) do
    case :ets.lookup(@form_ets, key) do
      [{^key, value}] -> {:ok, value}
      _ -> {:not_found}

  defp write_data(key, data) do
    :ets.insert(@form_ets, {key, data})

  defp delete_data(key), do: :ets.delete(@form_ets, key)


(It's purely for the sake of playing/research, and currently doesn't store or process any data).

Is this the Elixir way/the Phoenix way?

Thank you very much.

  • \$\begingroup\$ Hi, you should try to explain in a little more detail what your code is supposed to do :) \$\endgroup\$ – IEatBagels Feb 26 '19 at 18:30

Yes, using ETS for this sort of temporary data is totally fine, as long as you realize of course that all that intermediate state is lost whenever you restart your server. In fact, if you dig into Phoenix and Elixir a bit, you will see a lot of ETS usage - it's not so much a thing you turn to in exceptional circumstances, but more a tool in the extensive toolbox at the same level of, say, GenServer.


Can't say much on Elixir, but, make_key - why go through the trouble and use MD5 on a string format of the current time (at what resolution? seconds? so you'll get duplicates whenever this is called quickly enough in rapid succession) when you can just generate a bunch of random numbers of a fixed length instead? Additionally you might just be able to pass in the source of the random numbers too and then suddenly it's way more testable (because 1. you're not relying on the current time, 2. you're not on a global random number generator).

  • 1
    \$\begingroup\$ Well, one would probably use UUID or something in production... But frankly, I was going to mostly look at the capabilities of ETS so I needed something quick and not requiring external libraries for generating the key. \$\endgroup\$ – art-solopov Feb 27 '19 at 9:33
  • \$\begingroup\$ Right, I meant something like this, that looks like it's still all internal. \$\endgroup\$ – ferada Feb 27 '19 at 14:20
  • 2
    \$\begingroup\$ :erlang.unique_integer/0 will do the job for unique keys that are VM loocal. \$\endgroup\$ – cdegroot Mar 7 '19 at 17:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.