Arguments
Arguments are the inputs to a service. They are passed to the service when it is invoked.
TL;DR
- Define arguments with the
arg
keyword in the service class - Validate arguments by type
- Specify arguments as required or optional
- Set default values for arguments
- Access arguments like instance variables
- Use predicate methods for arguments
Example
class User::Charge < ApplicationService
arg :user, type: User
arg :amount, type: :float
arg :send_receipt, type: :boolean, default: true
arg :invoice_date, type: Date, default: -> { Date.current }
step :send_email_receipt, if: :send_receipt?
# ...
end
Define Arguments
Arguments are defined using the arg
keyword in the service class.
class HappyBirthdayService < ApplicationService
arg :name
arg :age
end
Type Validation
Arguments can be validated by type.
class HappyBirthdayService < ApplicationService
arg :name, type: String # or type: :string
arg :age, type: Integer # or type: :integer
end
You can specify multiple allowed types using an array.
class HappyBirthdayService < ApplicationService
arg :name, type: [String, Symbol]
end
Required Arguments
By default, arguments are required. You can make them optional by setting optional
to true
.
class HappyBirthdayService < ApplicationService
arg :name, type: String
arg :age, type: Integer, optional: true
end
Default Values
Set a default value for an argument to make it optional.
class HappyBirthdayService < ApplicationService
arg :name, type: String
arg :age, type: Integer, default: 18
end
Complex Default Values
Default values are deep duplicated when the service is invoked, making it safe to use mutable objects.
arg :options, type: Hash, default: { a: 1, b: 2 }
Procs as Default Values
Use procs for dynamic default values.
arg :current_date, type: Date, default: -> { Date.current }
Inheritance
Arguments are inherited from parent classes.
UpdateRecordService
class UpdateRecordService < ApplicationService
# Arguments
arg :record, type: ApplicationRecord
arg :attributes, type: Hash
# Steps
step :authorize
step :update_record
end
User::Update inherited from UpdateRecordService
class User::Update < UpdateRecordService
# Nothing to do here
# Arguments and steps are inherited from UpdateRecordService
end
To remove inherited arguments, use remove_arg :argument_name
.
Context Arguments
Context arguments are automatically passed to all child services in the same context. Define them using the context
option. This is useful for passing objects like current_user
.
You'll learn more about this in context docs.
class ApplicationService < LightService::Base
arg :current_user, type: User, optional: true, context: true
end
Accessing Arguments
Arguments are accessible like instance variables, similar to attr_accessor
.
class HappyBirthdayService < ApplicationService
# Arguments
arg :name, type: String
arg :age, type: Integer
# Steps
step :greet
private
def greet
puts "Happy birthday, #{name}! You are #{age} years old."
end
end
Accessing Arguments Using arguments
For dynamic access or to avoid conflicts, use the arguments
method.
class HappyBirthdayService < ApplicationService
# Arguments
arg :name, type: String
arg :age, type: Integer
# Steps
step :greet
private
def greet
name = arguments[:name] # or arguments.get(:name)
age = arguments[:age] # or arguments.get(:age)
puts "Happy birthday, #{name}! You are #{age} years old."
end
end
Argument Predicate Methods
Predicate methods are automatically generated for each argument, allowing you to check if an argument is true
or false
.
class User::GenerateInvoice < ApplicationService
# Arguments
arg :user, type: User
arg :charge, type: :boolean, default: false
# Steps
step :generate_invoice
step :charge_user, if: :charge?
# ...
end
The predicate methods return true
or false
based on Ruby's convention: nil
and false
are false
, everything else is true
.
What's Next?
Next step is steps
(I love this pun). Steps are the building blocks of a service, the methods that do the actual work.