Elixir library that provides a macro which converts the modules into operations for encapsulating business logics.
MIT License
Elixir library that provides a macro which converts the modules into operations for encapsulating business logics. It uses Ecto Schema (embedded_schema
) for defining input / output and Ecto Changeset for the validations. :)
It can be installed by adding ex_runner
to your list of dependencies in mix.exs
:
def deps do
[
{:ex_runner, "~> 0.2.0"}
]
end
Let's take an example to understand how this library works.
Example - Write an operation which creates the user session by taking email and password.
Step 1 - Define a module and add use ExRunner
.
defmodule CreateSession do
use ExRunner
end
Step 2 - Define input and output for the operation. Input is what will be given as parameters to this operation and Output is what will get returned from the operation as a result.
defmodule CreateSession do
use ExRunner
input do
field :email, :string
field :password, :string
end
output do
field :session_id, Ecto.UUID
end
end
This is just Ecto Schema For complex schema definition, embeds_one and embeds_many can be used.
Step 3 - Define validate function which adds the required validations on the inputs passed to the operation.
defmodule CreateSession do
use ExRunner
input do
field :email, :string
field :password, :string
end
output do
field :session_id, Ecto.UUID
end
defp validate(changeset) do
changeset
|> validate_required([:email, :password])
|> validate_format(:email, ~r/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)
|> validate_length(:password, min: 4)
end
end
Validate function takes changeset as argument, params
of which contains the input passed and schema is of type input defined in step 2.
For complex validations, read Ecto Changeset
Step 4- Define execute function which performs business logic with the inputs provided.
defmodule CreateSession do
use ExRunner
input do
field :email, :string
field :password, :string
end
output do
field :session_id, Ecto.UUID
end
defp validate(changeset) do
changeset
|> validate_required([:email, :password])
|> validate_format(:email, ~r/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)
|> validate_length(:password, min: 4)
end
defp execute(changeset) do
params = changeset.params
case (params.email == "[email protected]" and params.password == "test") do
true -> %{session_id: "870df8e8-3107-4487-8316-81e089b8c2cf"}
false -> add_error(changeset, :credentials, "are invalid")
end
end
end
Execute function takes changeset as argument. Inputs / parameters can be found in changeset.params
.
To add an error, just call add_error of Ecto.Changeset.
In case of :ok, return the needed response of type output defined in step 4.
In case of :error, return Ecto.Changeset.
Step 5 - Try running the operation.
# run with valid credentials
> CreateSession.run(email: "[email protected]", password: "test")
{:ok, %{session_id: "870df8e8-3107-4487-8316-81e089b8c2cf"}}
# run! with valid credentials
> CreateSession.run!(email: "[email protected]", password: "test")
%{session_id: "870df8e8-3107-4487-8316-81e089b8c2cf"}
# run with invalid email format
> CreateSession.run(email: "test", password: "test")
{:error,
#Ecto.Changeset<
action: nil,
changes: %{email: "test", password: "testi"},
errors: [email: {"has invalid format", [validation: :format]}],
data: #CreateSession.Input<>,
valid?: false
>}
# run! with invalid email format
> CreateSession.run(email: "test", password: "test")
# raises Ecto.InvalidChangesetError
# run with invalid credentials
> CreateSession.run(email: "[email protected]", password: "test1")
{:error,
#Ecto.Changeset<
action: nil,
changes: %{email: "[email protected]", password: "testi"},
errors: [credentials: {"are invalid", []}],
data: #CreateSession.Input<>,
valid?: false
>}
# run! with invalid credentials
> CreateSession.run!(email: "[email protected]", password: "test1")
# raises Ecto.InvalidChangesetError
I recommend reading How does the library work internally ? to understand in detail.
1 - Write an operation which creates the user session by taking email and password.
defmodule CreateSession do
use ExRunner
input do
field :email, :string
field :password, :string
end
output do
field :session_id, Ecto.UUID
end
defp validate(changeset) do
changeset
|> validate_required([:email, :password])
|> validate_format(:email, ~r/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)
|> validate_length(:password, min: 4)
end
defp execute(changeset) do
params = changeset.params
case (params.email == "[email protected]" and params.password == "test") do
true -> %{session_id: "870df8e8-3107-4487-8316-81e089b8c2cf"}
false -> add_error(changeset, :credentials, "are invalid")
end
end
end
2 - (Embed Example) - Write an operation which takes the user data, process and return them.
defmodule ProcessUserData do
use ExRunner
embed_object Profile do
field :name, :string
field :picture, :string
field :mobile_numbers, {:array, :string}
end
embed_object Address do
field :address, :string
field :country, :string
end
input do
field :id, :integer
embeds_one :profile, ProcessUserData.Profile
embeds_many :addresses, ProcessUserData.Address
end
output do
field :id, :integer
embeds_one :profile, ProcessUserData.Profile
embeds_many :addresses, ProcessUserData.Address
end
defp validate(changeset) do
changeset
|> validate_required([:id])
|> EctoMorph.validate_nested_changeset([:profile], fn changeset ->
changeset
|> validate_required([:name, :picture])
end)
|> EctoMorph.validate_nested_changeset([:addresses], fn changeset ->
changeset
|> validate_required([:address, :country])
|> validate_inclusion(:country, ["US", "NL"])
end)
end
defp execute(changeset) do
changeset.params |> process_data
end
defp process_data(data) do
profile = data.profile
processed_picture = "processed_picture"
profile = Map.put(profile, :picture, processed_picture)
Map.put(data, :profile, profile)
end
end
run
can be called either with a keyword list or a map.
1 - It first filters the input and permits only the ones defined in input schema. Even if string keys gets passed to run, it converts them to atoms. This is also true for embeds_one and embeds_many.
2 - It checks the input against the field types defined in input. If invalid, it returns {:error, changeset}
3 - It calls validate which has been defined in the module. If invalid, it returns {:error, changeset}
4 - It calls execute which has been defined in the module. If execute returns changeset, it returns {:error, changeset}. If execute returns other than changeset, it stores it as output.
5 - It filters the output and permits only the ones defined in output schema. This is also true for embeds_one and embeds_many.
6 - It checks the output against the field types defined in output. If invalid, it raises Ecto.InvalidChangesetError.
7 - It returns the output as map finally. {:ok, output}
In case of run!
, if the returned tuple is of {:error}, it raises errors. if the returned tuple is of {:ok}, it returns output.