You are browsing docs for Avo 2: go to Avo 3

Skip to content

Actions

Avo actions allow you to perform specific tasks on one or more of your records.

For example, you might want to mark a user as active/inactive and optionally send a message that may be customized by the person that wants to run the action.

Once you attach an action to a resource using the action method, it will appear in the Actions dropdown. By default, actions appear on the Index, Show, and Edit views. Versions previous to 2.9 would only display the actions on the Index and Show views.

Actions dropdown

INFO

Since version 2.13.0 you may use the customizable controls feature to show the actions outside the dropdown.

Overview

You generate one running bin/rails generate avo:action toggle_inactive, creating an action configuration file.

ruby
class ToggleInactive < Avo::BaseAction
  self.name = 'Toggle inactive'

  field :notify_user, as: :boolean, default: true
  field :message, as: :text, default: 'Your account has been marked as inactive.'

  def handle(**args)
    models, fields, current_user, resource = args.values_at(:models, :fields, :current_user, :resource)

    models.each do |model|
      if model.active
        model.update active: false
      else
        model.update active: true
      end

      # Optionally, you may send a notification with the message to that user from inside the action
      UserMailer.with(user: model).toggle_inactive(fields["message"]).deliver_later
    end

    succeed 'Perfect!'
  end
end

You may add fields to the action just as you do it in a resource. Adding fields is optional. You may have actions that don't have any fields attached.

ruby
field :notify_user, as: :boolean
field :message, as: :textarea, default: 'Your account has been marked as inactive.'

Files authorization

If you're using the file field on an action and attach it to a resource that's using the authorization feature, please ensure you have the upload_{FIELD_ID}? policy method returning true. Otherwise, the file input might be hidden.

More about this on the authorization page.

Actions

