Ruby Developer Guide
Other documentationsDemoCommunityGitHub
  • Forest Admin
  • Getting started
    • How it works
    • Quick start
      • Ruby on Rails
    • Create your agent
    • Troubleshooting
    • Migrating legacy agents
      • Pre-requisites
      • Recommendations
      • Migration steps
      • Code transformations
        • API Charts
        • Live Queries
        • Smart Charts
        • Route overrides
        • Smart Actions
        • Smart Fields
        • Smart Relationships
        • Smart Segments
  • Data Sources
    • Getting Started
      • Collection selection
      • Naming conflicts
      • Query interface and Native Queries
        • Fields and projections
        • Filters
        • Aggregations
    • Provided data sources
      • ActiveRecord
        • Polymorphic relationships
      • Mongoid
    • Write your own
      • Translation strategy
        • Structure declaration
        • Capabilities declaration
        • Read implementation
        • Write implementation
        • Intra-data source Relationships
      • Contribute
  • Agent customization
    • Getting Started
    • Actions
      • Scope and context
      • Result builder
      • Static Forms
      • Widgets in Forms
      • Dynamic Forms
      • Form layout customization
      • Related data invalidation
    • Charts
      • Value
      • Objective
      • Percentage
      • Distribution
      • Leaderboard
      • Time-based
    • Fields
      • Add fields
      • Move, rename and remove fields
      • Override binary field mode
      • Override writing behavior
      • Override filtering behavior
      • Override sorting behavior
      • Validation
    • Hooks
      • Collection hook
      • Collection override
    • Pagination
    • Plugins
      • Write your own
    • Relationships
      • To a single record
      • To multiple records
      • Computed foreign keys
      • Under the hood
    • Search
    • Segments
  • Frontend customization
    • Smart Charts
      • Create a table chart
      • Create a bar chart
      • Create a cohort chart
      • Create a density map
    • Smart Views
      • Create a Map view
      • Create a Calendar view
      • Create a Shipping view
      • Create a Gallery view
      • Create a custom tinder-like validation view
      • Create a custom moderation view
  • Deploying to production
    • Environments
      • Deploy on AWS
      • Deploy on Heroku
      • Deploy on GCP
      • Deploy on Ubuntu
    • Development workflow
    • Using branches
    • Deploying your changes
    • Forest Admin CLI commands
      • init
      • login
      • branch
      • switch
      • set-origin
      • push
      • environments:create
      • environments:reset
      • deploy
  • Under the hood
    • .forestadmin-schema.json
    • Data Model
      • Typing
      • Relationships
    • Security & Privacy
Powered by GitBook
On this page
  • Code cheatsheet
  • Steps
  • Step 1: Calling add_action for the appropriate collection
  • Step 2: Porting the form definition
  • Step 3: Porting the route to the new agent execute function
  • Step 4: Porting Smart Action hooks

Was this helpful?

  1. Getting started
  2. Migrating legacy agents
  3. Code transformations

Smart Actions

PreviousRoute overridesNextSmart Fields

Last updated 8 months ago

Was this helpful?

This is the official documentation of the agent_ruby Ruby agent.

