Skip to content

A foundation for building modular, extensible DSLs in Ruby

License

Notifications You must be signed in to change notification settings

servactory/stroma

Stroma

A foundation for building modular, extensible DSLs in Ruby.

Gem version Release Date Downloads Ruby version

πŸ’‘ Why Stroma?

Building modular DSLs shouldn't require reinventing the wheel. Stroma provides a structured approach for library authors to compose DSL modules with:

  • πŸ”Œ Module Registration - Register DSL modules at boot time, compose them into a unified interface
  • 🧱 Structured Composition - Include all registered modules automatically via single DSL entry point
  • πŸ›οΈ Inheritance Safe - Per-class state isolation with automatic deep copying
  • πŸͺ Extension Hooks - Optional before/after hooks for user customization
  • βš™οΈ Extension Settings - Three-level hierarchical storage for extension configuration
  • πŸ”’ Thread Safe - Immutable registry after finalization, safe concurrent reads

🧬 Concept

Stroma is a foundation for library authors building DSL-driven frameworks (service objects, form objects, decorators, etc.).

Core lifecycle:

  1. Define - Create a Matrix with DSL modules at boot time
  2. Include - Classes include the matrix's DSL to gain all modules
  3. Extend (optional) - Add cross-cutting logic via before/after hooks

πŸš€ Quick Start

Installation

spec.add_dependency "stroma", ">= 0.4"

Define your library's DSL

module MyLib
  STROMA = Stroma::Matrix.define(:my_lib) do
    register :inputs, MyLib::Inputs::DSL
    register :actions, MyLib::Actions::DSL
  end
  private_constant :STROMA
end

Create base class

module MyLib
  class Base
    include STROMA.dsl
  end
end

Usage

Create an intermediate class with lifecycle hooks:

class ApplicationService < MyLib::Base
  # Add lifecycle hooks (optional)
  extensions do
    before :actions, ApplicationService::Extensions::Rollbackable::DSL
  end
end

Build services that inherit extension functionality:

class UserService < ApplicationService
  # DSL method from Rollbackable extension
  on_rollback(...)

  input :email, type: String

  make :create_user

  private

  def create_user
    # implementation
  end
end

Extensions allow you to add cross-cutting concerns like transactions, authorization, and rollback support. See extension examples for implementation details.

🧩 Building Extensions

Extensions are standard Ruby modules that hook into the DSL lifecycle. Stroma places them at the correct position in the method chain, so super naturally flows through all registered extensions.

Define an extension

module Authorization
  def self.included(base)
    base.extend(ClassMethods)
    base.include(InstanceMethods)
  end

  module ClassMethods
    def authorize_with(method_name)
      stroma.settings[:actions][:authorization][:method_name] = method_name
    end
  end

  module InstanceMethods
    def call(...)
      method_name = self.class.stroma.settings[:actions][:authorization][:method_name]
      send(method_name) if method_name
      super
    end
  end
end

ClassMethods provides the class-level DSL. InstanceMethods overrides the orchestrator method defined by your library (here call) and delegates via super. Split them into separate files as the extension grows.

Register the extension

class ApplicationService < MyLib::Base
  extensions do
    before :actions, Authorization
  end
end

before places the module so its call executes before the :actions entry. Use after for post-processing. Multiple modules in one call: before :actions, ModA, ModB.

Use in a service

class UserService < ApplicationService
  authorize_with :check_permissions

  input :email, type: String

  make :create_user

  private

  def check_permissions
    # authorization logic
  end

  def create_user
    # runs only after check_permissions passes
  end
end

Settings and hooks are deep-copied on inheritance β€” each subclass has independent configuration.

πŸ’Ž Projects Using Stroma

  • Servactory β€” Service objects framework for Ruby applications

🀝 Contributing

We welcome contributions! Check out our Contributing Guide to get started.

Ways to contribute:

  • πŸ› Report bugs and issues
  • πŸ’‘ Suggest new features
  • πŸ“ Improve documentation
  • πŸ§ͺ Add test cases
  • πŸ”§ Submit pull requests

πŸ™ Acknowledgments

Special thanks to all our contributors!

πŸ“„ License

Stroma is available as open source under the terms of the MIT License.

About

A foundation for building modular, extensible DSLs in Ruby

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks