Convenience macros for defining check functions.
The usage of these macros is optional, but they can make your rules more readable.
Summary
Functions
Defines a function that returns a Spek.Check struct that uses an existing
function in the same module.
Generates three functions from a single check definition.
Functions
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)
endThis 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}
endYou 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
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:okor{:error, term}.{name}_check- A function that returns aSpek.Checkstruct.
Options
:args- The list of arguments as used in theSpek.Checkstruct. 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
endWill 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}
endThe 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
endWhich 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
}
endIn 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}
)