# `LetMe.Policy`
[🔗](https://github.com/woylie/let_me/blob/3.0.4/lib/let_me/policy.ex#L1)

This module defines a DSL for authorization rules and compiles these rules
to authorization and introspection functions.

## Usage

    defmodule MyApp.Policy do
      use LetMe.Policy

      object :article do
        # Creating articles is allowed if the user role is `editor` or `writer`.
        action :create do
          allow role: :editor
          allow role: :writer
        end

        # Viewing articles is always allowed, unless the user is banned.
        action :read do
          allow true
          deny :banned
        end

        # Updating an article is allowed if (the user role is `editor`) OR
        # (the user role is `writer` AND the article belongs to the user).
        action :update do
          allow role: :editor
          allow [:own_resource, role: :writer]
        end

        # Deleting an article is allowed if the user is an editor.
        action :delete do
          allow role: :editor
        end
      end
    end

> #### `use LetMe.Policy` {: .info}
>
> When you `use LetMe.Policy`, the module will set `@behaviour LetMe.Policy`
> and define all callback functions for that behaviour based on the macros
> you use. It will also define an `action` type based your rules.

### Options

`use LetMe.Policy` accepts two options, both of which are optional.

#### check_module

The module where the check functions are defined. Defaults to
`__MODULE__.Checks`.

#### error

If set to `:detailed`, `c:LetMe.Policy.authorize/4` collects the
evaluated policy expressions and returns an
`{:error, LetMe.UnauthorizedError.t()}` tuple if the policy check fails.

If set to `:simple`, `c:LetMe.Policy.authorize/4` returns an
`{:error, LetMe.UnauthorizedError.t()}` tuple without expression.

Any other value will be used unchanged in the error tuple.

While `c:LetMe.Policy.authorize!/4` always raises a `LetMe.UnauthorizedError`
on error, the exception struct only contains the expression if `error` is
`:detailed`.

To prevent unnecessary work, it is recommended to only set the option to
`:detailed` if you do something with the collected expressions, e.g. for
logging policy decisions or to inform the user about why a policy check
failed.

It is also possible to override the default by passing the option directly to
the authorize functions.

The default value is `:unauthorized`.

## Check module

The checks passed to `allow/1` and `deny/1` reference the names of functions
in the check module.

By default, LetMe tries to find the functions in `__MODULE__.Checks` (in the
example, this would be `MyApp.Policy.Checks`). However, you can override the
default check module:

    use LetMe.Policy, check_module: MyApp.AuthChecks

Each check function has to take the subject (user), the object, and optionally
an additional argument, and must return a boolean value.

For example, this check determines whether a user is banned:

    def banned(%User{banned: true}, _), do: true
    def banned(%User{}, _), do: false

This check determines whether the user has the given role:

    def role(%User{role: role}, _, role), do: true
    def role(_, _, _), do: false

And this check determines whether the object belongs to the user:

    def own_resource(%User{id: user_id}, %{user_id: user_id}), do: true
    def own_resource(_, _), do: false

LetMe does not make any assumptions about your access control model, as long
as you can map your rules to subject, object and action. You can use the three
rules above with the `allow/1` and `deny/1` macros.

    allow role: :admin
    allow :own_resource
    deny :banned

## Combining checks

Rules evaluate to `false` by default. These rules will always be `false`
because they don't have any `allow` clauses:

    action :create do
    end

    action :update do
      deny false
    end

Trying to evaluate a rule name that does not exist also evaluates to `false`.

As soon as one `deny` check evaluates to `true`, the whole rule will evaluate
to `false`. This rule will always evaluate to `false`:

    action :create do
      allow true
      deny true
    end

If you pass a list of checks to either `allow/1` or `deny/1`, the checks
are combined with a logical `AND`.

    # false
    action :create do
      allow [true, false]
    end

    # true
    action :create do
      allow [true, true]
    end

    # true
    action :create do
      allow [true, true]
      deny [true, false]
    end

    # false
    action :create do
      allow [true, true]
      deny [true, true]
    end

On the other hand, if either the `allow/1` or the `deny/1` macro is used
multiple times, the checks are combined with a logical `OR`.

    # true
    action :create do
      allow true
      allow false
    end

    # false
    action :create do
      allow [true, false]
      allow false
    end

    # true
    action :create do
      allow [true, false]
      allow true
    end

    # false
    action :create do
      allow [true, true]
      allow true
      deny false
      deny true
    end

## Pre-hooks

You can use pre-hooks to process or gather additional data about the subject
and/or object before running the checks. This can be useful if you need to
preload associations or make external requests. Pre-hooks run once per
authorization request before running the checks. See the documentation for
`pre_hooks/1`.

# `authorize`

```elixir
@callback authorize(atom(), any(), any(), keyword()) :: :ok | {:error, any()}
```

Authorizes a request defined by the action, subject and object.

## Example

Assume we defined this authorization rule:

    object :article do
      action :update do
        allow :own_resource
      end
    end

And the `:own_resource` check is defined as:

    def own_resource(%{id: user_id}, %{user_id: user_id}), do: true
    def own_resource(_, _), do: false

The rule name consists of the object and the action name, in this case
`:article_create`. To authorize the action, we need to pass the rule name, the
subject (current user) and the object (the article to be updated).

This example assumes that the `error` option is set to `:detailed`
(see below).

    iex> article = %{id: 80, user_id: 1}
    iex> user_1 = %{id: 1}
    iex> user_2 = %{id: 2}
    iex> MyApp.Policy.authorize(:article_update, user_1, article)
    :ok
    iex> MyApp.Policy.authorize(:article_update, user_2, article)
    {:error, %LetMe.UnauthorizedError{
      message: "unauthorized",
      expression: %Spek.Check{
        args: [{:ctx, :subject}, {:ctx, :object}],
        module: MyApp.Checks,
        fun: :own_resource,
        result: false,
        satisfied?: false
      }
    }}

If the checks don't require the object, it can be omitted.

    object :user do
      action :list do
        allow {:role, :admin}
        allow {:role, :client}
      end
    end

    iex> user = %{id: 1, role: :admin}
    iex> MyApp.Policy.authorize(:user_list, user)
    :ok
    iex> user = %{id: 2, role: :user}
    iex> MyApp.Policy.authorize(:user_list, user)
    {
      :error,
      %LetMe.UnauthorizedError{
        expression: %Spek.AnyOf{
          children: [
            %Spek.Check{
            module: MyApp.Checks,
              fun: :role,
              args: [{:ctx, :subject}, {:ctx, :object}, :admin],
              result: false,
              satisfied?: false
            },
            %Spek.Check{
            module: MyApp.Checks,
              fun: :role,
              args: [{:ctx, :subject}, {:ctx, :object}, :client],
              result: false,
              satisfied?: false
            }
          ],
          satisfied?: false
        },
        message: "unauthorized"
      }
    }

## Error format

By default, `{:error, :unauthorized}` is returned if the policy check fails.
You can customize the error by passing the `error` option to the function.

```elixir
MyApp.Policy.authorize(:article_update, scope, object, error: :forbidden)

{:error, :forbidden}
```

You can also set a default as an option for `use LetMe.Policy` (see module
documentation).

To obtain a `LetMe.UnauthorizedError` struct with detailed evaluation
information, set `error: :detailed`.

```elixir
MyApp.Policy.authorize(:article_update, scope, object, error: :detailed)

%LetMe.UnauthorizedError{
  message: "unauthorized",
  expression: %Spek.AnyOf{
    children: [
      %Spek.Check{
        module: MyApp.Policy.Checks,
        fun: :role,
        args: [{:ctx, :subject}, {:ctx, :object}, :editor],
        result: false,
        satisfied?: false
      },
      %Spek.Check{
        module: MyApp.Policy.Checks,
        fun: :role,
        args: [{:ctx, :subject}, {:ctx, :object}, :writer],
        result: false,
        satisfied?: false
      }
    ]
  }
}
```

To obtain a `LetMe.UnauthorizedError` struct without the expression, set
`error: :simple`.

```elixir
MyApp.Policy.authorize(:article_update, scope, object, error: :simple)

%LetMe.UnauthorizedError{
  message: "unauthorized",
  expression: nil
}
```

## Additional options

All options except `:error` are passed into any `pre_hook`s defined on the
resource's policy. This can be used to dynamically pass additional
information.

# `authorize!`

```elixir
@callback authorize!(atom(), any(), any(), keyword()) :: :ok
```

Same as `c:authorize/4`, but raises an error if unauthorized.

## Example

With the same authorization rules as defined in the `c:authorize/4`
documentation, we get this:

    iex> article = %{id: 80, user_id: 1}
    iex> user_1 = %{id: 1}
    iex> user_2 = %{id: 2}
    iex> MyApp.Policy.authorize!(:article_update, user_1, article)
    :ok
    iex> MyApp.Policy.authorize!(:article_update, user_2, article)
    ** (LetMe.UnauthorizedError) unauthorized

## Error details

If you require detailed information about the performed checks under the
`expression` field in the `LetMe.UnauthorizedError` exception struct, you can
set the `error` option to `:detailed`.

    MyApp.Policy.authorize!(:article_update, scope, object, error: :detailed)

With any other option values, the `expression` field will be `nil`.

You can also set the default error option on `use LetMe.Policy` (see module
documentation).

# `authorize?`

```elixir
@callback authorize?(atom(), any(), any(), keyword()) :: boolean()
```

Same as `c:authorize/4`, but returns a boolean.

## Example

With the same authorization rules as defined in the `c:authorize/4`
documentation, we get this:

    iex> article = %{id: 80, user_id: 1}
    iex> user_1 = %{id: 1}
    iex> user_2 = %{id: 2}
    iex> MyApp.Policy.authorize?(:article_update, user_1, article)
    true
    iex> MyApp.Policy.authorize?(:article_update, user_2, article)
    false

# `fetch_expression`
*since 3.0.0* 

```elixir
@callback fetch_expression(atom()) :: {:ok, Spek.expression()} | :error
```

Returns the expression of the rule with the given name. Returns an `:ok` tuple
or `:error`.

The rule name is an atom with the format `{object}_{action}`.

## Example

    iex> MyApp.Policy.fetch_expression(:article_create)
    {:ok,
     %Spek.AnyOf{
       children: [
         %Spek.Check{module: MyApp.Checks, fun: :role, args: [{:ctx, :subject}, {:ctx, :object}, :admin]},
         %Spek.Check{module: MyApp.Checks, fun: :role, args: [{:ctx, :subject}, {:ctx, :object}, :writer]}
       ],
     }}

     iex> MyApp.Policy.fetch_expression(:cookie_eat)
     :error

# `fetch_expression!`
*since 3.0.0* 

```elixir
@callback fetch_expression!(atom()) :: Spek.expression()
```

Returns the expression of the rule with the given name. Raises if the rule is
not found.

The rule name is an atom with the format `{object}_{action}`.

## Example

    iex> MyApp.Policy.fetch_expression!(:article_create)
    %Spek.AnyOf{children: [
      %Spek.Check{module: MyApp.Checks, fun: :role, args: [{:ctx, :subject}, {:ctx, :object}, :admin]},
      %Spek.Check{module: MyApp.Checks, fun: :role, args: [{:ctx, :subject}, {:ctx, :object}, :writer]}
    ]}

# `fetch_rule`

```elixir
@callback fetch_rule(atom()) :: {:ok, LetMe.Rule.t()} | :error
```

Returns the rule for the given name. Returns an `:ok` tuple or `:error`.

The rule name is an atom with the format `{object}_{action}`.

## Example

    iex> MyApp.Policy.fetch_rule(:article_create)
    {:ok,
     %LetMe.Rule{
       action: :create,
       expression: %Spek.AnyOf{
         children: [
           %Spek.Check{module: MyApp.Checks, fun: :role, args: [{:ctx, :subject}, {:ctx, :object}, :admin]},
           %Spek.Check{module: MyApp.Checks, fun: :role, args: [{:ctx, :subject}, {:ctx, :object}, :writer]}
         ],
       },
       name: :article_create,
       object: :article,
       pre_hooks: []
     }}

     iex> MyApp.Policy.fetch_rule(:cookie_eat)
     :error

# `fetch_rule!`

```elixir
@callback fetch_rule!(atom()) :: LetMe.Rule.t()
```

Returns the rule with the given name. Raises if the rule is not found.

The rule name is an atom with the format `{object}_{action}`.

## Example

    iex> MyApp.Policy.fetch_rule!(:article_create)
    %LetMe.Rule{
      action: :create,
      expression: %Spek.AnyOf{children: [
        %Spek.Check{module: MyApp.Checks, fun: :role, args: [{:ctx, :subject}, {:ctx, :object}, :admin]},
        %Spek.Check{module: MyApp.Checks, fun: :role, args: [{:ctx, :subject}, {:ctx, :object}, :writer]}
      ]},
      name: :article_create,
      object: :article,
      pre_hooks: []
    }

# `filter_allowed_actions`

```elixir
@callback filter_allowed_actions([LetMe.Rule.t()], subject, object) :: [LetMe.Rule.t()]
when subject: any(), object: {atom(), any()} | struct()
```

Takes a list of rules and only returns the rules that would evaluate to `true`
for the given subject and object.

## Examples

The object can be passed as a tuple, where the first element is the
object name, and the second element is the actual object, e.g.
`{:article, %Article{}}`.

    iex> rules = MyApp.Policy.list_rules()
    iex> MyApp.Policy.filter_allowed_actions(
    ...>   rules,
    ...>   %{id: 2, role: nil},
    ...>   {:article, %MyApp.Blog.Article{}}
    ...> )
    [
      %LetMe.Rule{
        action: :view,
        expression: %Spek.Literal{result: true, satisfied?: true},
        description: "allows to view an article and the list of articles",
        name: :article_view,
        object: :article,
        pre_hooks: []
      }
    ]

If you registered the schema module with `LetMe.Policy.object/3`, you can
pass the struct without tagging it with the object name.

    iex> rules = MyApp.Policy.list_rules()
    iex> MyApp.Policy.filter_allowed_actions(
    ...>   rules,
    ...>   %{id: 2, role: nil},
    ...>   %MyApp.Blog.Article{}
    ...> )
    [
      %LetMe.Rule{
        action: :view,
        expression: %Spek.Literal{result: true, satisfied?: true},
        description: "allows to view an article and the list of articles",
        name: :article_view,
        object: :article,
        pre_hooks: []
      }
    ]

# `get_expression`
*since 3.0.0* 

```elixir
@callback get_expression(atom()) :: Spek.expression() | nil
```

Returns the expression of the rule with the given name. Returns `nil` if the
rule is not found.

The rule name is an atom with the format `{object}_{action}`.

## Example

    iex> MyApp.Policy.get_expression(:article_create)
    %Spek.AnyOf{
      children: [
        %Spek.Check{module: MyApp.Checks, fun: :role, args: [{:ctx, :subject}, {:ctx, :object}, :admin]},
        %Spek.Check{module: MyApp.Checks, fun: :role, args: [{:ctx, :subject}, {:ctx, :object}, :writer]}
      ]
    }

    iex> MyApp.Policy.get_expression(:cookie_eat)
    nil

# `get_object_name`

```elixir
@callback get_object_name(module()) :: atom() | nil
```

Returns the object name for the given schema module or struct, if it was
registered using `object/3`.

## Examples

    iex> MyApp.Policy.get_object_name(MyApp.Blog.Article)
    :article

    iex> MyApp.Policy.get_object_name(%MyApp.Blog.Article{})
    :article

    iex> MyApp.Policy.get_object_name(MyApp.Blog.Tag)
    nil

# `get_rule`

```elixir
@callback get_rule(atom()) :: LetMe.Rule.t() | nil
```

Returns the rule for the given rule name. Returns `nil` if the rule is
not found.

The rule name is an atom with the format `{object}_{action}`.

## Example

    iex> MyApp.Policy.get_rule(:article_create)
    %LetMe.Rule{
      action: :create,
      expression: %Spek.AnyOf{
        children: [
          %Spek.Check{module: MyApp.Checks, fun: :role, args: [{:ctx, :subject}, {:ctx, :object}, :admin]},
          %Spek.Check{module: MyApp.Checks, fun: :role, args: [{:ctx, :subject}, {:ctx, :object}, :writer]}
        ]
      },
      name: :article_create,
      object: :article,
      pre_hooks: []
    }

    iex> MyApp.Policy.get_rule(:cookie_eat)
    nil

# `get_schema`

```elixir
@callback get_schema(atom()) :: module() | nil
```

Returns the schema module for the given object name, if it was registered
using `object/3`.

## Examples

    iex> MyApp.Policy.get_schema(:article)
    MyApp.Blog.Article

    iex> MyApp.Policy.get_schema(:user)
    nil

# `list_rules`

```elixir
@callback list_rules() :: [LetMe.Rule.t()]
```

Returns all authorization rules as a list.

## Example

    iex> MyApp.PolicyShort.list_rules() |> Enum.sort()
    [
      %LetMe.Rule{
        action: :create,
        expression: %Spek.AnyOf{
          children: [
            %Spek.Check{module: MyApp.Checks, fun: :role, args: [{:ctx, :subject}, {:ctx, :object}, :admin]},
            %Spek.Check{module: MyApp.Checks, fun: :role, args: [{:ctx, :subject}, {:ctx, :object}, :writer]}
          ]
        },
        name: :article_create,
        object: :article,
        pre_hooks: []
      },
      %LetMe.Rule{
        action: :update,
        expression: %Spek.Check{module: MyApp.Checks, fun: :own_resource, args: [{:ctx, :subject}, {:ctx, :object}]},
        name: :article_update,
        object: :article,
        pre_hooks: [:preload_groups]
      }
    ]

# `list_rules`

```elixir
@callback list_rules(keyword()) :: [LetMe.Rule.t()]
```

Same as `c:list_rules/0`, but takes a keyword list with filter options.

See `LetMe.filter_rules/2` for a list of available filter options.

# `action`
*macro* 

```elixir
@spec action(atom() | [atom()], Macro.t()) :: Macro.t()
```

Defines an action that needs to be authorized.

Within the do-block, you can use the `allow/1`, `deny/1` and `pre_hooks/1`
macros to define the checks to be run and the `desc/1` macro to add a
description.

This macro must be used within the do-block of `object/2`.

Each `action` block will be compiled to a rule. The rule name is an atom with
the format `{object}_{action}`.

## Example

    object :article do
      action :create do
        allow role: :editor
        allow role: :writer
      end

      action :update do
        allow role: :editor
        allow [:own_resource, role: :writer]
      end
    end

If you have multiple actions with the same allow and deny rules, you can also
pass a list of action names as the first argument.

    object :article do
      action [:create, :update, :delete] do
        allow role: :editor
        allow role: :writer
      end
    end

# `allow`
*macro* 

```elixir
@spec allow(LetMe.Rule.check() | [LetMe.Rule.check()]) :: Macro.t()
```

Defines the checks to be run to determine if an action is allowed.

The argument can be:

- a function name as an atom
- a tuple with the function name and an additional argument
- a list of function names or function/argument tuples
- `true` or `false` - Always allows or denies an action. Can be useful in
  combination with the `deny/1` macro.

The function must be defined in the configured check module and take the
subject (current user), object as arguments, and if given, the additional
argument.

If a list is given as an argument, the checks are combined with a logical
`AND`.

If the `allow/1` macro is used multiple times within the same `action/2`
block, the checks of each macro call are combined with a logical `OR`.

## Examples

Let's assume you defined the following checks:

    defmodule MyApp.Policy.Checks do
      def role(%User{role: role}, _, role), do: true
      def role(_, _, _), do: false

      def own_resource(%User{id: id}, %{user_id: id}, _), do: true
      def own_resource(_, _, _), do: false
    end

This would allow the `:article_update` action only if the current user has
the role `:admin`:

    object :article do
      action :update do
        allow role: :admin
      end
    end

This is equivalent to:

    object :article do
      action :update do
        allow {:role, :admin}
      end
    end

This would allow the `:article_update` action if the user has the role
`:writer` _and_ the article belongs to the user:

    object :article do
      action :update do
        allow [:own_resource, role: :writer]
      end
    end

This is equivalent to:

    object :article do
      action :update do
        allow [:own_resource, {:role, :writer}]
      end
    end

This would allow the `:article_update` action if
(the user has the role `:admin` _or_ (the user has the role `:writer` _and_
the article belongs to the user)):

    object :article do
      action :update do
        allow role: :admin
        allow [:own_resource, role: :writer]
      end
    end

# `deny`
*macro* 

```elixir
@spec deny(LetMe.Rule.check() | [LetMe.Rule.check()]) :: Macro.t()
```

Defines the checks to be run to determine if an action is denied.

If any of the checks evaluates to `true`, the `allow` checks are overridden
and the authorization request is automatically denied.

If a list is given as an argument, the checks are combined with a logical
`AND`.

If the `deny/1` macro is used multiple times within the same `action/2`
block, the checks of each macro call are combined with a logical `OR`.

## Examples

Let's assume you defined the following checks:

    defmodule MyApp.Policy.Checks do
      def role(%User{role: role}, _, role), do: true
      def role(_, _, _), do: false

      def own_resource(%User{id: id}, %{user_id: id}, _), do: true
      def own_resource(_, _, _), do: false

      def same_user(%User{id: id}, %User{id: id}, _), do: true
      def same_user(_, _, _), do: false
    end

This would allow the `:user_delete` action by default, _unless_ the object is
the current user:

    object :user do
      action :delete do
        allow true
        deny :same_user
      end
    end

This would allow the `:article_update` action only if the current user has
the role `:admin`, _unless_ the object is the current user:

    object :user do
      action :delete do
        allow role: :admin
        deny :same_user
      end
    end

This would allow the `:user_delete` by default, _unless_ the object is the
current user _and_ the current user is an admin:

    object :user do
      action :delete do
        allow true
        deny [:same_user, role: :admin]
      end
    end

This would allow the `:user_delete` by default, _unless_ the object is the
current user _or_ the current user is a peasant:

    object :user do
      action :delete do
        allow true
        deny :same_user
        deny role: :peasant
      end
    end

# `desc`
*macro* 

```elixir
@spec desc(String.t()) :: Macro.t()
```

Allows you to add a description to a rule.

The description can be accessed from the `LetMe.Rule` struct. You can use it
to generate help texts or documentation.

## Example

    object :article do
      action :create do
        desc "allows a user to create a new article"
        allow role: :writer
      end
    end

# `metadata`
*macro* 

```elixir
@spec metadata(atom(), term()) :: Macro.t()
```

Assigns metadata to the action in the form of a key value pair.

The metadata can be accessed from the `LetMe.Rule` struct. You can use it
to extend the functionality of the library.

## Example

    object :article do
      action :create do
        allow role: :writer

        desc "Allows a user to create a new article."
        metadata :desc_ja, "ユーザーが新しい記事を作成できるようにする"
      end
    end

The `LetMe.Rule` struct returned by the introspection functions would look
like this:

    %LetMe.Rule{
      action: :create,
      expression: %Spek.Check{
        module: MyApp.Policy.Checks,
        fun: :role,
        args: [{:ctx, :subject}, {:ctx, :object}, :writer]
      },
      description: "Allows a user to create a new article.",
      name: :article_create,
      object: :article,
      pre_hooks: [],
      metadata: [
        desc_ja: "ユーザーが新しい記事を作成できるようにする"
      ]
    }

It is also possible to use the `metadata` macro multiple times.

    object :article do
      action :create do
        allow role: :writer

        desc "Allows a user to create a new article."
        metadata :desc_ja, "ユーザーが新しい記事を作成できるようにする"
        metadata :desc_es, "Permite al usuario crear un nuevo artículo."
      end
    end

This would result in:

    %LetMe.Rule{
      action: :create,
      expression: %Spek.Check{
        module: MyApp.Policy.Checks,
        fun: :role,
        args: [{:ctx, :subject}, {:ctx, :object}, :writer]
      },
      description: "Allows a user to create a new article.",
      name: :article_create,
      object: :article,
      pre_hooks: [],
      metadata: [
        desc_ja: "ユーザーが新しい記事を作成できるようにする",
        desc_es: "Permite al usuario crear un nuevo artículo."
      ]
    }

# `object`
*macro* 

```elixir
@spec object(atom(), module() | nil, Macro.t()) :: Macro.t()
```

Defines an object on which actions can be performed.

Within the do-block, you can use the `action/2` macro to define the actions
and checks.

## Examples

    object :article do
      action :create do
        allow role: :writer
      end

      action :delete do
        allow role: :editor
      end
    end

You can optionally pass the schema module as the second argument. The schema
module should implement the `LetMe.Schema` behaviour.

    object :article, MyApp.Blog.Article do
      action :create do
        allow role: :writer
      end
    end

At the moment, this doesn't do much, except that you can find the schema
module by passing the object name to `c:get_schema/1`, or find the object name
by passing the schema module or struct to `c:get_object_name/1` now. Also,
you can now only pass the struct to `c:filter_allowed_actions/3`, without
explicitly passing the object name.

# `pre_hooks`
*macro* 

```elixir
@spec pre_hooks(LetMe.Rule.hook() | [LetMe.Rule.hook()]) :: Macro.t()
```

Registers one or multiple functions to run in order to hydrate the subject
and/or object of the request.

This is useful if you need to enhance the data for multiple checks in the
same action by preloading associations, making external requests, or similar
things. The configured hook functions will be called once before running the
checks for an action.

The referenced functions must take the subject and object as arguments and
return a 2-tuple with the updated subject and object.

The referenced functions may also take an optional third argument that are
opts passed through the `authorize` functions. These opts are merged into
any opts that are specified in the `pre_hook` definition.

## Examples

Let's assume we defined these check and hook functions in our check module:

    def MyApp.Policy.Checks do
      # Checks

      def min_age(%{age: age}, _, min_age), do: age >= min_age

      # Hooks

      def double_age(subject, object) do
        new_subject = %{subject | age: subject.age * 2}
        {new_subject, object}
      end

      def set_age(subject, object, age: age) do
        new_subject = %{subject | age: age}
        {new_subject, object}
      end
    end

If an atom is passed, LetMe will try to find the function in the check module.

    object :article do
      action :view do
        pre_hooks :double_age
        allow min_age: 50
      end
    end

With this in place, the following authorization request will evaluate to
`true`:

    MyApp.Policy.authorize!(:article_view, %{age: 25})
    # => true

If your hooks are defined in a different module, you can also pass a
module/function tuple. The pre-hook configuration above is equivalent to:

    object :article do
      action :view do
        pre_hooks {MyApp.Policy.Checks, :double_age}
        allow min_age: 50
      end
    end

You can also pass options to a hook by using an MFA tuple:

    object :article do
      action :view do
        pre_hooks {MyApp.Policy.Checks, :set_age, age: 50}
        allow min_age: 50
      end
    end

    MyApp.Policy.authorize!(:article_view, %{age: 10})
    # => true

You can achieve the same functionality dynamically using the opts on `authorize!`:

    object :article do
      action :view do
        pre_hooks {MyApp.Policy.Checks, :set_age}
        allow min_age: 50
      end
    end

    MyApp.Policy.authorize!(:article_view, %{age: 10}, age: 50)
    # => true

And finally, you can also pass a list of hooks, which will be run in sequence:

    alias MyApp.Policy.Checks

    object :article do
      action :view do
        pre_hooks [{Checks, :set_age, age: 25}, :double_age]
        allow min_age: 50
      end
    end

    MyApp.Policy.authorize!(:article_view, %{age: 10})
    # => true

---

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