Last active
May 7, 2024 09:53
-
-
Save hl/4c23592cf9e86a8821041df6cd84533e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Engine do | |
@moduledoc """ | |
Engine that can run a series of stages. | |
It will return a tuple containing the last effect and all the effects. | |
## Example | |
defmodule HelloWorld do | |
import Engine | |
def greeting(name) do | |
new() | |
|> run(:greeting, &construct_greeting/2) | |
|> run(:greeting_and_name, &add_name_to_greeting/2) | |
|> execute(%{name: name}) | |
end | |
def construct_greeting(_effects_so_far, _attrs) do | |
{:ok, "Hello, {{name}}!"} | |
end | |
def add_name_to_greeting(%{greeting: greeting}, %{name: name}) do | |
{:ok, String.replace(greeting, "{{name}}", name)} | |
end | |
end | |
iex> HelloWorld.greeting("Jane") | |
{:ok, "Hello, Jane!", %{greeting: "Hello, {{name}}!", greeting_and_name: "Hello, Jane!"}} | |
""" | |
defstruct stages: [] | |
@type t :: %__MODULE__{stages: stages()} | |
@type stages :: Keyword.t() | |
@type last_effect :: term() | |
@type effects_so_far :: %{atom() => last_effect()} | |
@type result :: {:ok, last_effect(), effects_so_far()} | {:error, term()} | |
@type effect_result :: {:ok, term()} | {:error, term()} | |
@spec new() :: t() | |
def new, do: %__MODULE__{} | |
@spec run(t(), atom(), fun()) :: Token.t() | |
def run(%__MODULE__{} = token, key, fun) when is_atom(key) and is_function(fun) do | |
%{token | stages: [{key, fun} | token.stages]} | |
end | |
@spec execute(t(), term()) :: result() | |
def execute(%__MODULE__{stages: stages}, attrs) do | |
stages | |
|> Enum.reverse() | |
|> Enum.reduce_while({_last_effect = nil, _effects_so_far = %{}}, fn | |
{key, fun}, {_last_effect, effects_so_far} -> | |
case fun.(effects_so_far, attrs) do | |
{:ok, last_effect} -> | |
effects_so_far = Map.put(effects_so_far, key, last_effect) | |
{:cont, {last_effect, effects_so_far}} | |
{:error, error} -> | |
{:halt, {:error, error}} | |
end | |
end) | |
|> then(fn | |
{:error, error} -> {:error, error} | |
{last_effect, effects_so_far} -> {:ok, last_effect, effects_so_far} | |
end) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Payroll do | |
import Engine | |
@data [ | |
%{user: "Joe", salary: 2000}, | |
%{user: "Jane", salary: 3000} | |
] | |
@rules [ | |
%{"name" => "add", "value" => 100} | |
] | |
@contracts [] | |
@spec call(list(), list(), list()) :: Engine.result() | |
def call(data \\ @data, rules \\ @rules, contracts \\ @contracts) do | |
new() | |
|> run(:rules_map, &generate_rules_map/2) | |
|> run(:salaries, &add_to_salaries/2) | |
|> execute(%{data: data, rules: rules, contracts: contracts}) | |
end | |
@spec generate_rules_map(Engine.effects_so_far(), map()) :: Engine.effect_result() | |
def generate_rules_map(_effects_so_far, attrs) do | |
%{rules: rules} = attrs | |
{:ok, Map.new(rules, &{&1["name"], &1})} | |
end | |
@spec add_to_salaries(Engine.effects_so_far(), map()) :: Engine.effect_result() | |
def add_to_salaries(effects_so_far, attrs) do | |
%{rules_map: %{"add" => rule}} = effects_so_far | |
%{data: data} = attrs | |
{:ok, | |
Enum.map(data, fn row -> | |
%{row | salary: row.salary + rule["value"]} | |
end)} | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment