# Collection hook

{% hint style="success" %}
This is the official documentation of the `agent_ruby` Ruby agent.
{% endhint %}

Forest Admin allows customizing at a very low level the behavior of any given Collection via the usage of Collection Hooks.

{% hint style="info" %}
Collection Hooks are a very powerful feature and require special care when using them.
{% endhint %}

### How it works

Any given Collection should implement all of the following functions:

* `list`
* `create`
* `update`
* `delete`
* `aggregate`

The Collection Hooks feature allows executing code before and/or after any of these functions, providing an easy way to interact with your Collections.

To declare a Hook on a Collection, the following information is required:

* A lifecycle position (`Before` | `After`)
* An action type (`List` | `Create` | `Update` | `Delete` | `Aggregate`)
* A callback, that will receive a context matching the provided hook position and hook definition.

### Context object reference

All hook contexts provide access to:

* `collection` - the current collection, which can be queried using the [Forest Admin Query Interface](/developer-guide-agents-ruby/data-sources/getting-started/queries.md)
* `dataSource` - the composite data source containing all collections
* `caller` - information about the user performing the operation

#### The `caller` object

The `caller` object contains information about the current user:

| Property    | Description   |
| ----------- | ------------- |
| `id`        | User ID       |
| `email`     | User email    |
| `firstName` | First name    |
| `lastName`  | Last name     |
| `team`      | Team name     |
| `role`      | Role name     |
| `tags`      | Custom tags   |
| `timezone`  | User timezone |

#### Error methods

All contexts provide methods to throw errors that will be displayed in the Forest Admin UI:

| Method                            | Description                |
| --------------------------------- | -------------------------- |
| `throw_validation_error(message)` | Display a validation error |
| `throw_forbidden_error(message)`  | Display a forbidden error  |
| `throw_error(message)`            | Display a generic error    |

#### Hook-specific context properties

Each hook type provides additional properties:

| Hook          | Position     | Properties                                                   |
| ------------- | ------------ | ------------------------------------------------------------ |
| **List**      | Before       | `filter` (paginated filter), `projection` (fields to return) |
| **List**      | After        | Same as Before + `records` (returned records)                |
| **Create**    | Before       | `data` (data to create)                                      |
| **Create**    | After        | `data` + `records` (created records)                         |
| **Update**    | Before       | `filter`, `patch` (read-only)                                |
| **Update**    | After        | `filter`, `patch` (read-only)                                |
| **Delete**    | Before/After | `filter` (filter for records to delete)                      |
| **Aggregate** | Before       | `filter`, `aggregation`, `limit`                             |
| **Aggregate** | After        | Same as Before + `aggregateResult`                           |

{% hint style="warning" %}
A single Collection can have multiple Hooks with the same position and the same type. They will run in their declaration order. Collection Hooks are only called when the Collection function is contacted by the UI. This means that any usage of the Forest Admin [query interface](/developer-guide-agents-ruby/data-sources/getting-started/queries.md) will not trigger them.
{% endhint %}

### Basic use cases

In the following example, we want to prevent a set of users from updating any records of the `Transactions` table. We want to check if the user email is allowed to update a record via an external API call.

#### Traditional Syntax

Using full context methods with `customize_collection`:

```ruby
@create_agent.customize_collection('transactions') do |collection|
  collection.add_hook('Before', 'Update') do |context|
    # context.caller contains information about the current user, the defined timezone, etc.
    # In this case, context.caller.email is the email used in Forest Admin by the user that initiated the call
    is_allowed = my_function_to_check_user_is_allowed(context.caller.email)
    unless is_allowed
      # Raising an error here will prevent the execution of the update function,
      # as well as any other hooks that may be defined afterwards.
      context.raise_forbidden_error("#{context.caller.email} is not allowed!")
    end
  end
end
```

#### DSL Syntax

Using simplified DSL with `collection`:

```ruby
@create_agent.collection :transactions do |collection|
  collection.before :update do |context|
    is_allowed = my_function_to_check_user_is_allowed(context.caller.email)
    unless is_allowed
      context.raise_forbidden_error("#{context.caller.email} is not allowed!")
    end
  end
end
```

Another good example would be the following: Each time a new `User` is created in the database, I want to send him an email.

#### Traditional Syntax

Using full context methods with `customize_collection`:

```ruby
@create_agent.customize_collection('user') do |collection|
  collection.add_hook('After', 'Create') do |context|
    # The result of the create function always return a record
    email = context.record['email']
    MyEmailSender.send_email(
      from: 'erlich@bachman.com',
      to: email,
      message: 'Hey, a new account was created with this email.'
    )
  end
end
```

#### DSL Syntax

Using simplified DSL with `collection`:

```ruby
@create_agent.collection :user do |collection|
  collection.after :create do |context|
    email = context.record['email']
    MyEmailSender.send_email(
      from: 'erlich@bachman.com',
      to: email,
      message: 'Hey, a new account was created with this email.'
    )
  end
end
```

### Advanced examples

#### Preventing deletion of protected records

```ruby
@create_agent.customize_collection('user') do |collection|
  collection.add_hook('Before', 'Delete') do |context|
    records = context.collection.list(context.filter, ['protected', 'email'])

    protected_records = records.select { |r| r['protected'] }
    if protected_records.any?
      emails = protected_records.map { |r| r['email'] }.join(', ')
      context.throw_forbidden_error("Cannot delete protected users: #{emails}")
    end
  end
end
```

#### Querying other collections via dataSource

```ruby
@create_agent.customize_collection('order') do |collection|
  collection.add_hook('Before', 'Create') do |context|
    customers_collection = context.datasource.get_collection('customer')

    context.data.each do |order|
      filter = { field: 'id', operator: 'Equal', value: order['customerId'] }
      customer = customers_collection.list(filter, ['creditLimit', 'currentBalance']).first

      if customer['currentBalance'] + order['amount'] > customer['creditLimit']
        context.throw_validation_error("Order exceeds credit limit for customer #{order['customerId']}")
      end
    end
  end
end
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.forestadmin.com/developer-guide-agents-ruby/agent-customization/hooks/collection-hook.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
