Light Services is a simple yet powerful way to organize business logic in Ruby applications. Build services that are easy to test, maintain, and understand.
- ✨ Simple: Define your service as a class with
arguments,steps, andoutputs - 📦 No runtime dependencies: Works stand-alone without requiring external gems at runtime
- 🔄 Transactions: Automatically rollback database changes if any step fails
- 🧬 Inheritance: Inherit from other services to reuse logic seamlessly
⚠️ Error Handling: Collect errors from steps and handle them your way- 🔗 Context: Run multiple services sequentially within the same context
- 🧪 RSpec Matchers: Built-in RSpec matchers for expressive service tests
- 🌐 Framework Agnostic: Compatible with Rails, Hanami, or any Ruby framework
- 🧩 Modularity: Isolate and test your services with ease
- ✅ 100% Test Coverage: Thoroughly tested and reliable
- ⚔️ Battle-Tested: In production use since 2017
gem "light-services", "~> 3.0"rails generate light_services:installclass GreetService < Light::Services::Base
# Arguments
arg :name, type: String
arg :age, type: Integer
# Steps
step :build_message
step :send_message
# Outputs
output :message, type: String
private
def build_message
self.message = "Hello, #{name}! You are #{age} years old."
end
def send_message
# Send logic goes here
end
endclass User::ResetPassword < Light::Services::Base
# Arguments with dry-types for advanced validation and coercion
arg :user, type: Types.Instance(User), optional: true
arg :email, type: Types::Coercible::String, optional: true
arg :send_email, type: Types::Params::Bool, default: true
# Steps
step :validate
step :find_user, unless: :user?
step :generate_reset_token
step :save_reset_token
step :send_reset_email, if: :send_email?
# Outputs with dry-types
output :user, type: Types.Instance(User)
output :reset_token, type: Types::Strict::String
private
def validate
errors.add(:base, "user or email is required") if !user? && !email?
end
def find_user
self.user = User.find_by("LOWER(email) = ?", email.downcase)
errors.add(:email, "not found") unless user
end
def generate_reset_token
self.reset_token = SecureRandom.hex(32)
end
def save_reset_token
user.update!(
reset_password_token: reset_token,
reset_password_sent_at: Time.current,
)
rescue ActiveRecord::RecordInvalid => e
errors.from_record(e.record)
end
def send_reset_email
Mailer::SendEmail
.with(self) # Call sub-service with the same context
.run(template: :reset_password, user:, reset_token:)
end
endGet started with Light Services
Light Services includes Rails generators to help you quickly set up and create services in your Rails application.
Set up Light Services in your Rails application:
bin/rails generate light_services:installThis creates:
app/services/application_service.rb- Base service class for your applicationconfig/initializers/light_services.rb- Configuration filespec/services/application_service_spec.rb- RSpec test file (if RSpec is detected)
Options:
--skip-initializer- Skip creating the initializer file--skip-spec- Skip creating the spec file
Create a new service class:
# Basic service
bin/rails generate light_services:service user/create
# Service with predefined structure
bin/rails generate light_services:service CreateOrder \
--args=user product \
--steps=validate process \
--outputs=orderThis creates:
app/services/user/create.rb- Service class filespec/services/user/create_spec.rb- RSpec test file (if RSpec is detected)
Options:
--args- List of arguments for the service (e.g.,--args=user product)--steps- List of steps for the service (e.g.,--steps=validate process)--outputs- List of outputs for the service (e.g.,--outputs=result)--skip-spec- Skip creating the spec file--parent- Parent class (default: ApplicationService)
You can find the full documentation at light-services.kodkod.me.
The gem is available as open source under the terms of the MIT License.