defmodule QuickFactory.Sequence do @moduledoc """ Module for generating sequential values. Use `QuickFactory.Sequence/1` or `QuickFactory.Sequence/2` to generate sequential values instead of calling this module directly. """ use Agent @doc false def start_link(_) do Agent.start_link(fn -> Map.new() end, name: __MODULE__) end @doc """ Reset all sequences so that the next sequence starts from 0 ## Example QuickFactory.Sequence.next("joe") # "joe0" QuickFactory.Sequence.next("joe") # "joe1" QuickFactory.Sequence.reset() QuickFactory.Sequence.next("joe") # resets so the return value is "joe0" You can use list as well QuickFactory.Sequence.next("alphabet_sequence", ["A", "B"]) # "A" QuickFactory.Sequence.next("alphabet_sequence", ["A", "B"]) # "B" QuickFactory.Sequence.reset() QuickFactory.Sequence.next("alphabet_sequence", ["A", "B"]) # resets so the return value is "A" If you want to reset sequences at the beginning of every test, put it in a `setup` block in your test. setup do QuickFactory.Sequence.reset() end """ @spec reset() :: :ok def reset do Agent.update(__MODULE__, fn _ -> Map.new() end) end @doc """ Reset specific sequences so long as they already exist. The sequences specified will be reset to 0, while others will remain at their current index. You can reset a single sequence, ## Example QuickFactory.Sequence.next(:alphabet, ["A", "B", "C"]) # "A" QuickFactory.Sequence.next(:alphabet, ["A", "B", "C"]) # "B" QuickFactory.Sequence.reset(:alphabet) QuickFactory.Sequence.next(:alphabet, ["A", "B", "C"]) # "A" And you can also reset multiple sequences at once, ## Example QuickFactory.Sequence.next(:numeric, [1, 2, 3]) # 1 QuickFactory.Sequence.next(:numeric, [1, 2, 3]) # 2 QuickFactory.Sequence.next("joe") # "joe0" QuickFactory.Sequence.next("joe") # "joe1" QuickFactory.Sequence.reset(["joe", :numeric]) QuickFactory.Sequence.next(:numeric, [1, 2, 3]) # 1 QuickFactory.Sequence.next("joe") # "joe0" """ @spec reset(any()) :: :ok def reset(sequence_names) when is_list(sequence_names) do Agent.update(__MODULE__, fn sequences -> Enum.reduce(sequence_names, sequences, &Map.put(&2, &1, 0)) end) end def reset(sequence_name) do Agent.update(__MODULE__, fn sequences -> Map.put(sequences, sequence_name, 0) end) end @doc false def next(sequence_name) when is_binary(sequence_name) do next(sequence_name, &(sequence_name <> to_string(&1))) end @doc false def next(sequence_name) do raise( ArgumentError, "Sequence name must be a string, got #{inspect(sequence_name)} instead" ) end @doc false def next(sequence_name, [_ | _] = list) do length = length(list) Agent.get_and_update(__MODULE__, fn sequences -> current_value = Map.get(sequences, sequence_name, 0) index = rem(current_value, length) new_sequences = Map.put(sequences, sequence_name, index + 1) {value, _} = List.pop_at(list, index) {value, new_sequences} end) end @doc false def next(sequence_name, formatter, opts \\ []) do start_at = Keyword.get(opts, :start_at, 0) Agent.get_and_update(__MODULE__, fn sequences -> current_value = Map.get(sequences, sequence_name, start_at) new_sequences = Map.put(sequences, sequence_name, current_value + 1) {formatter.(current_value), new_sequences} end) end end