An admin backend running on forest-express-sequelize
Relationship One-To-Many between two collections (in this example an organisation hasMany companies <-> a company belongsTo an organisation)
How it works
Directory: /forest
Create a new smart action in the forest file of the collection with the hasMany relationship (organizations in this example).
This smart action will be usable on a single record (type: 'single'). We will create two fields in the smart action form, one will be used for the search on the referenced collection and the second will be used to see the selection made by the operator.
const { collection } =require('forest-express-sequelize');const { companies } =require('../models');collection('organizations', { actions: [{ name:'Associate companies', type:'single', fields: [ { field:'search', type:'String', reference:'companies', isRequired:false, hook:'onSearchChange', }, { field:'selection', type: ['String'], isReadOnly:true, isRequired:true, hook:'onSelectionChange', }, ], hooks: { change: {onSearchChange:async ({ fields }) => {// Retrieve fieldsconstselection=fields.find((field) =>field.field ==='selection');constsearch=fields.find((field) =>field.field ==='search');if(!!search.value){// Retrieve the company name by querying the DBconst { name: searchValue } = (awaitcompanies.findByPk(search.value)) || {};// Adding company names when searching matchesif (searchValue) {constallAddedValues= [...(selection.previousValue || []), searchValue]; // ...() spread the array// Unique array values using a setselection.value = [...newSet(allAddedValues)];// Allow user to interact with selection fieldselection.isReadOnly =false;// Reset search valuesearch.value =''; } }return fields; },onSelectionChange:async ({ fields }) => {// This hooks is needed to allow company removal from selectionconstselectionField=fields.find((field) =>field.field ==="selection");// Enable or disable user interactionsif (selectionField.value?.length>0) {selectionField.isReadOnly =false; } else {selectionField.isReadOnly =true; }return fields; }, }, }, }], fields: [], segments: [],});
Directory: /routes
When the user validates the action, this route is called. We will use the selection to retrieve all companies' ids and then updates all companies organizationId field to create the associations.
In addition, once the smart action has been successfully runned, it refreshs the relationship to properly display newly added associations.
constexpress=require('express');const { PermissionMiddlewareCreator } =require('forest-express-sequelize');const { companies, objectMapping: { Op } } =require('../models');constrouter=express.Router();constpermissionMiddlewareCreator=newPermissionMiddlewareCreator('organizations');// Associate companies smart action routerouter.post('/actions/associate-companies',permissionMiddlewareCreator.smartAction(),async (req, res) => {const { body: { data: { attributes } } } = req;constcompanyNames=attributes.values['selection'];// Retrieve all companies ids using the company names sent by the action formconstcompanyIds= (awaitcompanies.findAll({ where: { name: { [Op.in]: companyNames } }, attributes: ['id'] })).map((company) =>company.id);// Retrieve organization id from the requestconstorganizationId=attributes.ids[0];// Update the companies to add the belongsTo associationawaitcompanies.update({ organizationId: organizationId }, { where: { id: companyIds }});// Send success toasted and refresh the related data in the Summaryres.send({ success:'Companies have been added!', refresh: { relationships: ['companies'] }, });});module.exports= router;