The handle method is where the magic happens. That is where you put your action logic. In this method, you will have access to the selected models (if there's only one, it will be automatically wrapped in an array) and the values passed to the fields.

ruby
def handle(**args)
  models, fields = args.values_at(:models, :fields)

  models.each do |model|
    if model.active
      model.update active: false
    else
      model.update active: true
    end

    # Optionally, you may send a notification with the message to that user.
    UserMailer.with(user: model).toggle_inactive(fields["message"]).deliver_later
  end

  succeed 'Perfect!'
end

Registering actions

To add an action to one of your resources, you need to declare it on the resource using the action method.

ruby
class UserResource < Avo::BaseResource
  self.title = :name
  self.search = [:id, :first_name, :last_name]

  field :id, as: :id
  # other fields

  action ToggleActive
end

Action responses

After an action runs, you may use several methods to respond to the user. For example, you may respond with just a message or with a message and an action.

The default response is to reload the page and show the Action ran successfully message.

Message responses

You will have four message response methods at your disposal succeed, error, warn, and inform. These will render the user green, red, orange, and blue alerts.

ruby
def handle(**args)
  # Demo handle action

  succeed "Success response ✌️"
  warn "Warning response ✌️"
  inform "Info response ✌️"
  error "Error response ✌️"
end

WARNING

Since Avo 2.20 we deprecated the fail method in favor of error.

Avo alert responses

Run actions silently

You may want to run an action and show no notification when it's done. That is useful for redirect scenarios. You can use the silent response for that.

ruby
def handle(**args)
  # Demo handle action

  redirect_to "/admin/some-tool"
  silent
end

Response types

After you notify the user about what happened through a message, you may want to execute an action like reload (default action) or redirect_to. You may use message and action responses together.

ruby
def handle(**args)
  models = args[:models]

  models.each do |model|
    if model.admin?
      error "Can't mark inactive! The user is an admin."
    else
      model.update active: false

      succeed "Done! User marked as inactive!"
    end
  end

  reload
end

The available action responses are:

reload

When you use reload, a full-page reload will be triggered.

ruby
def handle(**args)
  models = args[:models]

  models.each do |project|
    project.update active: false
  end

  succeed 'Done!'
  reload
end

redirect_to

redirect_to will execute a redirect to a new path of your app. It accept allow_other_host, status and any other arguments.

Example: redirect_to path, allow_other_host: true, status: 303

ruby
def handle(**args)
  models = args[:models]

  models.each do |project|
    project.update active: false
  end

  succeed 'Done!'
  redirect_to avo.resources_users_path
end

You may want to redirect to another action. Here's an example of how to create a multi-step process, passing arguments from one action to another. In this example the initial action prompts the user to select the fields they wish to update, and in the subsequent action, the chosen fields will be accessible for updating.

ruby
class PreUpdate < Avo::BaseAction
  self.name = "Update"
  self.message = "Set the fields you want to update."

  with_options as: :boolean do
    field :first_name
    field :last_name
    field :user_email
    field :active
    field :admin
  end

  def handle(**args)
    arguments = Base64.encode64 Avo::Services::EncryptionService.encrypt(
      message: {
        render_first_name: args[:fields][:first_name],
        render_last_name: args[:fields][:last_name],
        render_user_email: args[:fields][:user_email],
        render_active: args[:fields][:active],
        render_admin: args[:fields][:admin]
      },
      purpose: :action_arguments
    )

    redirect_to "/admin/resources/users/actions?action_id=Update&arguments=#{arguments}", turbo_frame: "actions_show"
  end
end
ruby
class Update < Avo::BaseAction
  self.name = "Update"
  self.message = ""
  self.visible = -> do
    false
  end

  {
    first_name: :text,
    last_name: :text,
    user_email: :text,
    active: :boolean,
    admin: :boolean
  }.each do |field_name, field_type|
    field field_name.to_sym, as: field_type, visible: -> (resource:) {
      Avo::Services::EncryptionService.decrypt(
        message: Base64.decode64(resource.params[:arguments]),
        purpose: :action_arguments
      ).dig("render_#{field_name}".to_sym)
    }
  end

  def handle(models:, fields:, **args)
    non_roles_fields = fields.slice!(:admin)

    models.each { |model| model.update!(non_roles_fields) }

    fields.each do |field_name, field_value|
      models.each { |model|  model.update! roles: model.roles.merge!({"#{field_name}": field_value}) }
    end

    succeed "User(s) updated!"
  end
end

turbo_frame

Notice the turbo_frame: "actions_show" present on the redirect of PreUpdate action. That argument is essential to have a flawless redirect between the actions.

turbo

There are times when you don't want to perform the actions with Turbo. In such cases, turbo should be set to false.

download

download will start a file download to your specified path and filename.

You need to set may_download_file to true for the download response to work like below. That's required because we can't respond with a file download (send_data) when making a Turbo request.

If you find another way, please let us know 😅.

ruby
class DownloadFile < Avo::BaseAction
  self.name = "Download file"
  self.may_download_file = true

  def handle(**args)
    models = args[:models]

    filename = "projects.csv"
    report_data = []

    models.each do |project|
      report_data << project.generate_report_data
    end

    succeed 'Done!'

    if report_data.present? and filename.present?
      download report_data, filename
    end
  end
end
ruby
class ProjectResource < Avo::BaseResource

  # fields here

  action DownloadFile
end

keep_modal_open

There might be situations where you want to run an action and if it fails, respond back to the user with some feedback but still keep it open and the inputs filled in.

keep_modal_open will tell Avo to keep the modal open.

ruby
class KeepModalOpenAction < Avo::BaseAction
  self.name = "Keep Modal Open"
  self.standalone = true

  field :name, as: :text
  field :birthday, as: :date

  def handle(**args)
    begin
    user = User.create args[:fields]
    rescue => error
      error "Something happened: #{error.message}"
      keep_modal_open
      return
    end

    succeed "All good ✌️"
  end
end

Customization

ruby
class TogglePublished < Avo::BaseAction
  self.name = 'Mark inactive'
  self.message = 'Are you sure you want to mark this user as inactive?'
  self.confirm_button_label = 'Mark inactive'
  self.cancel_button_label = 'Not yet'
  self.no_confirmation = true

Customize the message

You may update the self.message class attribute to customize the message if there are no fields present.

Callable message

Since v2.21

Since version 2.21 you can pass a block to self.message where you have access to a baunch of variables.

ruby
class ReleaseFish < Avo::BaseAction
  self.message = -> {
    # you have access to:
    # - params
    # - current_user
    # - context
    # - view_context
    # - request
    # - resource
    # - record
    "Are you sure you want to release the #{record.name}?"
  }
end

Customize the buttons

You may customize the labels for the action buttons using confirm_button_label and cancel_button_label.

Avo button labels

No confirmation actions

You will be prompted by a confirmation modal when you run an action. If you don't want to show the confirmation modal, pass in the self.no_confirmation = true class attribute. That will execute the action without showing the modal at all.

Standalone actions

You may need to run actions that are not necessarily tied to a model. Standalone actions help you do just that. Add self.standalone to an existing action or generate a new one using the --standalone option (bin/rails generate avo:action global_action --standalone).

ruby
class DummyAction < Avo::BaseAction
  self.name = "Dummy action"
  self.standalone = true

  def handle(**args)
    fields, current_user, resource = args.values_at(:fields, :current_user, :resource)

    # Do something here

    succeed 'Yup'
  end
end

Actions visibility

You may want to hide specific actions on screens, like a standalone action on the Show screen. You can do that using the self.visible attribute.

ruby
class DummyAction < Avo::BaseAction
  self.name = "Dummy action"
  self.standalone = true
  self.visible = -> { view == :index }

  def handle(**args)
    fields, current_user, resource = args.values_at(:fields, :current_user, :resource)

    # Do something here

    succeed 'Yup'
  end
end

By default, actions are visible on the Index, Show, and Edit views, but you can enable them on the New screen, too (from version 2.9.0).

ruby
self.visible = -> { view == :new }

# Or use this if you want them to be visible on any view
self.visible = -> { true }

Inside the visible block you can access the following variables:

ruby
  self.visible = -> do
    #   You have access to:
    #   block
    #   context
    #   current_user
    #   params
    #   parent_resource (can access the parent_model by parent_resource.model)
    #   resource (can access the model by resource.model)
    #   view
    #   view_context
  end

Actions authorization

WARNING

Using the Pundit policies, you can restrict access to actions using the act_on? method. If you think you should see an action on a resource and you don't, please check the policy method.

More info here

Actions arguments

Actions can have different behaviors according to their host resource. In order to achieve that, arguments must be passed like on the example below:

ruby
class FishResource < Avo::BaseResource
  self.title = :name

  field :id, as: :id
  field :name, as: :text
  field :user, as: :belongs_to
  field :type, as: :text, hide_on: :forms

  action DummyAction, arguments: {
    special_message: true
  }
end

Now, the arguments can be accessed inside DummyAction handle method and on the visible block!

ruby
class DummyAction < Avo::BaseAction
  self.name = "Dummy action"
  self.standalone = true
  self.visible = -> do
    arguments[:special_message]
  end

  def handle(**args)
    if arguments[:special_message]
      succeed "I love 🥑"
    else
      succeed "Success response ✌️"
    end
  end
end