In legacy agents declaring a Smart Action was a two-step process:

  • First, you had to declare by changing the parameters of the collection function in the appropriate /lib/forest_liana/collections/*.rb file.

  • Then, you had to implement the action by creating a route handler in the appropriate config/routes.rb file.

    In the new agent, the process is simplified to a single step.

You can find the full documentation of action customization .

Code cheatsheet

Legacy agent
New agent

type: 'single' type: 'bulk' type: 'global'

scope: 'Single' scope: 'Bulk' scope: 'Global'

download: true

generate_file: true

reference: 'otherCollection.id'

{ type: 'Collection', collection_name: 'otherCollection' }

enums: ['foo', 'bar']

{ type: 'Enum', enum_values: ['foo', 'bar'] }

Request object

context.get_record_ids()

Response object

return result_builder.success(...) return result_builder.error(...) ...

Steps

Step 1: Calling add_action for the appropriate collection

Start by calling the add_action function on the appropriate collection and passing the appropriate parameters.

Most notably, you will need to pass:

  • type should become scope

    • Note that the values are now capitalized (e.g. single becomes Single)

    • Legacy agents defaulted to 'bulk' if no type was specified. The new agent requires you to specify the scope.

  • download should become generate_file. This is still a boolean and the same value can be passed.

  • endpoint and httpMethod should be removed. The agent will now automatically handle the routing.

class Forest::Company
  include ForestLiana::Collection

  collection :Company

  action 'Mark as Live', type: 'bulk', download: false, endpoint: '/forest/actions/mark-as-live'
end
include ForestAdminDatasourceCustomizer::Decorators::Action::Types
include ForestAdminDatasourceCustomizer::Decorators::Action

module ForestAdminRails
  class CreateAgent
    def self.customize
      @create_agent.customize_collection('Company') do |collection|
        collection.add_action(
          'Mark as live',
          BaseAction.new(scope: ActionScope::SINGLE) do |context|
            # Implement your controller here.
          end
        )
      end
    end
  end
end

Step 2: Porting the form definition

Forms are now defined in the form property of the action.

You can simply copy the field's definition from the legacy agent to the new agent with the following differences:

  • fields should become form.

  • widget choice is no longer supported. A default widget will be used depending on the field type.

  • hook can be removed, those will be handled by the new agent automatically.

  • reference no longer exists. Use { type: "Collection", collection_name: '... } instead.

  • enums no longer exist. Use { type: "Enum", enum_values: ['...'] } instead.

class Forest::Company
  include ForestLiana::Collection

  collection :Company

  action 'Charge credit card', type: 'single', fields: [
    {
      field: 'amount',
      is_required: true,
      description: 'The amount (USD) to charge the credit card. Example: 42.50',
      type: 'Number',
    }
  ]
end
module ForestAdminRails
  class CreateAgent
    include ForestAdminDatasourceCustomizer::Decorators::Action::Types

    def self.customize
      @create_agent.customize_collection('Company') do |collection|
        collection.add_action(
          'Charge credit card',
          BaseAction.new(
            scope: ActionScope::SINGLE,
            form: [
              {
                label: 'amount',
                type: FieldType::NUMBER,
                description: 'The amount (USD) to charge the credit card. Example: 42.50',
                is_required: true,
              }
            ]
          ) do |context, result_builder|
            #...
          end
        )
      end
    end
  end
end

Step 3: Porting the route to the new agent execute function

In the legacy agent, users had to implement the action by creating a route handler in the appropriate config/routes.rb file.

This is no longer needed as the new agent provides a context object that contains all the information that is needed to implement the action.

When porting the route handler to the new agent, you will need to:

  • Move the body of the route handler to the execute function of the action.

  • Replace call with context.get_record_ids().

class Forest::CompaniesController < ForestLiana::SmartActionsController
  def mark_as_live
    company_id = ForestLiana::ResourcesGetter.get_ids_from_request(params, forest_user).first
    Company.update(company_id, status: 'live')

    head :no_content
  end
end
module ForestAdminRails
  class CreateAgent
    def self.customize
      include ForestAdminDatasourceCustomizer::Decorators::Action::Types

      @create_agent.customize_collection('Company') do |collection|
        collection.add_action(
          'Mark as live',
          BaseAction.new(scope: ActionScope::SINGLE) do |context, result_builder|
            company_id = context.get_record_id
            Company.update(company_id, status: 'live')

            result_builder.success(message: 'Company is now live!')
          end
        )
      end
    end
  end
end

Step 4: Porting Smart Action hooks

Load hooks and change hooks have been replaced on the new agent by the possibility to use callbacks in the form definition.

Here is an example of a load hook where the default value of a field is set to 50 euros converted into dollars:

class Forest::Customer
  include ForestLiana::Collection

  collection :Customer

  action 'Charge credit card', type: 'single', fields: [
    {
      field: 'country',
      type: 'Enum',
      enums: [],
    }
  ],
  hooks: {
     :load => -> do
       fields = get_fields
       fields['country']['enums'] = Company.get_enums_from_database_for_this_record

       fields
     end
  }
end
module ForestAdminRails
  class CreateAgent
    def self.customize
      include ForestAdminDatasourceCustomizer::Decorators::Action::Types

      @create_agent.customize_collection('Customer') do |collection|
        collection.add_action(
          'Charge credit card',
          BaseAction.new(
            scope: ActionScope::SINGLE,
            form: [
              {
                label: 'Country',
                type: FieldType::ENUM,
                enum_values: -> { Company.get_enums_from_database_for_this_record }
              }
            ]
          ) do |context, result_builder|
            #...
          end
        )
      end
    end
  end
end

And another for a change hook which makes a field required if the value of another field is greater than 100:

class Forest::Customer
  include ForestLiana::Collection

  collection :Customer

  action 'Charge credit card', type: 'single', fields: [
    {
      field: 'amount',
      type: 'Number',
      hook: 'onAmountChange',
    },
    {
      field: 'motivation',
      type: 'String',
      is_required: false,
    }
  ],
  hooks: {
    :change => {
      'onAmountChanged' => -> (context) {
        amount_field = context[:fields].find{ |field| field[:field] == 'amount'}
        motivation_field = context[:fields].find{ |field| field[:field] == 'motivation'}

        motivation_field[:is_required] = amount_field[:value] > 100

        context[:fields]
      }
    }
  }
end
module ForestAdminRails
  class CreateAgent
    def self.customize
      include ForestAdminDatasourceCustomizer::Decorators::Action::Types
      include ForestAdminDatasourceCustomizer::Decorators::Action::Context

      @create_agent.customize_collection('Customer') do |collection|
        collection.add_action(
          'Charge credit card',
          BaseAction.new(
            scope: ActionScope::SINGLE,
            form: [
              {
                label: 'Amount',
                type: FieldType::NUMBER,
              },
              {
                label: 'Motivation',
                type: FieldType::STRING,
                is_required: -> (context) { context.form_values['amount'] > 100 }
              }
            ]
          ) do |context, result_builder|
            #...
          end
        )
      end
    end
  end
end

Replace render json calls with return result_builder.success() or return result_builder.error(), or the .

here
appropriate result_builder method