Basics - Context¶
Context is your data container for inputs, intermediate values, and outputs. It makes sharing data between tasks effortless.
Building Context¶
Context.build intelligently handles different input types:
# From a hash
Context.build(email: "user@example.com")
# From an existing Context (reuses if not frozen)
Context.build(existing_context)
# From a Result or Task (extracts its context)
Context.build(some_result) # equivalent to some_result.context
# From nil (creates empty context)
Context.build(nil)
Important
Context.build raises ArgumentError if the argument doesn't respond to to_h or to_hash.
Assigning Data¶
Context automatically captures all task inputs, normalizing keys to symbols:
# Direct execution
CalculateShipping.execute(weight: 2.5, destination: "CA")
# Instance creation
CalculateShipping.new(weight: 2.5, "destination" => "CA")
Important
String keys convert to symbols automatically. Prefer symbols for consistency.
Accessing Data¶
Access context data using method notation, hash keys, or safe accessors:
class CalculateShipping < CMDx::Task
def work
# Method style access (preferred)
weight = context.weight
destination = context.destination
# Hash style access
service_type = context[:service_type]
options = context["options"]
# Safe access with defaults
rush_delivery = context.fetch(:rush_delivery, false)
carrier = context.dig(:options, :carrier)
# Fetch or set a default (returns existing value, or stores and returns the default)
context.fetch_or_store(:attempt_count, 0)
# Check key existence
context.key?(:weight) #=> true
# Iteration
context.each { |key, value| logger.debug("#{key}: #{value}") }
keys = context.map { |key, _| key }
# Shorter alias
cost = ctx.weight * ctx.rate_per_pound # ctx aliases context
end
end
Important
Undefined attributes return nil instead of raising errors—perfect for optional data.
Modifying Context¶
Context supports dynamic modification during task execution:
class CalculateShipping < CMDx::Task
def work
# Direct assignment
context.carrier = Carrier.find_by(code: context.carrier_code)
context.package = Package.new(weight: context.weight)
context.calculated_at = Time.now
# Hash-style assignment
context[:status] = "calculating"
context["tracking_number"] = "SHIP#{SecureRandom.hex(6)}"
# Conditional assignment
context.insurance_included ||= false
# Batch updates
context.merge!(
status: "completed",
shipping_cost: calculate_cost,
estimated_delivery: Time.now + 3.days
)
# Remove sensitive data
context.delete!(:credit_card_token)
# Clear all data
context.clear!
end
private
def calculate_cost
base_rate = context.weight * context.rate_per_pound
base_rate + (base_rate * context.tax_percentage)
end
end
Tip
Use context for both input values and intermediate results. This creates natural data flow through your task execution pipeline.
Data Sharing¶
Share context across tasks for seamless data flow:
# During execution
class CalculateShipping < CMDx::Task
def work
# Validate shipping data
validation_result = ValidateAddress.execute(context)
# Via context
CalculateInsurance.execute(context)
# Via result
NotifyShippingCalculated.execute(validation_result)
# Context now contains accumulated data from all tasks
context.address_validated #=> true (from validation)
context.insurance_calculated #=> true (from insurance)
context.notification_sent #=> true (from notification)
end
end
# After execution
result = CalculateShipping.execute(destination: "New York, NY")
CreateShippingLabel.execute(result)
Important
When passing context, a Result, or a Task to another task, the context is shared by reference—not copied. Mutations in one task are visible in the other. This enables natural data flow in pipelines but can cause surprises if you expect isolation. Use context.to_h to pass a snapshot instead.