You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在纯Ruby类中实现类似Rails的before/after方法回调?

Awesome question! You don't need ActiveSupport to add Rails-style before/after callbacks to a plain Ruby class—you can build this yourself with a little metaprogramming magic. Let's break this down for your Espresso::MyExampleClass.

Step 1: Create a Reusable Callback Module

First, we'll build a Callbackable module that adds core callback functionality. This module lets you register before and after callbacks, and handles running them around your target methods.

module Callbackable
  def self.included(base)
    base.extend(ClassMethods)
    # Initialize storage for callbacks (auto-creates empty arrays for new methods)
    base.instance_variable_set(:@before_callbacks, Hash.new { |h, k| h[k] = [] })
    base.instance_variable_set(:@after_callbacks, Hash.new { |h, k| h[k] = [] })
  end

  module ClassMethods
    # Register a before callback for a target method
    def before(target_method, callback)
      @before_callbacks[target_method] << callback
      wrap_method_with_callbacks(target_method)
    end

    # Register an after callback for a target method
    def after(target_method, callback)
      @after_callbacks[target_method] << callback
      wrap_method_with_callbacks(target_method)
    end

    private

    # Wrap the original method to include callbacks (only runs once per method)
    def wrap_method_with_callbacks(method_name)
      return if method_defined?("#{method_name}_without_callbacks")

      # Alias the original method so we can still execute its logic
      alias_method "#{method_name}_without_callbacks", method_name

      # Define a new version of the method that runs callbacks
      define_method(method_name) do
        # Execute all registered before callbacks
        self.class.instance_variable_get(:@before_callbacks)[method_name].each do |callback|
          send(callback)
        end

        # Run the original method and capture its return value
        result = send("#{method_name}_without_callbacks")

        # Execute all registered after callbacks
        self.class.instance_variable_get(:@after_callbacks)[method_name].each do |callback|
          send(callback)
        end

        # Return the original method's result (matches Rails' behavior)
        result
      end
    end
  end
end

Step 2: Add Callbacks to Your Class

Include the Callbackable module in your Espresso::MyExampleClass, then use the before macro to attach your callback to my_first_function:

module Espresso
  class MyExampleClass
    include Callbackable

    # Your callback method to run before my_first_function
    def my_function_to_run_before
      puts "🔧 Running setup tasks before my_first_function!"
    end

    # Attach the before callback to your target method
    before :my_first_function, :my_function_to_run_before

    # The main method you want to wrap with callbacks
    def my_first_function
      puts "🚀 Executing my_first_function!"
      "Operation completed successfully" # Example return value
    end

    # Bonus: Add an after callback example
    def cleanup_after_task
      puts "🧹 Running cleanup after my_second_function!"
    end

    after :my_second_function, :cleanup_after_task

    def my_second_function
      puts "🛠️ Executing my_second_function!"
    end
  end
end

Step 3: Test the Callbacks

When you call your methods, the callbacks will run automatically in the correct order:

instance = Espresso::MyExampleClass.new

# Test the before callback
result = instance.my_first_function
# Output:
# 🔧 Running setup tasks before my_first_function!
# 🚀 Executing my_first_function!
puts result # => "Operation completed successfully" (preserves original return value)

# Test the after callback
instance.my_second_function
# Output:
# 🛠️ Executing my_second_function!
# 🧹 Running cleanup after my_second_function!

Bonus: Advanced Features

Want to expand this functionality? Here are two quick additions:

Multiple Callbacks per Method

You can register multiple callbacks for the same method—they'll run in the order you define them:

before :my_first_function, :additional_setup

def additional_setup
  puts "🔧 Running a second setup task!"
end

Block-Based Callbacks

Modify the before/after methods in ClassMethods to support inline block callbacks:

def before(target_method, callback = nil, &block)
  if block_given?
    @before_callbacks[target_method] << block
  else
    @before_callbacks[target_method] << callback
  end
  wrap_method_with_callbacks(target_method)
end

# Usage example:
before :my_third_function do
  puts "🔧 Inline before callback logic!"
end

def my_third_function
  puts "🚀 Executing my_third_function!"
end

How It Works

  • The Callbackable module uses Ruby's metaprogramming tools (included, alias_method, define_method) to wrap your original methods without modifying their core logic.
  • Callbacks are stored in class-level hashes, organized by the target method name.
  • When you register a callback, we alias the original method and define a new version that runs the callbacks before/after executing the original code.

This approach mirrors Rails' callback behavior while keeping everything in pure Ruby—no external dependencies required!

内容的提问来源于stack exchange,提问作者DogEatDog

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 09:14:03