Skip to content

Attributes - Definitions

Attributes define your task's interface with automatic validation, type coercion, and accessor generation. They're the contract between callers and your business logic.

Declarations

Tip

Prefer using the required and optional alias for attributes for brevity and to clearly signal intent.

Optional

Optional attributes return nil when not provided.

class ScheduleEvent < CMDx::Task
  attribute :title
  attributes :duration, :location

  # Alias for attributes (preferred)
  optional :description
  optional :visibility, :attendees

  def work
    title       #=> "Team Standup"
    duration    #=> 30
    location    #=> nil
    description #=> nil
    visibility  #=> nil
    attendees   #=> ["alice@company.com", "bob@company.com"]
  end
end

# Attributes passed as keyword arguments
ScheduleEvent.execute(
  title: "Team Standup",
  duration: 30,
  attendees: ["alice@company.com", "bob@company.com"]
)

Required

Required attributes must be provided in call arguments or task execution will fail.

class PublishArticle < CMDx::Task
  attribute :title, required: true
  attributes :content, :author_id, required: true

  # Alias for attributes => required: true (preferred)
  required :category
  required :status, :tags

  def work
    title     #=> "Getting Started with Ruby"
    content   #=> "This is a comprehensive guide..."
    author_id #=> 42
    category  #=> "programming"
    status    #=> :published
    tags      #=> ["ruby", "beginner"]
  end
end

# Attributes passed as keyword arguments
PublishArticle.execute(
  title: "Getting Started with Ruby",
  content: "This is a comprehensive guide...",
  author_id: 42,
  category: "programming",
  status: :published,
  tags: ["ruby", "beginner"]
)

Sources

Attributes read from any accessible object—not just context. Use sources to pull data from models, services, or any callable:

Context

class BackupDatabase < CMDx::Task
  # Default source is :context
  required :database_name
  optional :compression_level

  # Explicitly specify context source
  attribute :backup_path, source: :context

  def work
    database_name     #=> context.database_name
    backup_path       #=> context.backup_path
    compression_level #=> context.compression_level
  end
end

Symbol References

Reference instance methods by symbol for dynamic source values:

class BackupDatabase < CMDx::Task
  attributes :host, :credentials, source: :database_config

  # Access from declared attributes
  attribute :connection_string, source: :credentials

  def work
    # Your logic here...
  end

  private

  def database_config
    @database_config ||= DatabaseConfig.find(context.database_name)
  end
end

Proc or Lambda

Use anonymous functions for dynamic source values:

class BackupDatabase < CMDx::Task
  # Proc
  attribute :timestamp, source: proc { Time.current }

  # Lambda
  attribute :server, source: -> { Current.server }
end

Class or Module

For complex source logic, use classes or modules:

class DatabaseResolver
  def self.call(task)
    Database.find(task.context.database_name)
  end
end

class BackupDatabase < CMDx::Task
  # Class or Module
  attribute :schema, source: DatabaseResolver

  # Instance
  attribute :metadata, source: DatabaseResolver.new
end

Nesting

Build complex structures with nested attributes. Children inherit their parent as source and support all attribute options:

Note

Nested attributes support all features: naming, coercions, validations, defaults, and more.

class ConfigureServer < CMDx::Task
  # Required parent with required children
  required :network_config do
    required :hostname, :port, :protocol, :subnet
    optional :load_balancer
    attribute :firewall_rules
  end

  # Optional parent with conditional children
  optional :ssl_config do
    required :certificate_path, :private_key # Only required if ssl_config provided
    optional :enable_http2, prefix: true
  end

  # Multi-level nesting
  attribute :monitoring do
    required :provider

    optional :alerting do
      required :threshold_percentage
      optional :notification_channel
    end
  end

  def work
    network_config   #=> { hostname: "api.company.com" ... }
    hostname         #=> "api.company.com"
    load_balancer    #=> nil
  end
end

ConfigureServer.execute(
  server_id: "srv-001",
  network_config: {
    hostname: "api.company.com",
    port: 443,
    protocol: "https",
    subnet: "10.0.1.0/24",
    firewall_rules: "allow_web_traffic"
  },
  monitoring: {
    provider: "datadog",
    alerting: {
      threshold_percentage: 85.0,
      notification_channel: "slack"
    }
  }
)

Important

Child requirements only apply when the parent is provided—perfect for optional structures.

Error Handling

Validation failures provide detailed, structured error messages:

Note

Nested attributes are only validated when their parent is present and valid.

class ConfigureServer < CMDx::Task
  required :server_id, :environment
  required :network_config do
    required :hostname, :port
  end

  def work
    # Your logic here...
  end
end

# Missing required top-level attributes
result = ConfigureServer.execute(server_id: "srv-001")

result.state    #=> "interrupted"
result.status   #=> "failed"
result.reason   #=> "Invalid"
result.metadata #=> {
                #     errors: {
                #       full_message: "environment is required. network_config is required.",
                #       messages: {
                #         environment: ["is required"],
                #         network_config: ["is required"]
                #       }
                #     }
                #   }

# Missing required nested attributes
result = ConfigureServer.execute(
  server_id: "srv-001",
  environment: "production",
  network_config: { hostname: "api.company.com" } # Missing port
)

result.state    #=> "interrupted"
result.status   #=> "failed"
result.reason   #=> "Invalid"
result.metadata #=> {
                #     errors: {
                #       full_message: "port is required.",
                #       messages: {
                #         port: ["is required"]
                #       }
                #     }
                #   }