Gaskit is a flexible, pluggable, and structured operations framework for Ruby and Rails applications. It provides a consistent way to implement application logic using service objects, query objects, flows, and contracts β with robust support for early exits, structured logging, duration tracking, and failure handling.
- β
Operation,Service, andQueryclasses - π Customizable result and early exit contracts via
use_contract - π§± Composable multi-step flows using
FlowDSL - π§ͺ Built-in error declarations and early exits via
exit(:key) - β± Integrated duration tracking and structured logging
- π§° Generators for Rails to scaffold operations, services, queries, flows, and repositories
- πͺ Hook system for before/after/around instrumentation and auditing
Add this line to your application's Gemfile:
gem 'gaskit'And then execute:
$ bundle installOr install it yourself as:
$ gem install gaskitYou can configure Gaskit via an initializer:
Gaskit.configure do |config|
config.context_provider = -> { { request_id: RequestStore.store[:request_id] } }
config.setup_logger(Logger.new($stdout), formatter: Gaskit::Logger.formatter(:pretty))
endclass MyOp < Gaskit::Operation
use_contract :service
def call(user_id:)
user = User.find_by(id: user_id)
exit(:not_found, "User not found") unless user
logger.info("Found user id=#{user_id}")
user
end
endresult = MyOp.call(user_id: 42)
if result.success?
puts "Found user: #{result.value}"
elsif result.early_exit?
puts "Early exit: #{result.to_h[:exit]}"
else
puts "Failure: #{result.to_h[:error]}"
endclass AuthOp < Gaskit::Operation
error :unauthorized, "Not allowed", code: "AUTH-001"
def call
exit(:unauthorized)
end
endclass CheckoutFlow < Gaskit::Flow
step AddToCart
step ApplyDiscount
step FinalizeOrder
end
result = CheckoutFlow.call(user_id: 123)Use use_hooks to activate instrumentation:
class HookedOp < Gaskit::Operation
use_contract :service
use_hooks :audit
before do |op|
op.logger.info("Starting operation")
end
after do |op, result:, error:|
op.logger.info("Finished with result=#{result.inspect} error=#{error.inspect}")
end
def call
"hello"
end
endRegister global hooks via:
Gaskit.hooks.register(:before, :audit) { |op| puts "Before: #{op.class}" }# Generate an operation
rails generate gaskit:operation CreateUser
# Generate a query
rails generate gaskit:query FetchUsers
# Generate a service
rails generate gaskit:service RegisterAccount
# Generate a flow
rails generate gaskit:flow Checkout AddToCart ApplyDiscount FinalizeOrder
# Generate a repository
rails generate gaskit:repository UserYou can define contracts using registered result types:
class MyResult < Gaskit::OperationResult; end
Gaskit.contracts.register(:custom, MyResult)
class CustomOp < Gaskit::Operation
use_contract :custom
endOr override just part of the contract:
class CustomResult < Gaskit::OperationResult; end
class PartialContractOp < Gaskit::Operation
use_contract :service, result: CustomResult
endclass UserRepository < Gaskit::Repository
model User
def find_by_name_or_slug(name, profile_slug)
where(name: name).or(where(profile_slug: profile_slug))
end
end
users = UserRepository.where(active: true)
user = UserRepository.find_by_name_or_slug("User", "user123")Gaskit includes a flexible logger with support for structured JSON or pretty logs:
logger = Gaskit::Logger.new(self.class)
logger.info("Started process", context: { user_id: 1 })- Caching Flow operations to provide replaying and resume on failure.
Bug reports and pull requests are welcome! Feel free to fork, extend, and share improvements.
This gem is licensed under the MIT License.
Made with β€οΈ by bnlucas