【问题标题】:Insert code into the beginning of each method of a class在类的每个方法的开头插入代码
【发布时间】:2016-07-30 12:24:31
【问题描述】:

如何在不实际手动插入的情况下动态轻松地将代码插入到类和子类的每个方法的开头?我想要宏之类的东西。

class C1
   def m1
     @i_am = __method__
   end

   def m2
      @i_am = __method__
   end
 end

这是我想避免重复的例子之一。

【问题讨论】:

  • 你的例子的意图不明确。
  • @sawa,不,不是。
  • Ramano,提问者无法确定他们的问题对读者来说是否清楚。我和@sawa 一起讨论这个。
  • @CarySwoveland,您错误地认为 SO 是唯一可以得到答案的地方。我完全不在乎你站在哪一边,不管是不是萨瓦的。
  • 请在提问前再查看一些问题。人们只会被标题误导

标签: ruby metaprogramming


【解决方案1】:

我最初误解了这个问题(但在下面的水平线之后留下了我的原始答案)。我相信以下可能是您正在寻找的。​​p>

class C1
  [:m1, :m2].each do |m|
    define_method(m) do |name|
      @i_am = __method__
      puts "I'm #{name} from method #{@i_am}"
    end
  end
end

C1.instance_methods(false)
  #=> [:m1, :m2] 
c1 = C1.new
  #=> #<C1:0x007f94a10c0b60> 
c1.m1 "Bob"
  # I'm Bob from method m1
c1.m2 "Lucy"
  # I'm Lucy from method m2

我的原始解决方案如下。

class C1
  def add_code_to_beginning(meth)
    meth = meth.to_sym
    self.class.send(:alias_method, "old_#{meth}".to_sym, meth)
    self.class.send(:define_method, meth) do
      yield
      send("old_#{meth}".to_sym)
    end
  end
end

Module#alias_methodModule#define_method 是私有的;因此需要使用send

c = C1.new
  #=> #<C1:0x007ff5e3023650> 
C1.instance_methods(false)
  #=> [:m1, :m2, :add_code_to_beginning] 

c.add_code_to_beginning(:m1) do
  puts "hiya"
end

C1.instance_methods(false)
  #=> [:m1, :m2, :add_code_to_beginning, :old_m1] 
c.m1
  # hiya
  #=> :m1 

【讨论】:

  • 这比手动将相同的代码插入每个方法更容易吗?
  • 在类中创建一个新的公共方法,我不希望这样。
  • 您希望“动态地...将代码插入每个方法的开头...”。在 Ruby 中,“动态”意味着在运行时做一些像 C 这样的静态语言只能在编译时做的事情,比如创建一个方法。这只能通过一种方法来完成,这就是我在回答中给你的。我现在认为您只需要一种(非动态)方法来避免重复输入类似方法的乏味(这也可能减少错误)。我明天早上会做一个编辑来解决这个问题。
  • 我修改了我的代码以解决我目前对您的问题的理解。
【解决方案2】:

您可以使用类装饰器之类的导轨。下面的代码正在渲染一个名为before_action 的方法,该方法定义在 ActiveRecord 模块的 Base 类中。 Test 类继承自 ActiveRecord。如果我们想从 Base 类中显式调用某些东西,则使用 define_method

module ActiveRecord
    class Base

        def self.before_action(name)
            puts "#{name}"
            puts "inside before_action of class Base"

            define_method(name) do
                puts "Base: rendering code from Base class"
            end
        end
    end
end

class Test < ActiveRecord::Base

    before_action :hola

    def render()
        puts "inside render of class Test"
    end

end

test = Test.new
test.render
test.hola

有输出

hola
inside before_action of class Base
inside render of class Test
Base: rendering code from Base class

因此,在运行render 方法之前,它会运行Base 类中的before_action 方法。它可以应用于Test 类中的所有其他方法。这是在 ruby​​ 中表示宏的一种方式。

【讨论】:

    【解决方案3】:

    假设功能相同,您可以创建一个模块并将其包含在您的类中。

    例子:

    module MyModule
      def test_method
        puts "abc"
      end
    end
    
    class MyClass
      include MyModule
      def my_method
        puts "my method"
      end
    end
    
    inst = MyClass.new
    inst.test_method # => should print "abc"
    inst.my_method   # => should print "my method"
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-12-25
      • 1970-01-01
      • 2019-09-22
      • 2017-07-30
      • 2011-11-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多