Smart Actions
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 - collectionfunction in the appropriate- /lib/forest_liana/collections/*.rbfile.
- Then, you had to implement the action by creating a route handler in the appropriate - config/routes.rbfile.- In the new agent, the process is simplified to a single step. 
Code cheatsheet
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
add_action for the appropriate collectionStart by calling the add_action function on the appropriate collection and passing the appropriate parameters.
Most notably, you will need to pass:
- typeshould become- scope- Note that the values are now capitalized (e.g. - singlebecomes- Single)
- Legacy agents defaulted to - 'bulk'if no type was specified. The new agent requires you to specify the scope.
 
- downloadshould become- generate_file. This is still a boolean and the same value can be passed.
- endpointand- httpMethodshould 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'
endinclude 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
endStep 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:
- fieldsshould become- form.
- widgetchoice is no longer supported. A default widget will be used depending on the field type.
- hookcan be removed, those will be handled by the new agent automatically.
- referenceno longer exists. Use- { type: "Collection", collection_name: '... }instead.
- enumsno 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',
    }
  ]
endmodule 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
endStep 3: Porting the route to the new agent execute function
execute functionIn 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 - executefunction of the action.
- Replace call with - context.get_record_ids().
- Replace - render jsoncalls with- return result_builder.success()or- return result_builder.error(), or the appropriate- result_buildermethod.
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
endmodule 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
endStep 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
  }
endmodule 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
endAnd 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]
      }
    }
  }
endmodule 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
endLast updated
Was this helpful?
