Skip to content

Instantly share code, notes, and snippets.

@octosteve
Created April 20, 2017 01:46
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save octosteve/cd955e1f1576397c01c528ba41a88730 to your computer and use it in GitHub Desktop.
Save octosteve/cd955e1f1576397c01c528ba41a88730 to your computer and use it in GitHub Desktop.
defmodule Store do
@initializer_action %{type: "@@INIT"}
# Code your Mom calls
def start_link(reducer, initial_state \\ nil) do
GenServer.start_link(__MODULE__, [reducer, initial_state])
end
def get_state(store) do
GenServer.call(store, {:get_state})
end
def dispatch(store, action) do
GenServer.cast(store, {:dispatch, action})
end
def subscribe(store, subscriber) do
GenServer.call(store, {:subscribe, subscriber})
end
def remove_subscriber(store, ref) do
GenServer.cast(store, {:remove_subscriber, ref})
end
def init([reducer_map, nil]) when is_map(reducer_map), do: init([reducer_map, %{}])
def init([reducer_map, initial_state]) when is_map(reducer_map) do
store_state = CombineReducers.reduce(reducer_map, initial_state, @initializer_action)
{:ok, %{reducer: reducer_map, store_state: store_state, subscribers: %{}}} # add new subscribers map to state
end
def init([reducer, initial_state]) do
store_state = apply(reducer, :reduce, [initial_state, @initializer_action])
{:ok, %{reducer: reducer, store_state: store_state, subscribers: %{}}} # add new subscribers map to state
end
def handle_call({:get_state}, _from, state) do
{:reply, Map.get(state, :store_state), state}
end
def handle_call({:subscribe, subscriber}, _from, %{subscribers: subscribers} = state) do
ref = make_ref()
{:reply, ref, put_in(state, [:subscribers, ref], subscriber)}
end
def handle_cast({:remove_subscriber, ref}, %{subscribers: subscribers} = state) do
subscribers = Map.delete(subscribers, ref)
{:noreply, Map.put(state, :subscribers, subscribers)}
end
def handle_cast({:dispatch, action}, %{reducer: reducer_map, store_state: store_state, subscribers: subscribers} = state) when is_map(reducer_map) do
store_state = CombineReducers.reduce(reducer_map, store_state, action)
for {_ref, sub} <- subscribers, do: sub.(store_state) # notify those subscribers
{:noreply, Map.put(state, :store_state, store_state)}
end
def handle_cast({:dispatch, action}, %{reducer: reducer, store_state: store_state, subscribers: subscribers} = state) do
store_state = apply(reducer, :reduce, [store_state, action])
for {_ref, sub} <- subscribers, do: sub.(store_state) # notify those subscribers
{:noreply, Map.put(state, :store_state, store_state)}
end
def handle_cast({:dispatch, action}, %{reducer: reducer, store_state: store_state} = state) do
store_state = apply(reducer, :reduce, [store_state, action])
{:noreply, Map.put(state, :store_state, store_state)}
end
end
defmodule Reducer do
@callback reduce(any, Map :: map()) :: any
end
defmodule SquareReducer do
@behaviour Reducer
def reduce(nil, action), do: reduce(2, action)
def reduce(state, action), do: do_reduce(state, action)
defp do_reduce(state, %{type: "INCREMENT"}), do: state * 1
defp do_reduce(state, _), do: state
end
defmodule CountReducer do
@behaviour Reducer
def reduce(nil, action), do: reduce(0, action)
def reduce(state, action), do: do_reduce(state, action)
defp do_reduce(state, %{type: "INCREMENT"}), do: state + 1
defp do_reduce(state, _), do: state
end
defmodule CombineReducers do
def reduce(reducers, state, action) do
for {state_name, reducer} <- reducers do
Task.async(fn () ->
{state_name, apply(reducer, :reduce, [state[state_name], action])}
end)
end
|> Enum.map(&Task.await/1)
|> Enum.into(%{})
end
end
{:ok, store} = Store.start_link(%{count: CountReducer, square: SquareReducer})
Store.dispatch(store, %{type: "INCREMENT"})
Store.get_state(store)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment