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

如何在Ruby中为所有公开类方法「魔法般」注入代码?

Elegant Method Injection for Ruby Classes

Your existing code gets the job done, but injecting code during instance initialization feels a bit clunky—plus it runs redundant logic every time a new instance is created. Let's refactor this to use clean, attr_accessor-style syntax, and even support injecting code into all public methods with a single flag.

Solution 1: Custom Class Method with alias_method

We'll create a reusable class method that lets you specify which methods to wrap directly in the class definition. This avoids per-instance setup and keeps your code organized.

module MethodInjection
  # Class method to define wrapped methods
  def attr_add_code(*methods)
    # Handle the :all flag to target all public instance methods defined in this class
    if methods.include?(:all)
      methods = public_instance_methods(false) # `false` excludes inherited methods
    end

    methods.each do |meth|
      meth = meth.to_sym
      # Alias the original method before redefining it
      alias_method "original_#{meth}".to_sym, meth

      # Redefine the method to wrap the original with our timing logic
      define_method(meth) do
        elapsed do
          send("original_#{meth}".to_sym)
        end
      end
    end
  end

  private

  # Reusable timing logic (inherited by including classes)
  def elapsed
    start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
    block_value = yield
    finish = Process.clock_gettime(Process::CLOCK_MONOTONIC)
    puts "elapsed: #{finish - start} seconds, block_value: #{block_value}."
    block_value
  end
end

class MyClass
  include MethodInjection

  # Use our custom syntax to specify methods to wrap
  attr_add_code :a, :b
  # Or wrap all public methods: attr_add_code :all

  def a
    sleep 1
    "returning from a"
  end

  def b
    sleep 1
    "returning from b"
  end
end

Why this works better:

  • Clean syntax: Just like attr_accessor, you declare which methods to wrap at the top of your class.
  • One-time setup: Injection happens once during class definition, not every time you create an instance.
  • Safe targeting: public_instance_methods(false) ensures we only wrap methods defined directly in MyClass, not inherited ones like to_s or inspect.

Solution 2: Using prepend (Ruby's Preferred Method Injection)

For even more robustness, we can use Ruby's prepend feature, which inserts a module into the method lookup chain before the class itself. This avoids managing aliases and plays nicer with other metaprogramming.

module MethodInjection
  def attr_add_code(*methods)
    if methods.include?(:all)
      methods = public_instance_methods(false)
    end

    # Dynamically create an injection module with wrapped methods
    injection_module = Module.new do
      methods.each do |meth|
        define_method(meth) do
          elapsed { super() } # `super` calls the original class method directly
        end
      end

      private

      def elapsed
        start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
        block_value = yield
        finish = Process.clock_gettime(Process::CLOCK_MONOTONIC)
        puts "elapsed: #{finish - start} seconds, block_value: #{block_value}."
        block_value
      end
    end

    # Prepend the module to the class so our wrapped methods run first
    prepend injection_module
  end
end

class MyClass
  extend MethodInjection # Use extend since we're adding a class method

  attr_add_code :a, :b
  # Or: attr_add_code :all

  def a
    sleep 1
    "returning from a"
  end

  def b
    sleep 1
    "returning from b"
  end
end

Benefits of this approach:

  • No alias management: super directly references the original method, so we don't have to track original_* aliases.
  • Better compatibility: prepend is the standard Ruby way for method wrapping, and it works seamlessly with other modules or inheritance.
  • Cleaner separation: The injection logic is encapsulated in a dynamic module, keeping your class code uncluttered.

Both solutions let you achieve your goal of clean, non-repetitive method injection. The prepend approach is generally preferred for modern Ruby code due to its robustness.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 09:07:53