Agent skill

plutonium-definition-actions

Add custom actions and interactions to Plutonium resources

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/plutonium-definition-actions

SKILL.md

Definition Actions

Actions define custom operations that can be performed on resources. They can be simple (navigation) or interactive (with business logic via Interactions).

Action Types

Type Shows In Use Case
resource_action Index page Import, Export, Create
record_action Show page Edit, Delete, Archive
collection_record_action Table rows Quick actions per row
bulk_action Selected records Bulk operations

Simple Actions (Navigation)

Simple actions link to existing routes. The target route must already exist - these don't create new functionality, just navigation links.

ruby
class PostDefinition < ResourceDefinition
  # Link to external URL
  action :documentation,
    label: "Documentation",
    route_options: {url: "https://docs.example.com"},
    icon: Phlex::TablerIcons::Book,
    resource_action: true

  # Link to custom controller action (you must add the action + route yourself)
  action :reports,
    route_options: {action: :reports},
    icon: Phlex::TablerIcons::ChartBar,
    resource_action: true
end

Important: When adding custom routes for actions, always use the as: option to name them:

ruby
# In your portal routes or config/routes.rb
resources :posts do
  collection do
    get :reports, as: :reports  # Named route required!
  end
end

This ensures resource_url_for can generate correct URLs, especially for nested resources.

Note: For custom operations with business logic, use Interactive Actions with an Interaction class instead. That's the recommended approach for most custom actions.

Interactive Actions (with Interaction)

ruby
class PostDefinition < ResourceDefinition
  action :publish,
    interaction: PublishInteraction,
    icon: Phlex::TablerIcons::Send

  action :archive,
    interaction: ArchiveInteraction,
    color: :danger,
    category: :danger,
    position: 1000,
    confirmation: "Are you sure?"
end

Action Options

ruby
action :name,
  # Display
  label: "Custom Label",           # Button text (default: name.titleize)
  description: "What it does",     # Tooltip/description
  icon: Phlex::TablerIcons::Star,  # Icon component
  color: :danger,                  # :primary, :secondary, :danger

  # Visibility
  resource_action: true,           # Show on index page
  record_action: true,             # Show on show page
  collection_record_action: true,  # Show in table rows
  bulk_action: true,               # For selected records

  # Grouping
  category: :primary,              # :primary, :secondary, :danger
  position: 50,                    # Order (lower = first)

  # Behavior
  confirmation: "Are you sure?",   # Confirmation dialog
  turbo_frame: "_top",             # Turbo frame target
  route_options: {action: :foo}    # Route configuration

Creating an Interaction

Basic Structure

ruby
# app/interactions/resource_interaction.rb (generated during install)
class ResourceInteraction < Plutonium::Resource::Interaction
end

# app/interactions/archive_interaction.rb
class ArchiveInteraction < ResourceInteraction
  presents label: "Archive",
           icon: Phlex::TablerIcons::Archive,
           description: "Archive this record"

  attribute :resource  # The record being acted on

  def execute
    resource.archived!
    succeed(resource).with_message("Record archived successfully.")
  rescue ActiveRecord::RecordInvalid => e
    failed(e.record.errors)
  rescue => error
    failed("Archive failed. Please try again.")
  end
end

With Additional Inputs

ruby
# app/interactions/company/invite_user_interaction.rb
class Company::InviteUserInteraction < Plutonium::Resource::Interaction
  presents label: "Invite User", icon: Phlex::TablerIcons::Mail

  attribute :resource  # The company
  attribute :email
  attribute :role

  # Configure form inputs
  input :email, as: :email, hint: "User's email address"
  input :role, as: :select, choices: %w[admin member viewer]

  # Validations
  validates :email, presence: true, format: {with: URI::MailTo::EMAIL_REGEXP}
  validates :role, presence: true, inclusion: {in: %w[admin member viewer]}

  def execute
    UserInvite.create!(
      company: resource,
      email: email,
      role: role,
      invited_by: current_user
    )
    succeed(resource).with_message("Invitation sent to #{email}.")
  rescue ActiveRecord::RecordInvalid => e
    failed(e.record.errors)
  end
end

Bulk Action (Multiple Records)

Bulk actions operate on multiple selected records at once. When a definition has bulk actions, the resource table automatically shows:

  • Selection checkboxes in each row
  • Bulk actions toolbar that appears when records are selected
ruby
# 1. Create the interaction (note: plural `resources` attribute)
class BulkArchiveInteraction < Plutonium::Resource::Interaction
  presents label: "Archive Selected", icon: Phlex::TablerIcons::Archive

  attribute :resources  # Array of records (note: plural)

  def execute
    count = 0
    resources.each do |record|
      record.archived!
      count += 1
    end
    succeed(resources).with_message("#{count} records archived.")
  rescue => error
    failed("Bulk archive failed: #{error.message}")
  end
end

# 2. Register the action in the definition
class PostDefinition < ResourceDefinition
  action :bulk_archive, interaction: BulkArchiveInteraction
  # bulk_action: true is automatically inferred from `resources` attribute
end

