Create and manage Smart Fields

What is a Smart Field?

A field that displays a computed value in your collection.

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

Lumber
Rails
Express/Sequelize
Express/Mongoose

On our Live Demo, the very simple Smart Field fullname is available on the customers collection.

/forest/customers.js
const Liana = require('forest-express-sequelize');
Liana.collection('customers', {
fields: [{
field: 'fullname',
type: 'String',
get: (customer) => {
return customer.firstname + ' ' + customer.lastname;
}
}]
});

Very often, the business logic behind the Smart Field is more complex and must be asynchronous. To do that, you can simply return a Promise.

In the following example, we illustrate an async logic through our full_address Smart Field also defined in the customers collection.

/forest/customers.js
const Liana = require('forest-express-sequelize');
const models = require('../models/');
Liana.collection('customers', {
fields: [{
field: 'full_address',
type: 'String',
get: (customer) => {
return models.addresses
.findOne({ where: { customer_id: customer.id } })
.then((address) => {
return address.address_line_1 + '\n' +
address.address_line_2 + '\n' +
address.address_city + ' ' + address.country;
});
}
}]
});

On our Live Demo, the very simple Smart Field fullname is available on the Customer collection.

/lib/forest_liana/collections/customer.rb
class Forest::Customer
include ForestLiana::Collection
collection :Customer
field :fullname, type: 'String' do
"#{object.firstname} #{object.lastname}"
end
end

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.

/lib/forest_liana/collections/customer.rb
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

On our Live Demo, the very simple Smart Field fullname is available on the customers collection.

/forest/customers.js
const Liana = require('forest-express-sequelize');
Liana.collection('customers', {
fields: [{
field: 'fullname',
type: 'String',
get: (customer) => {
return customer.firstname + ' ' + customer.lastname;
}
}]
});

Very often, the business logic behind the Smart Field is more complex and must be asynchronous. To do that, you can simply return a Promise.

In the following example, we illustrate an async logic through our full_address Smart Field also defined in the customers collection.

/forest/customers.js
const Liana = require('forest-express-sequelize');
const models = require('../models/');
Liana.collection('customers', {
fields: [{
field: 'full_address',
type: 'String',
get: (customer) => {
return models.addresses
.findOne({ where: { customer_id: customer.id } })
.then((address) => {
return address.address_line_1 + '\n' +
address.address_line_2 + '\n' +
address.address_city + ' ' + address.country;
});
}
}]
});

On our Live Demo, the very simple Smart Field fullname is available on the customers collection.

/forest/customers.js
const Liana = require('forest-express-mongoose');
Liana.collection('customers', {
fields: [{
field: 'fullname',
type: 'String',
get: (customer) => {
return customer.firstname + ' ' + customer.lastname;
}
}]
});

Very often, the business logic behind the Smart Field is more complex and must be asynchronous. To do that, you can simply return a Promise.

In the following example, we illustrate an async logic through our full_address Smart Field also defined in the customers collection.

/forest/customers.js
const Liana = require('forest-express-mongoose');
const Address = require('../models/addresses');
Liana.collection('customers', {
fields: [{
field: 'full_address',
type: 'String',
get: (customer) => {
return Address
.findOne({ customer_id: customer.id })
.then((address) => {
return address.address_line_1 + '\n' +
address.address_line_2 + '\n' +
address.address_city + ' ' + address.country;
});
}
}]
});

The collection name must be the same as the model name.

Updating a Smart Field

Lumber
Rails
Express/Sequelize
Express/Mongoose

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.

/forest/customers.js
const Liana = require('forest-express-sequelize');
Liana.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;
}
}]
});

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.

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.

/lib/forest_liana/collections/customer.rb
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

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.

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.

/forest/customers.js
const Liana = require('forest-express-sequelize');
Liana.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;
}
}]
});

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.

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.

/forest/customers.js
const Liana = require('forest-express-mongoose');
Liana.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;
}
}]
});

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.

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.

Lumber
Rails
Express/Sequelize
Express/Mongoose

If you are working with Async, you can also return a Promise.

/forest/customers.js
const Liana = require('forest-express-sequelize');
const models = require('../models/');
const _ = require('lodash');
const Op = models.Sequelize.Op;
Liana.collection('customers', {
fields: [{
field: 'fullname',
type: 'String',
get: (customer) => {
return customer.firstname + ' ' + customer.lastname;
},
search: function (query, search) {
let s = models.sequelize;
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;
}
}]
});

For case insensitive search using PostgreSQL database use ilike operator. See Sequelize operators documentation.

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.

/lib/forest_liana/collections/customer.rb
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

If you are working with Async, you can also return a Promise.

/forest/customers.js
const Liana = require('forest-express-sequelize');
const models = require('../models/');
const _ = require('lodash');
const Op = models.Sequelize.Op;
Liana.collection('customers', {
fields: [{
field: 'fullname',
type: 'String',
get: (customer) => {
return customer.firstname + ' ' + customer.lastname;
},
search: function (query, search) {
let s = models.sequelize;
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;
}
}]
});

For case insensitive search using PostgreSQL database use ilike operator. See Sequelize operators documentation.

If you are working with Async, you can also return a Promise.

/forest/customers.js
const Liana = require('forest-express-mongoose');
const models = require('../models/');
const _ = require('lodash');
Liana.collection('customers', {
fields: [{
field: 'fullname',
type: 'String',
get: (customer) => {
return customer.firstname + ' ' + customer.lastname;
},
search: function (query, search) {
let names = search.split(' ');
query._conditions.$or.push({
firstname: names[0],
lastname: names[1]
});
return query;
}
}]
});

Sorting on Smart Field is not supported in Forest Admin. Indeed, being able to sort on Smart Field would mean that we have to compute the Smart Field values for all the records of your collection, and then sort the records on this value.

While this is something we could implement, it would not be something scalable. Imagine if your collection has millions of records.

If this sorting is really important for your operations, you should consider the creation of a dedicated column in the database.

Filtering on a Smart Field is possible but not recommended for the same performance reasons than sorting.

Would you decide to enable it anyway, here's the option to use: isFilterable: true You will then receive the query parameters with the filters to override the records list using smart routes.

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, 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.

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.

You can define a widget for a smart field from the settings of your collection.