# ExRunner
Elixir library that provides a macro which converts the modules into operations for encapsulating business logics. It uses **[Ecto Schema](https://hexdocs.pm/ecto/Ecto.Schema.html)** (`embedded_schema`) for defining input / output and **[Ecto Changeset](https://hexdocs.pm/ecto/Ecto.Changeset.html)** for the validations. :)
## Installation
It can be installed by adding `ex_runner` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:ex_runner, "~> 0.1.0"}
]
end
```
## Usage
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`.
```elixir
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.
```elixir
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](https://hexdocs.pm/ecto/Ecto.Schema.html) For complex schema definition, [embeds_one](https://hexdocs.pm/ecto/Ecto.Schema.html#embeds_one/3) and [embeds_many](https://hexdocs.pm/ecto/Ecto.Schema.html#embeds_many/3) can be used.
**Step 3** - Define validate function which adds the required validations on the inputs passed to the operation.
```elixir
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, `changes` of which contains the input passed and schema is of type input defined in step 2.
For complex validations, read [Ecto Changeset](https://hexdocs.pm/ecto/Ecto.Changeset.html)
**Step 4**- Define execute function which performs business logic with the inputs provided.
```elixir
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.changes
case (params.email == "test@test.com" 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.changes`.
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.
```elixir
# run with valid credentials
> CreateSession.run(email: "test@test.com", password: "test")
{:ok, %{session_id: "870df8e8-3107-4487-8316-81e089b8c2cf"}}
# run! with valid credentials
> CreateSession.run!(email: "test@test.com", 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: "test@test.com", password: "test1")
{:error,
#Ecto.Changeset<
action: nil,
changes: %{email: "test@test.com", password: "testi"},
errors: [credentials: {"are invalid", []}],
data: #CreateSession.Input<>,
valid?: false
>}
# run! with invalid credentials
> CreateSession.run!(email: "test@test.com", password: "test1")
# raises Ecto.InvalidChangesetError
```
I recommend reading [How does the library work internally ?](#how-does-the-library-work-internally) to understand in detail.
## How does the library work internally ?
`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. 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.