# 3. Add policy method
class PostPolicy < ResourcePolicy
  def bulk_archive?
    create?  # Or whatever permission level is appropriate
  end
end

Authorization for bulk actions:

  • Policy method (e.g., bulk_archive?) is checked per record - the backend fails the entire request if any selected record is not authorized
  • Records are fetched via current_authorized_scope - only records the user can access are included
  • The UI only shows action buttons that all selected records support (intersection of allowed actions)

Resource Action (No Record)

ruby
class ImportInteraction < Plutonium::Resource::Interaction
  presents label: "Import CSV", icon: Phlex::TablerIcons::Upload

  # No :resource or :resources attribute = resource action
  attribute :file

  input :file, as: :file

  validates :file, presence: true

  def execute
    # Import logic...
    succeed(nil).with_message("Import completed.")
  end
end

Interaction Responses

ruby
def execute
  # Success with message (redirects to resource automatically)
  succeed(resource).with_message("Done!")

  # Success with custom redirect (only if different from default)
  succeed(resource)
    .with_redirect_response(custom_dashboard_path)
    .with_message("Redirecting...")

  # Failure with field errors
  failed(resource.errors)

  # Failure with custom message
  failed("Something went wrong")

  # Failure with specific field
  failed("Invalid value", :email)
end

Note: Redirect is automatic on success. Only use with_redirect_response for a different destination.

Interaction Context

Inside an interaction:

  • current_user - The authenticated user
  • view_context - Access to helpers and view methods
ruby
def execute
  resource.update!(
    archived_by: current_user,
    archived_at: Time.current
  )
  succeed(resource)
end

Defining in Definition

Basic

ruby
class PostDefinition < ResourceDefinition
  action :publish, interaction: PublishInteraction
  action :archive, interaction: ArchiveInteraction
end

With Overrides

ruby
class PostDefinition < ResourceDefinition
  action :archive,
    interaction: ArchiveInteraction,
    collection_record_action: false,  # Don't show in table
    color: :danger,
    position: 1000                    # Show last
end

Inherited Actions

Actions defined in ResourceDefinition (created during install) are inherited by all definitions:

ruby
# app/definitions/resource_definition.rb (created during install)
class ResourceDefinition < Plutonium::Resource::Definition
  action :archive, interaction: ArchiveInteraction, color: :danger, position: 1000
end

# All definitions inherit the archive action automatically
class PostDefinition < ResourceDefinition
end

Portal-Specific Actions

Override actions for a specific portal:

ruby
# packages/admin_portal/app/definitions/admin_portal/post_definition.rb
class AdminPortal::PostDefinition < ::PostDefinition
  # Add admin-only actions
  action :feature, interaction: FeaturePostInteraction
  action :bulk_publish, interaction: BulkPublishInteraction
end

Default CRUD Actions

Plutonium provides these by default:

ruby
action :new,     resource_action: true,           position: 10
action :show,    collection_record_action: true,  position: 10
action :edit,    record_action: true,             position: 20
action :destroy, record_action: true,             position: 100, category: :danger

Authorization

Actions are authorized via policies:

ruby
# app/policies/post_policy.rb
class PostPolicy < ResourcePolicy
  def publish?
    user.admin? || record.author == user
  end

  def archive?
    user.admin?
  end
end

The action only appears if the policy method returns true.

Immediate vs Form Actions

Immediate - Executes without showing a form (when interaction has no extra inputs):

ruby
class ArchiveInteraction < Plutonium::Resource::Interaction
  attribute :resource  # Only resource, no other inputs
  # No input declarations

  def execute
    resource.archived!
    succeed(resource)
  end
end

Form - Shows a form first (when interaction has additional inputs):

ruby
class InviteUserInteraction < Plutonium::Resource::Interaction
  attribute :resource
  attribute :email
  attribute :role

  input :email
  input :role, as: :select, choices: %w[admin member]
  # Has inputs = shows form first
end

Common Patterns

Archive/Restore

ruby
class ArchiveInteraction < Plutonium::Resource::Interaction
  presents label: "Archive", icon: Phlex::TablerIcons::Archive
  attribute :resource

  def execute
    resource.archived!
    succeed(resource).with_message("Archived.")
  end
end

class RestoreInteraction < Plutonium::Resource::Interaction
  presents label: "Restore", icon: Phlex::TablerIcons::Refresh
  attribute :resource

  def execute
    resource.active!
    succeed(resource).with_message("Restored.")
  end
end

Send Notification

ruby
class SendReminderInteraction < Plutonium::Resource::Interaction
  presents label: "Send Reminder", icon: Phlex::TablerIcons::Bell
  attribute :resource
  attribute :message

  input :message, as: :text, hint: "Custom message (optional)"

  def execute
    ReminderMailer.with(record: resource, message: message).deliver_later
    succeed(resource).with_message("Reminder sent.")
  end
end

Related Skills

  • plutonium-definition - Overview and structure
  • plutonium-definition-fields - Fields, inputs, displays
  • plutonium-definition-query - Search, filters, scopes
  • plutonium-interaction - Writing interaction classes
  • plutonium-policy - Controlling action access

Didn't find tool you were looking for?

Be as detailed as possible for better results