Create a scope more than one level away based on a Smart field
Context: As a user I want to create a scope on a table that does not have the tag column in the table.
As a user I want to create a scope on related tables more than one level away
Example:
The objective is to implement scopes on all tables, filtering oncompanies to make sure that companies can only see their own data. In this example, companies has many departments, departments has many users. The company id is not in users table but in the departments table. We want to scope users according to a company value.

Step 1: Create a smart field and the filter for the users table

SQL
Rails
Laravel
forest/users.js
1
const { collection } = require('forest-express-sequelize');
2
const { users, departments, companies } = require('../models');
3
const models = require('../models');
4
const { Op } = models.objectMapping;
5
6
7
collection('users', {
8
actions: [],
9
fields: [ {
10
field: 'company name',
11
isFilterable: true,
12
type: 'String',
13
get: async user => {
14
//We are looking for the company name of the user (user belongs to a department that belongs to a company)
15
const company = await companies.findOne({
16
attributes: ['name'],
17
include: {
18
required: true,
19
model: departments,
20
where: { id: user.departmentId },
21
}
22
});
23
24
return company.name
25
},
26
filter: async ({ condition: { value, operator } }) => {
27
28
switch (operator) {
29
case 'equal':
30
//We are looking for all the users ids that have a company name equal to the condition value
31
const queryToFindUsers = await users
32
.findAll({
33
attributes: ['id'],
34
include: [{
35
required: true,
36
model: departments,
37
include: [{
38
required: true,
39
model: companies,
40
where: { name: { [Op.eq]: value } },
41
}]
42
}],
43
});
44
//We map this array of objects to retrieve the user ids
45
const userIds = queryToFindUsers.map(user => user.id);
46
return { id: { [Op.in]: userIds } };
47
default:
48
return null;
49
}
50
},
51
},],
52
segments: [],
53
});
Copied!
lib/forest_liana/collections/user.rb
1
class Forest::Customer
2
include ForestLiana::Collection
3
4
collection :User
5
6
filter_company = lambda do |condition, where|
7
company_value = condition['value']
8
case condition['operator']
9
when 'equal'
10
"users.id IN (SELECT users.id
11
FROM users
12
JOIN departments ON departments.id = users.department_id
13
JOIN companies ON companies.id = departments.company_id
14
WHERE companies.name = '#{company_value}')"
15
end
16
end
17
18
field :company, type: 'String', is_filterable: true, filter: filter_company do
19
company = User.find(object.id).department.company
20
"#{company.name}"
21
end
22
end
Copied!
app/Models/User.php
1
<?php
2
3
namespace App\Models;
4
5
use ForestAdmin\LaravelForestAdmin\Services\Concerns\ForestCollection;
6
use ForestAdmin\LaravelForestAdmin\Services\SmartFeatures\SmartField;
7
use Illuminate\Database\Eloquent\Builder;
8
use Illuminate\Database\Eloquent\Factories\HasFactory;
9
use Illuminate\Database\Eloquent\Model;
10
use Illuminate\Database\Eloquent\Relations\BelongsTo;
11
12
class User extends Model
13
{
14
use HasFactory;
15
use ForestCollection;
16
17
/**
18
* @return SmartField
19
*/
20
public function company(): SmartField
21
{
22
return $this->smartField(['type' => 'String'])
23
->get(function() {
24
$company = Company::whereHas('departments', fn($query) => $query->where('departments.id', $this->department->id))->first();
25
return $company->name;
26
})
27
->filter(
28
function (Builder $query, $value, string $operator, string $aggregator) {
29
switch ($operator) {
30
case 'equal':
31
$query
32
->whereIn('users.id', function ($q) use ($value, $aggregator) {
33
return $q
34
->select('users.id')
35
->from('companies')
36
->join('departments', 'departments.company_id', '=', 'companies.id')
37
->join('users', 'users.department_id', '=', 'departments.id')
38
->whereRaw("LOWER (companies.name) LIKE LOWER(?)", ['%' . $value . '%'], $aggregator);
39
});
40
break;
41
default:
42
throw new ForestException(
43
"Unsupported operator: $operator"
44
);
45
}
46
47
return $query;
48
}
49
);
50
}
51
52
public function department(): BelongsTo
53
{
54
return $this->belongsTo(Department::class);
55
}
56
}
Copied!

Step 2: Configure the scope in the UI

In project settings:
In the table users