Project

gaskit

0.0
The project is in a healthy, maintained state
Gaskit provides a lightweight, extensible framework for encapsulating business logic using a consistent, composable operation pattern. It supports context propagation, exit handling, structured results, and flexible logging via configuration.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Development

~> 3.12
~> 1.60
~> 0.9

Runtime

~> 7.0
 Project Readme

Gaskit

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.

โœจ Features

  • โœ… Operation, Service, and Query classes
  • ๐Ÿ”€ Customizable result and early exit contracts via use_contract
  • ๐Ÿงฑ Composable multi-step flows using Flow DSL
  • ๐Ÿงช 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

๐Ÿ“ฆ Installation

Add this line to your application's Gemfile:

gem 'gaskit'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install gaskit

๐Ÿ”ง Configuration

You 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))
end

๐Ÿš€ Usage

Define an Operation

class 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
end

Handle Result

result = 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]}"
end

Define Errors

class AuthOp < Gaskit::Operation
  error :unauthorized, "Not allowed", code: "AUTH-001"

  def call
    exit(:unauthorized)
  end
end

Composing Flows

class CheckoutFlow < Gaskit::Flow
  step AddToCart
  step ApplyDiscount
  step FinalizeOrder
end

result = CheckoutFlow.call(user_id: 123)

๐Ÿช Hooks

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
end

Register global hooks via:

Gaskit.hooks.register(:before, :audit) { |op| puts "Before: #{op.class}" }

๐Ÿงช Generators

# 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 User

๐Ÿ“‚ Contracts

You can define contracts using registered result types:

class MyResult < Gaskit::OperationResult; end

Gaskit.contracts.register(:custom, MyResult)

class CustomOp < Gaskit::Operation
  use_contract :custom
end

Or override just part of the contract:

class CustomResult < Gaskit::OperationResult; end

class PartialContractOp < Gaskit::Operation
  use_contract :service, result: CustomResult
end

๐Ÿงฑ Repositories

class 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")

๐Ÿ“ˆ Logging

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 })

Planned Features

  • Caching Flow operations to provide replaying and resume on failure.

๐Ÿค Contributing

Bug reports and pull requests are welcome! Feel free to fork, extend, and share improvements.

๐Ÿ“œ License

This gem is licensed under the MIT License.


Made with โค๏ธ by bnlucas