如何在纯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
Callbackablemodule 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

