Woodshop for old agent generation
Try the new agent generation
  • What is woodshop
  • How to's
    • Smart Relationship
      • GetIdsFromRequest
    • Smart views
      • Display a calendar view
      • Create a custom tinder-like validation view
      • Create a custom moderation view
      • Create a dynamic calendar view for an event-booking use case
    • Configure environment variables
      • NodeJS/Express projects
    • Elasticsearch Integration
      • Interact with your Elasticsearch data
      • Elasticsearch service/utils
      • Another example
    • Zendesk Integration
      • Authentication, Filtering & Sorting
      • Display Zendesk tickets
      • Display Zendesk users
      • View tickets related to a user
      • Bonus: Direct link to Zendesk + change priority of a ticket
    • Dwolla integration
      • Display Dwolla customers
      • Display Dwolla funding sources
      • Display Dwolla transfers
      • Link users and Dwolla customers
      • Dwolla service
    • Make filters case insensitive
    • Use Azure Table Storage
    • Create multiple line charts
    • Create Charts with AWS Redshift
    • View soft-deleted records
    • Send Smart Action notifications to Slack
    • Authenticate a Forest Admin API against an OAuth protected API Backend
    • Translate your project into TypeScript
      • V8
        • Migrate Mongoose files
        • Migrate Sequelize files
      • v7
        • Migrate Mongoose files
        • Migrate Sequelize files
      • v6
    • Geocode an address with Algolia
    • Display/edit a nested document
    • Send an SMS with Zapier
    • Hash a password with bcrypt
    • Display a customized response
    • Search on a smart field with two joints
    • Override the count route
    • Make a field readOnly with Sequelize
    • Hubspot integration
      • Create a Hubspot company
      • Display Hubspot companies
    • Impersonate a user
    • Import data from a CSV file
    • Import data from a JSON file
    • Load smart fields using hook
    • Pre-fill a form with data from a relationship
    • Re-use a smart field logic
    • Link to record info in a smart view
    • Display data in html format
    • Upload files to AWS S3
    • Display AWS S3 files from signed URLs
    • Prevent record update
    • Display, search and update attributes from a JSON field
    • Add many existing records at the same time (hasMany-belongsTo relationship)
    • Track users’ logs with morgan
    • Search on relationship fields by default
    • Export related data as CSV
    • Run automated tests
  • Forest Admin Documentation
Powered by GitBook
On this page
  • Requirements
  • How it works
  • Directory: /forest
  • Directory: /routes

Was this helpful?

  1. How to's

Add many existing records at the same time (hasMany-belongsTo relationship)

This example shows how to associate multiple existing records at once to a record using a simple smart action.

PreviousDisplay, search and update attributes from a JSON fieldNextTrack users’ logs with morgan

Last updated 2 years ago

Was this helpful?

Requirements

  • 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 fields
          const selection = fields.find((field) => field.field === 'selection');
          const search = fields.find((field) => field.field === 'search');


          if(!!search.value){
            // Retrieve the company name by querying the DB
            const { name: searchValue } = (await companies.findByPk(search.value)) || {};
  
            // Adding company names when searching matches
            if (searchValue) {
              const allAddedValues = [...(selection.previousValue || []), searchValue]; // ...() spread the array
              
              // Unique array values using a set
              selection.value = [...new Set(allAddedValues)];
              // Allow user to interact with selection field
              selection.isReadOnly = false;
              
              // Reset search value
              search.value = '';
            }
          }

          return fields;
        },
        onSelectionChange: async ({ fields }) => {
          // This hooks is needed to allow company removal from selection
          const selectionField = fields.find((field) => field.field === "selection");

          // Enable or disable user interactions
          if (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.

const express = require('express');
const { PermissionMiddlewareCreator } = require('forest-express-sequelize');

const { companies, objectMapping: { Op } } = require('../models');

const router = express.Router();
const permissionMiddlewareCreator = new PermissionMiddlewareCreator('organizations');

// Associate companies smart action route
router.post('/actions/associate-companies', permissionMiddlewareCreator.smartAction(), async (req, res) => {
  const { body: { data: { attributes } } } = req;
  const companyNames = attributes.values['selection'];

  // Retrieve all companies ids using the company names sent by the action form
  const companyIds = (await companies.findAll({
    where: { name: { [Op.in]: companyNames } },
    attributes: ['id']
  })).map((company) => company.id);

  // Retrieve organization id from the request
  const organizationId = attributes.ids[0];

  // Update the companies to add the belongsTo association
  await companies.update({ organizationId: organizationId }, { where: { id: companyIds }});

  // Send success toasted and refresh the related data in the Summary
  res.send({
    success: 'Companies have been added!',
    refresh: { relationships: ['companies'] },
  });
});

module.exports = router;