# `Spek.Macros`
[🔗](https://github.com/woylie/spek/blob/0.2.0/lib/spek/macros.ex#L1)

Convenience macros for defining check functions.

The usage of these macros is optional, but they can make your rules more
readable.

# `build_check`
*macro* 

Defines a function that returns a `Spek.Check` struct that uses an existing
function in the same module.

## Example

Let's say you have an existing `active_user/1` function that you want to use
in a Spek expression. Instead of defining the `Check` struct manually, you can
use `build_check` and pass the function name and the arguments.

    defmodule MyApp.MyModule do
      def active_user(%{state: :active}), do: :ok
      def active_user(%{state: :inactive}), do: {:error, :user_inactive}

      build_check(:active_user)
    end

This will compile a `{fun}_check/0` function like this:

    def active_user_check(args \\ [:ctx]) do
      %Check{module: MyApp.MyModule, fun: :active_user, args: args}
    end

You can then use this function when building complex rules:

    Spek.all_of[
      MyApp.MyModule.active_user_check(),
      # ...
    ])

The second argument sets the default `args`. This:

    build_check(:active_user, [{:ctx, :user}])

Compiles to:
  
    def active_user_check(args \\ [{:ctx, :user}]) do
      %Check{module: MyApp.MyModule, fun: :active_user, args: args}
    end

# `defcheck`
*macro* 

Generates three functions from a single check definition.

## Generated functions

The arity of the generated function depends on the number of arguments passed
to the macro.

- `{name}?` - A predicate function that returns the result of the boolean
  expression defined in the do-block.
- `{name}` - A function that runs the boolean expression defined in the
  do-block and returns `:ok` or `{:error, term}`.
- `{name}_check` - A function that returns a `Spek.Check` struct.

## Options

- `:args` - The list of arguments as used in the `Spek.Check` struct.
  Defaults to `[:ctx]`.
- `:reason` - The reason used in the error tuple. Defaults to `:failed`.

## Do-block

The do-block is required to evaluate to a boolean value.

## Example

This macro call:

    defmodule MyApp.MyModule do
      import Spek.Macros
      
      defcheck account_balanced(account,
                 args: [:ctx],
                 reason: :account_unbalanced
               ) do
        account.balance >= 0
      end

    end

Will result in these three functions:

    def account_balanced?(account) do
      account.balance >= 0
    end

    def account_balanced(account) do
      if account_balanced(account),
        do: :ok,
        else: {:error, :account_unbalanced}
    end

    def account_balanced_check(args \\ [:ctx]) do
      %Check{module: MyApp.MyModule, fun: :account_balanced, args: args}
    end

The `account_balanced?/1` and `account_balanced/1` functions can be used
directly, and the `account_balanced_check/0` function can be used
with the Spek evaluation functions, or be combined with additional checks to
define complex rules.

    def transfer_rule do
      Spek.all_of([
        account_balanced_check(),
        # additional checks
      ])
    end

    Spek.eval(transfer_rule(), %Account{balance: 100})

You can also override the check arguments, e.g. if you combine multiple checks
that work on different data:

    def transfer_rule do
      Spek.all_of([
        account_balanced_check([{:ctx, :account}]),
        # additional checks
      ])
    end

    Spek.eval(transfer_rule(), account: %Account{balance: 100})

The generated functions can have an arbitrary number of arguments. For
example, this macro call defines two arguments, `user` and `organization`:

    defcheck matching_organization(user, organization,
               args: [{:ctx, :user}, {:ctx, :organization}],
               reason: :no_organization_match
             ) do
      user.organization_id == organization.id
    end

Which is expanded to:
    
    def matching_organization?(user, account) do
      user.organization_id == organization.id
    end

    def matching_organization(user, account) do
      if matching_organization?(user, account),
        do: :ok,
        else: {:error, :no_organization_match}
    end

    def matching_organization_check(args \\ [{:ctx, :user}, {:ctx, :organization}]) do
      %Check{
        module: MyApp.MyModule,
        fun: :matching_organization,
        args: args
      }
    end

In this case, we would call the Spek evaluation functions like this:

    Spek.eval(matching_organization_check(),
      user: %User{organization_id: 1},
      organization: %Organization{id: 1}
    )

---

*Consult [api-reference.md](api-reference.md) for complete listing*
