# Smart Fields

{% hint style="warning" %}
Please be sure of your agent type and version and pick the right documentation accordingly.
{% endhint %}

{% tabs %}
{% tab title="Node.js" %}
{% hint style="danger" %}
This is the documentation of the `forest-express-sequelize` and `forest-express-mongoose` Node.js agents that will soon reach end-of-support.

`forest-express-sequelize` v9 and `forest-express-mongoose` v9 are replaced by [`@forestadmin/agent`](https://docs.forestadmin.com/developer-guide-agents-nodejs/) v1.

Please check your agent type and version and read on or switch to the right documentation.
{% endhint %}
{% endtab %}

{% tab title="Ruby on Rails" %}
{% hint style="success" %}
This is still the latest Ruby on Rails documentation of the `forest_liana` agent, you’re at the right place, please read on.
{% endhint %}
{% endtab %}

{% tab title="Python" %}
{% hint style="danger" %}
This is the documentation of the `django-forestadmin` Django agent that will soon reach end-of-support.

If you’re using a Django agent, notice that `django-forestadmin` v1 is replaced by [`forestadmin-agent-django`](https://docs.forestadmin.com/developer-guide-agents-python) v1.

If you’re using a Flask agent, go to the [`forestadmin-agent-flask`](https://docs.forestadmin.com/developer-guide-agents-python) v1 documentation.

Please check your agent type and version and read on or switch to the right documentation.
{% endhint %}
{% endtab %}

{% tab title="PHP" %}
{% hint style="danger" %}
This is the documentation of the `forestadmin/laravel-forestadmin` Laravel agent that will soon reach end-of-support.

If you’re using a Laravel agent, notice that `forestadmin/laravel-forestadmin` v1 is replaced by [`forestadmin/laravel-forestadmin`](https://docs.forestadmin.com/developer-guide-agents-php) v3.

If you’re using a Symfony agent, go to the [`forestadmin/symfony-forestadmin`](https://docs.forestadmin.com/developer-guide-agents-php) v1 documentation.

Please check your agent type and version and read on or switch to the right documentation.
{% endhint %}
{% endtab %}
{% endtabs %}

## Smart Fields

#### What is a Smart Field?

A field that displays a computed value in your collection.

<figure><img src="https://2014605362-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fx9GURwd0RAwi8uWZgH5v%2Fuploads%2FeWKsxwOaYG5iuWb9UuG5%2Fthe_smart_fields.jpg?alt=media&#x26;token=efa5cf19-3799-4022-ad58-6a3a42e9104c" alt=""><figcaption></figcaption></figure>

A Smart Field is a column that displays processed-on-the-fly data. It can be as simple as concatenating attributes to make them human friendly, or more complex (e.g. total of orders).

#### Creating a Smart Field

{% tabs %}
{% tab title="SQL" %}
On our Live Demo, the very simple Smart Field `fullname` is available on the `customers` collection.

{% code title="/forest/customers.js" %}

```javascript
const { collection } = require('forest-express-sequelize');

collection('customers', {
  fields: [
    {
      field: 'fullname',
      type: 'String',
      get: (customer) => {
        return customer.firstname + ' ' + customer.lastname;
      },
    },
  ],
});
```

{% endcode %}

\
Very often, the business logic behind the Smart Field is more complex and must be asynchronous. To do that, please have a look at [this section](#createadvancedsmartfield).
{% endtab %}

{% tab title="Mongodb" %}
On our Live Demo, the very simple Smart Field `fullname` is available on the `customers` collection.

{% code title="/forest/customers.js" %}

```javascript
const { collection } = require('forest-express-mongoose');

collection('customers', {
  fields: [
    {
      field: 'fullname',
      type: 'String',
      get: (customer) => {
        return customer.firstname + ' ' + customer.lastname;
      },
    },
  ],
});
```

{% endcode %}

\
Very often, the business logic behind the Smart Field is more complex and must be asynchronous. To do that, please have a look at [this section](#createadvancedsmartfield).
{% endtab %}

{% tab title="Rails" %}
On our Live Demo, the very simple Smart Field `fullname` is available on the `Customer` collection.

{% code title="/lib/forest\_liana/collections/customer.rb" %}

```ruby
class Forest::Customer
  include ForestLiana::Collection

  collection :Customer

  field :fullname, type: 'String' do
    "#{object.firstname} #{object.lastname}"
  end
end
```

{% endcode %}

Very often, the business logic behind the Smart Field is more complex and must interact with the database. Here’s an example with the Smart Field `full_address` on the `Customer` collection.

{% code title="/lib/forest\_liana/collections/customer.rb" %}

```ruby
class Forest::Customer
  include ForestLiana::Collection

  collection :Customer

  field :full_address, type: 'String' do
    address = Address.find_by(customer_id: object.id)
    "#{address[:address_line_1]} #{address[:address_line_2]} #{address[:address_city]} #{address[:country]}"
  end
end
```

{% endcode %}
{% endtab %}

{% tab title="Django" %}
On our Live Demo, the very simple Smart Field `fullname` is available on the `Customer` collection.

{% code title="app/forest/customer.py" %}

```python
from django_forest.utils.collection import Collection
from app.models import Customer


class CustomerForest(Collection):
    def load(self):
        self.fields = [
            {
                'field': 'fullname',
                'type': 'String',
                'get': self.get_fullname
            },
        ]

    def get_fullname(self, obj):
        return f'{obj.firstname} {obj.lastname}'


Collection.register(CustomerForest, Customer)
```

{% endcode %}

Ensure the file app/forest/\_\_init\_\_.py exists and contains the import of the previous defined class :

{% code title="app/forest/**init**.py" %}

```python
from app.forest.customer import CustomerForest
```

{% endcode %}

Very often, the business logic behind the Smart Field is more complex and must interact with the database. Here’s an example with the Smart Field `full_address` on the `Customer` collection.

{% code title="app/forest/customer.py" %}

```python
from django_forest.utils.collection import Collection
from app.models import Customer, Address


class CustomerForest(Collection):
    def load(self):
        self.fields = [
            {
                'field': 'full_address',
                'type': 'String',
                'get': self.get_full_address,
            },
        ]

    def get_full_address(self, obj):
        address = Address.objets.get(customer_id=obj.id)
        return f'{address.address_line_1} {address.address_line_2} {address.address_city} {address.country}'


Collection.register(CustomerForest, Customer)
```

{% endcode %}
{% endtab %}

{% tab title="Laravel" %}
On our Live Demo, the very simple Smart Field `fullname` is available on the `Customer` model.

{% code title="app/Models/Customer.php" %}

```php
<?php

namespace App\Models;

use ForestAdmin\LaravelForestAdmin\Services\Concerns\ForestCollection;
use ForestAdmin\LaravelForestAdmin\Services\SmartFeatures\SmartField;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

/**
 * Class Customer
 */
class Customer extends Model
{
    use HasFactory, ForestCollection;

    /**
     * @return SmartField
     */
    public function fullname(): SmartField
    {
        return $this->smartField(['type' => 'String'])
            ->get(fn() => $this->firstname . '-' . $this->lastname);
    }
}
```

{% endcode %}

Very often, the business logic behind the Smart Field is more complex and must interact with the database. Here’s an example with the Smart Field `full_address` on the `Customer` model.

{% code title="app/Models/Customer.php" %}

```php
<?php

namespace App\Models;

use ForestAdmin\LaravelForestAdmin\Services\Concerns\ForestCollection;
use ForestAdmin\LaravelForestAdmin\Services\SmartFeatures\SmartField;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

/**
 * Class Customer
 */
class Customer extends Model
{
    use HasFactory, ForestCollection;

    /**
     * @return SmartField
     */
    public function fullAddress(): SmartField
    {
        return $this->smartField(['type' => 'String'])
            ->get(
                function () {
                    $address = Address::firstWhere('customer_id', $this->id);

                    return "$address->address_line1  $address->address_line2 $address->address_city  $address->country";
                }
            );
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

{% hint style="warning" %}
The collection name must be the same as the **model name**.
{% endhint %}

![](https://2014605362-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fx9GURwd0RAwi8uWZgH5v%2Fuploads%2Fgit-blob-11c5a7323b1378903a8f6612af632eec52d880c6%2Fscreenshot%202019-07-01%20at%2012.16.05.png?alt=media)

#### Updating a Smart Field

{% tabs %}
{% tab title="SQL" %}
By default, your Smart Field is considered as read-only. If you want to update a Smart Field, you just need to write the logic to “unzip” the data. **Note that the `set` method should always return the object it’s working on**. In the example hereunder, the `customer` object is returned including only the modified data.

{% code title="/forest/customers.js" %}

```javascript
const { collection } = require('forest-express-sequelize');

collection('customers', {
  fields: [
    {
      field: 'fullname',
      type: 'String',
      get: (customer) => {
        return customer.firstname + ' ' + customer.lastname;
      },
      set: (customer, fullname) => {
        let names = fullname.split(' ');
        customer.firstname = names[0];
        customer.lastname = names[1];

        // Don't forget to return the customer.
        return customer;
      },
    },
  ],
});
```

{% endcode %}

Working with the actual record can be done this way:

{% code title="/forest/customers.js" %}

```javascript
const { collection, ResourceGetter } = require('forest-express-sequelize');
const { customers } = require('../models');

collection('customers', {
  fields: [
    {
      field: 'fullname',
      type: 'String',
      get: (customer) => {
        return customer.firstname + ' ' + customer.lastname;
      },
      set: async (customer, fullname) => {
        const customerBeforeUpdate = await customers.findOne({
          where: { id: customer.id },
        });

        const names = fullname.split(' ');
        customer.firstname = `${names[0]} ${customerBeforeUpdate.pseudo}`;
        return customer;
      },
    },
  ],
});
```

{% endcode %}

{% hint style="info" %}
For security reasons, the `fullname` Smart field will remain **read-only**, even after you implement the `set` method. To edit it, disable read-only mode in the field settings.
{% endhint %}
{% endtab %}

{% tab title="Mongodb" %}
By default, your Smart Field is considered as read-only. If you want to update a Smart Field, you just need to write the logic to “unzip” the data. **Note that the `set` method should always return the object it’s working on**. In the example hereunder, the `customer` record is returned.

{% code title="/forest/customers.js" %}

```javascript
const { collection } = require('forest-express-mongoose');

collection('customers', {
  fields: [
    {
      field: 'fullname',
      type: 'String',
      get: (customer) => {
        return customer.firstname + ' ' + customer.lastname;
      },
      set: (customer, fullname) => {
        let names = fullname.split(' ');
        customer.firstname = names[0];
        customer.lastname = names[1];

        // Don't forget to return the customer.
        return customer;
      },
    },
  ],
});
```

{% endcode %}

Working with the actual record can be done this way:

{% code title="/forest/customers.js" %}

```javascript
const { collection, ResourceGetter } = require('forest-express-mongoose');
const { customers } = require('../models');

collection('customers', {
  fields: [
    {
      field: 'fullname',
      type: 'String',
      get: (customer) => {
        return customer.firstname + ' ' + customer.lastname;
      },
      set: async (customer, fullname) => {
        const customerBeforeUpdate = await customers.findById(customer.id);

        const names = fullname.split(' ');
        customer.firstname = `${names[0]} ${customerBeforeUpdate.pseudo}`;
        return customer;
      },
    },
  ],
});
```

{% endcode %}

{% hint style="info" %}
For security reasons, the `fullname` Smart field will remain **read-only**, even after you implement the `set` method. To edit it, disable read-only mode in the field settings.
{% endhint %}
{% endtab %}

{% tab title="Rails" %}
By default, your Smart Field is considered as read-only. If you want to update a Smart Field, you just need to write the logic to “unzip” the data. **Note that the set method should always return the object it’s working on**. In the example hereunder, the `user_params` is returned is returned including only the modified data.

{% code title="/lib/forest\_liana/collections/customer.rb" %}

```ruby
class Forest::Customer
  include ForestLiana::Collection

  collection :Customer

  set_fullname = lambda do |user_params, fullname|
    fullname = fullname.split
    user_params[:firstname] = fullname.first
    user_params[:lastname] = fullname.last

    # Returns a hash of the updated values you want to persist.
    user_params
  end

  field :fullname, type: 'String', set: set_fullname do
    "#{object.firstname} #{object.lastname}"
  end
end
```

{% endcode %}

{% hint style="info" %}
For security reasons, the `fullname` Smart field will remain **read-only**, even after you implement the `set` method. To edit it, disable read-only mode in the field settings.
{% endhint %}
{% endtab %}

{% tab title="Django" %}
By default, your Smart Field is considered as read-only. If you want to update a Smart Field, you just need to write the logic to “unzip” the data. **Note that the `set` method should always return the object it’s working on**. In the example hereunder, the `customer` object is returned including only the modified data.

{% code title="app/forest/customer.py" %}

```python
from django_forest.utils.collection import Collection
from app.models import Customer


class CustomerForest(Collection):
    def load(self):
        self.fields = [
            {
                'field': 'fullname',
                'type': 'String',
                'get': self.get_fullname,
                'set': self.set_fullname
            },
        ]

    def get_fullname(self, obj):
        return f'{obj.firstname} {obj.lastname}'

    def set_fullname(self, obj, value):
        firstname, lastname = value.split()
        obj.firstname = firstname
        obj.lastname = lastname
        return obj


Collection.register(CustomerForest, Customer)
```

{% endcode %}
{% endtab %}

{% tab title="Laravel" %}
{% code title="app/Models/Customer.php" %}

```php
<?php

namespace App\Models;

use ForestAdmin\LaravelForestAdmin\Services\Concerns\ForestCollection;
use ForestAdmin\LaravelForestAdmin\Services\SmartFeatures\SmartField;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

/**
 * Class Customer
 */
class Customer extends Model
{
    use HasFactory, ForestCollection;

    /**
     * @return SmartField
     */
    public function fullname(): SmartField
    {
        return $this->smartField(['type' => 'String'])
            ->get(fn() => $this->firstname . ' ' . $this->lastname)
            ->set(
                function ($value) {
                    [$firstname, $lastname] = explode(' ', $value);
                    $this->firstname = $firstname;
                    $this->lastname = $lastname;

                    return $this;
                }
            );
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

![](https://2014605362-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fx9GURwd0RAwi8uWZgH5v%2Fuploads%2Fgit-blob-6fe190e5c03d2536d0f685eb0601b81428fc1bab%2Fscreenshot%202019-07-01%20at%2012.23.14.png?alt=media)

#### Searching, Sorting and Filtering on a Smart Field

To perform a search on a Smart Field, you also need to write the logic to “unzip” the data, then the search query which is specific to your zipping. In the example hereunder, the `firstname` and `lastname` are searched separately after having been unzipped.

{% tabs %}
{% tab title="SQL" %}
{% code title="/forest/customers.js" %}

```javascript
const { collection } = require('forest-express-sequelize');
const models = require('../models/');
const _ = require('lodash');
const Op = models.objectMapping.Op;

collection('customers', {
  fields: [
    {
      field: 'fullname',
      type: 'String',
      get: (customer) => {
        return customer.firstname + ' ' + customer.lastname;
      },
      search: function (query, search) {
        let split = search.split(' ');

        var searchCondition = {
          [Op.and]: [
            { firstname: { [Op.like]: `%${split[0]}%` } },
            { lastname: { [Op.like]: `%${split[1]}%` } },
          ],
        };

        query.where[Op.and][0][Op.or].push(searchCondition);

        return query;
      },
    },
  ],
});
```

{% endcode %}

{% hint style="info" %}
For **case insensitive** search using PostgreSQL database use `iLike` operator. See [Sequelize operators documentation](https://sequelize.org/docs/v6/core-concepts/model-querying-basics/#operators).
{% endhint %}
{% endtab %}

{% tab title="Mongodb" %}
{% code title="/forest/customers.js" %}

```javascript
const { collection } = require('forest-express-mongoose');
const models = require('../models/');
const _ = require('lodash');

collection('customers', {
  fields: [{
    field: 'fullname',
    type: 'String',
    get: (customer) => {
      return customer.firstname + ' ' + customer.lastname;
    },
    search(search) {
      let names = search.split(' ');
    ​
      return {
        firstname: names[0],
        lastname: names[1]
      };
    }
  }]
});
```

{% endcode %}
{% endtab %}

{% tab title="Rails" %}
{% code title="/lib/forest\_liana/collections/customer.rb" %}

```ruby
class Forest::Customer
  include ForestLiana::Collection
​
  collection :Customer
​
  search_fullname = lambda do |query, search|
    firstname, lastname = search.split
​
    # Injects your new filter into the WHERE clause.
    query.where_clause.send(:predicates)[0] << " OR (firstname = '#{firstname}' AND lastname = '#{lastname}')"
​
    query
  end
​
  field :fullname, type: 'String', set: set_fullname, search: search_fullname do
    "#{object.firstname} #{object.lastname}"
  end
end
```

{% endcode %}
{% endtab %}

{% tab title="Django" %}
{% code title="app/forest/customer.py" %}

```python
from django.db.models import Q

from django_forest.utils.collection import Collection
from app.models import Customer


class CustomerForest(Collection):
    def load(self):
        self.fields = [
            {
                'field': 'fullname',
                'type': 'String',
                'get': self.get_fullname,
                'search': self.search_fullname
            },
        ]

    def get_fullname(self, obj):
        return f'{obj.firstname} {obj.lastname}'

    def search_fullname(self, search):
        firstname, lastname = value.split()
        return Q(Q(firstname=firstname) & Q(lastname=lastname))


Collection.register(CustomerForest, Customer)
```

{% endcode %}
{% endtab %}

{% tab title="Laravel" %}
{% code title="app/Models/Customer.php" %}

```php
<?php

namespace App\Models;

use ForestAdmin\LaravelForestAdmin\Services\Concerns\ForestCollection;
use ForestAdmin\LaravelForestAdmin\Services\SmartFeatures\SmartAction;
use ForestAdmin\LaravelForestAdmin\Services\SmartFeatures\SmartField;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

/**
 * Class Customer
 */
class Customer extends Model
{
    use HasFactory, ForestCollection;

    /**
     * @return SmartField
     */
    public function fullname(): SmartField
    {
        return $this->smartField(['type' => 'String'])
            ->get(fn() => $this->firstname . ' ' . $this->lastname)
            ->set(
                function ($value) {
                    [$firstname, $lastname] = explode(' ', $value);
                    $this->firstname = $firstname;
                    $this->lastname = $lastname;

                    return $this;
                }
            )
            ->search(
                function (Builder $query, $value) {
                    [$firstname, $lastname] = explode(' ', $value);
                    return $query->orWhere(
                        fn($query) => $query->where('firstname', $firstname)
                            ->where('lastname', $lastname)
                    );
                }
            );
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

![](https://2014605362-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fx9GURwd0RAwi8uWZgH5v%2Fuploads%2Fgit-blob-d23a9f5009140a355e3b9a9071d7e95e7b975710%2Fscreenshot%202019-07-01%20at%2012.25.58.png?alt=media)

**Filtering**

{% hint style="warning" %}
This feature is only available on agents version **6.7+** (version **6.2+** for Rails).
{% endhint %}

To perform a filter on a Smart Field, you need to write the filter query logic, which is specific to your use case.

In the example hereunder, the `fullname` is filtered by checking conditions on the `firstname` and `lastname` depending on the filter operator selected.

{% tabs %}
{% tab title="SQL" %}
{% code title="/forest/customers.js" %}

```javascript
const { collection } = require('forest-express-sequelize');
const models = require('../models/');

const { Op } = models.Sequelize;

collection('customers', {
  fields: [
    {
      field: 'fullname',
      isFilterable: true,
      type: 'String',
      get: (customer) => {
        return customer.firstname + ' ' + customer.lastname;
      },
      filter({ condition, where }) {
        const firstWord = !!condition.value && condition.value.split(' ')[0];
        const secondWord = !!condition.value && condition.value.split(' ')[1];

        switch (condition.operator) {
          case 'equal':
            return {
              [Op.and]: [
                { firstname: firstWord },
                { lastname: secondWord || '' },
              ],
            };
          case 'ends_with':
            if (!secondWord) {
              return {
                lastName: { [Op.like]: `%${firstWord}` },
              };
            }
            return {
              [Op.and]: [
                { firstName: { [Op.like]: `%${firstWord}` } },
                { lastName: secondWord },
              ],
            };

          // ... And so on with the other operators not_equal, starts_with, etc.

          default:
            return null;
        }
      },
    },
  ],
  segments: [],
});
```

{% endcode %}
{% endtab %}

{% tab title="MongoDB" %}
{% code title="/forest/customers.js" %}

```javascript
const { collection } = require('forest-express-mongoose');
const models = require('../models');

collection('customer', {
  actions: [],
  fields: [
    {
      field: 'fullName',
      type: 'String',
      isFilterable: true,
      get: (customer) => {
        return customer.firstname + ' ' + customer.lastname;
      },
      filter({ condition, where }) {
        const firstWord = !!condition.value && condition.value.split(' ')[0];
        const secondWord = !!condition.value && condition.value.split(' ')[1];

        switch (condition.operator) {
          case 'equal':
            return {
              $and: [{ firstname: firstWord }, { lastname: secondWord || '' }],
            };
          case 'ends_with':
            if (!secondWord) {
              return {
                lastname: { $regex: `.*${firstWord}` },
              };
            }
            return {
              $and: [
                { firstname: { $regex: `.*${firstWord}` } },
                { lastname: secondWord },
              ],
            };

          // ... And so on with the other operators not_equal, starts_with, etc.

          default:
            return null;
        }
      },
    },
  ],
  segments: [],
});
```

{% endcode %}
{% endtab %}

{% tab title="Rails" %}
{% code title="/lib/forest\_liana/customer.rb" %}

```ruby
class Forest::Customer
  include ForestLiana::Collection

  collection :Customer

  filter_fullname = lambda do |condition, where|
    first_word = condition['value'] && condition['value'].split[0]
    second_word = condition['value'] && condition['value'].split[1]

    case condition['operator']
    when 'equal'
      "firstname = '#{first_word}' AND lastname = '#{second_word}'"
    when 'ends_with'
      if second_word.nil?
        "lastname LIKE '%#{first_word}'"
      else
        "firstname LIKE '%#{first_word}' AND lastname = '#{second_word}'"
      end
    # ... And so on with the other operators not_equal, starts_with, etc.
    end
  end

  field :fullname, type: 'String', is_read_only: false, is_required: true, is_filterable: true, filter: filter_fullname do
    "#{object.firstname} #{object.lastname}"
  end
end
```

{% endcode %}
{% endtab %}

{% tab title="Django" %}
{% code title="app/forest/customer.py" %}

```python
from django.db.models import Q

from django_forest.utils.collection import Collection
from django_forest.resources.utils.queryset.filters.utils import OPERATORS
from app.models import Customer


class CustomerForest(Collection):
    def load(self):
        self.fields = [
            {
                'field': 'fullname',
                'type': 'String',
                'get': self.get_fullname,
                'filter': self.filter_fullname
            },
        ]

    def get_fullname(self, obj):
        return f'{obj.firstname} {obj.lastname}'

    def filter_fullname(self, operator, value):
        firstname, lastname = value.split()
        firstname_kwargs = {f'firstname{OPERATORS[operator]}': firstname}
        firstname_filter = Q(**firstname_kwargs)
        flastname_kwargs = {f'lastname{OPERATORS[operator]}': lastname}
        lastname_filter = Q(**lastname_kwargs)

        is_negated = operator.startswith('not')
        if is_negated:
            return ~Q(firstname_filter & lastname_filter)
        return Q(firstname_filter & lastname_filter)


Collection.register(CustomerForest, Customer)
```

{% endcode %}
{% endtab %}

{% tab title="Laravel" %}
{% code title="app/Models/Customer.php" %}

```php
<?php

namespace App\Models;

use ForestAdmin\LaravelForestAdmin\Services\Concerns\ForestCollection;
use ForestAdmin\LaravelForestAdmin\Services\SmartFeatures\SmartAction;
use ForestAdmin\LaravelForestAdmin\Services\SmartFeatures\SmartField;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

/**
 * Class Customer
 */
class Customer extends Model
{
    use HasFactory, ForestCollection;

    /**
     * @return SmartField
     */
    public function fullname(): SmartField
    {
        return $this->smartField(['type' => 'String'])
            ->get(fn() => $this->firstname . ' ' . $this->lastname)
            ->set(
                function ($value) {
                    [$firstname, $lastname] = explode(' ', $value);
                    $this->firstname = $firstname;
                    $this->lastname = $lastname;

                    return $this;
                }
            )
            ->filter(
                function (Builder $query, $value, string $operator, string $aggregator) {
                    $data = explode(' ', $value);
                    switch ($operator) {
                        case 'equal':
                            $query->where(
                                fn($query) => $query->where('firstname', $data[0])
                                    ->where('lastname', $data[1]),
                                null,
                                null,
                                $aggregator
                            );
                            break;
                        case 'ends_with':
                            if ($data[1] === null) {
                                $query->where(
                                    fn($query) => $query->whereRaw("lastname LIKE ?", ['%' . $data[0] . '%']),
                                    null,
                                    null,
                                    $aggregator
                                );
                            } else {
                                $query->where(
                                    fn($query) => $query->whereRaw("firstname LIKE ?", ['%' . $value . '%'])
                                        ->whereRaw("lastname LIKE ?", ['%' . $value . '%']),
                                    null,
                                    null,
                                    $aggregator
                                );
                            }
                            break;
                       //... And so on with the other operators not_equal, starts_with, etc.
                        default:
                            throw new ForestException(
                                "Unsupported operator: $operator"
                            );
                    }

                    return $query;
                }
            );
    }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

![](https://2014605362-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fx9GURwd0RAwi8uWZgH5v%2Fuploads%2Fgit-blob-bac7473cd222c050a170218b9f58bd454936301e%2Fscreenshot%202021-04-02%20at%2016.09.16.png?alt=media)

{% hint style="info" %}
Make sure you set the option `isFilterable: true` in the field definition of your code. Then, you will be able to toggle the "Filtering enabled" option in the browser, in your **Fields Settings**.
{% endhint %}

![](https://2014605362-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LR7SWfEwsNtj_ZiSkSA%2F-MSCo6L-o0qZWgm06VA-%2F-MSCogp6Mt6UlyYbDp26%2Fimage.png?alt=media\&token=bc974076-12f1-4bb7-a59a-6129194085b0)

**Sorting**

{% hint style="danger" %}
**Sorting** on a Smart Field is not *natively supported* in Forest Admin. However you can check out those guides:

* [Sort by Smart field](https://docs.forestadmin.com/documentation/reference-guide/smart-fields/smart-field-examples/sort-by-smart-field)
* [Sort by Smart field that includes value from a belongsTo relationship](https://docs.forestadmin.com/documentation/reference-guide/smart-fields/smart-field-examples/sort-by-smart-field-that-includes-value-from-a-belongsto-relationship)
  {% endhint %}

#### Available Field Options

Here are the list of available options to customize your Smart Field:

| Name        | Type             | Description                                                                                                                                                   |
| ----------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| field       | string           | The name of your Smart Field.                                                                                                                                 |
| type        | string           | Type of your field. Can be `Boolean`, `Date`, `Json`,`Dateonly`, `Enum`, `File`, `Number, ['String']` or `String` .                                           |
| enums       | array of strings | (optional) Required only for the `Enum` type. This is where you list all the possible values for your input field.                                            |
| description | string           | (optional) Add a description to your field.                                                                                                                   |
| reference   | string           | (optional) Configure the Smart Field as a [Smart Relationship](https://docs.forestadmin.com/documentation/models/relationships#what-is-a-smart-relationship). |
| isReadOnly  | boolean          | (optional) If `true`, the Smart Field won’t be editable in the browser. Default is `true` if there’s no `set` option declared.                                |
| isRequired  | boolean          | (optional) If true, your Smart Field will be set as required in the browser. Default is false.                                                                |

{% hint style="info" %}
You can define a widget for a smart field from the [settings of your collection](https://docs.forestadmin.com/user-guide/collections/customize-your-fields).
{% endhint %}

#### Building Performant Smart Fields

To optimize your smart field performance, we recommend using a mechanism of batching and caching data requests.

Implement them using the DataLoader which is a generic utility to be used as part of your application's data fetching layer to provide a simplified and consistent API over various remote data sources.

**Smart field declaration**

{% tabs %}
{% tab title="SQL" %}
{% code title="forest/post.js" %}

```javascript
const DataLoader = require('dataloader');

const authorLoader = new DataLoader(async (authorKeys) => {
  const authors = await users.findAll({
    where: { id: authorKeys },
  });

  const authorsById = new Map(authors.map((user) => [user.id, user]));

  return authorKeys.map((authorKey) => authorsById.get(authorKey));
});

collection('posts', {
  actions: [],
  fields: [
    {
      field: 'author_name',
      type: 'String',
      get: async (record) => {
        const author = await authorLoader.load(record.authorKey);

        return author.name;
      },
    },
  ],
  segments: [],
});
```

{% endcode %}
{% endtab %}

{% tab title="MongoDB" %}
{% code title="forest/post.js" %}

```javascript
const { collection } = require('forest-express-mongoose');
const { Address } = require('../models');
const Dataloader = require('dataloader');

const addressLoader = new Dataloader((customerIds) => {
  const addresses = await models.addresses.find({
    customer_id: {
      $in: customerIds
    }
  });

  const addressesByCustomerId = new Map(addresses.map(
    address => [address.customer_id, address]
  ));

  return customerIds.map(customerId => addressesByCustomerId.get(customerId));
})

collection('customers', {
  fields: [{
    field: 'full_address',
    type: 'String',
    get: (customer) => {
      return addressLoader.load(customer.id)
        .then((address) => {
          return address.address_line_1 + '\n' +
            address.address_line_2 + '\n' +
            address.address_city + ' ' + address.country;
        });
    }
  }]
});
```

{% endcode %}
{% endtab %}
{% endtabs %}
