SolidCallback

Gem Version Test Downloads Github forks Github stars License: MIT

SolidCallback adds powerful method interception capabilities to your Ruby classes with near-zero overhead. Clean, flexible, and unobtrusive.

Features

  • 🔄 Method Lifecycle Hooks: before_call, after_call, and around_call - intercept methods without modifying their code
  • 🧩 Zero-coupling: Keep your business logic and cross-cutting concerns separate
  • 🔍 Selective targeting: Apply callbacks to specific methods or all methods
  • Performance-focused: Minimal overhead through efficient method wrapping
  • 🔒 Thread-safe: Safely use in concurrent applications
  • 📝 Conditional execution: Run callbacks only when specific conditions are met

Requirements

  • Ruby >= 3.3.0

Installation

Add this line to your application's Gemfile:

  gem 'solid_callback'

And then execute:

  bundle install

Or install it yourself:

  gem install solid_callback

Usage

Basic Example

require 'solid_callback'

class UserService
  include SolidCallback
  
  before_call :authenticate
  after_call :log_activity
  around_call :measure_performance
  
  def find_user(id)
    puts "Finding user with ID: #{id}"
    { id: id, name: "User #{id}" }
  end
  
  def update_user(id, attributes)
    puts "Updating user #{id} with #{attributes}"
    { id: id, updated: true }
  end
  
  private
  
  def authenticate
    puts "🔐 Authenticating request"
  end
  
  def log_activity
    puts "📝 Logging activity"
  end
  
  def measure_performance
    start_time = Time.now
    result = yield  # Execute the original method
    duration = Time.now - start_time
    puts "⏱️ Method took #{duration} seconds"
    result  # Return the original result
  end
end

service = UserService.new
service.find_user(42)

Advanced Usage

Apply callbacks to specific methods:

class PaymentProcessor
  include SolidCallback
  
  before_call :validate_amount, only: [:charge, :refund]
  after_call :send_receipt, only: [:charge]
  after_call :notify_fraud_department, only: [:flag_suspicious]
  around_call :transaction, only: [:charge, :refund]
  
  # Rest of class...
end

Use conditional callbacks:

class DocumentProcessor
  include SolidCallback
  
  attr_reader :document_size
  
  before_call :check_permissions
  before_call :backup_document, if: :large_document?
  around_call :with_retry, unless: :read_only?
  
  def process_document(doc)
    # Implementation...
  end
  
  private
  
  def large_document?
    @document_size > 10_000
  end
  
  def read_only?
    # Some condition
  end
end

You can even use procs for conditions:

before_call :notify_admin, if: -> { Rails.env.production? }

Skipping Callbacks

Use except: to exclude specific methods from a callback:

class ApiService
  include SolidCallback
  
  before_call :rate_limit, except: [:health_check]
  
  def get_data
    # Implementation...
  end
  
  def health_check
    # This method won't trigger the rate_limit callback
    { status: "ok" }
  end
end

Note: skip_callbacks_for is also available on the class, but it currently only records the given method names — it does not yet suppress callback execution. Prefer except: (as above) until that's wired up.

Callback Options

Each callback method accepts the following options:

Option Description
only Array of method names to which the callback applies
except Array of method names to which the callback does not apply
if Symbol (method name) or Proc that must return true for the callback to run
unless Symbol (method name) or Proc that must return false for the callback to run

How It Works

SolidCallback uses Ruby's metaprogramming to wrap your methods with callback functionality:

  1. include SolidCallback extends your class with the callback registration API and hooks into method_added so newly defined methods can be wrapped automatically.
  2. before_call, after_call, and around_call just store the callback configuration (method name, only/except/if/unless) on the class — they don't need to run before the target method is defined.
  3. Each time a public instance method is defined, it's aliased to _solid_callback_original_<method> and replaced with a wrapper that runs the applicable before callbacks, then the around chain (which ultimately calls the original method), then the applicable after callbacks. Private/protected methods are left untouched.
  4. Applicability (only, except, if, unless) is evaluated per call, against the actual instance, so the same callback can behave differently per invocation.
  5. around_call callbacks are nested in registration order (the first one registered is outermost) and must yield to continue the chain, much like Rack middleware.
  6. If a method needs wrapping outside the normal method_added flow, call wrap_methods(:method_name, ...) explicitly.

📚 Use Cases

  • Authentication & Authorization
  • Logging & Monitoring
  • Caching
  • Performance measurement
  • Error handling
  • Background job retries
  • Transaction management
  • Input validation
  • Data transformation

🤝 Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/gklsan/solid_callback/issues.

📄 License

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