【问题标题】:Is there a hook similar to Class#inherited that's triggered only after a Ruby class definition?是否有类似于 Class#inherited 的钩子仅在 Ruby 类定义后触发?
【发布时间】:2010-10-21 21:30:00
【问题描述】:

#inherited 在class Foo 语句之后被调用。我想要只在关闭类声明的end 语句之后运行的东西。

这里有一些代码来举例说明我需要什么:

class Class
  def inherited m
    puts "In #inherited for #{m}"
  end
end

class Foo
  puts "In Foo"
end
puts "I really wanted to have #inherited tiggered here."


### Output:
# In #inherited for Foo
# In Foo
# I really wanted to have #inherited tiggered here.

这样的东西存在吗?可以创建吗?我完全不走运吗?

【问题讨论】:

  • 我的第一个想法是,可能有更好的方法来实现您心目中的功能。当然,如果没有更多信息,很难肯定地说,我怀疑你已经继续前进了。
  • 现在可以使用TracePoint 轻松完成此操作。请参阅下面我的回答,或我对这个类似问题的回答:stackoverflow.com/questions/32233860/…

标签: ruby class inheritance metaprogramming


【解决方案1】:

您可以存储一个块,以便在所有代码加载后调用。

例如,如果您在 Rails 中:

在初始化器中:

module AfterInitialize
  extend self

  @@callbacks = []

  def add(&block)
    @@callbacks << block
  end

  def run
    @@callbacks.each(&:call)
  end
end

在application.rb:

config.after_initialize { AfterInitialize.run }

然后

class Parent
  def self.inherited(subclass)
    p "In inherited for #{subclass} before initialize"

    AfterInitialize.add do
      p "In inherited for #{subclass} after initialize"
    end
  end
end

和

class Child
  p "In Child"
end

【讨论】:

    【解决方案2】:

    使用TracePoint 跟踪您的班级何时发送:end 事件。

    这个模块可以让你在任何类中创建一个self.finalize 回调。

    module Finalize
      def self.extended(obj)
        TracePoint.trace(:end) do |t|
          if obj == t.self
            obj.finalize
            t.disable
          end
        end
      end
    end
    

    现在您可以扩展您的类并定义self.finalize,它将在类定义结束后立即运行:

    class Foo
      puts "Top of class"
    
      extend Finalize
    
      def self.finalize
        puts "Finalizing #{self}"
      end
    
      puts "Bottom of class"
    end
    
    puts "Outside class"
    
    # output:
    #   Top of class
    #   Bottom of class
    #   Finalizing Foo
    #   Outside class
    

    【讨论】:

    • TracePoint 没有用于 jruby 的方法 trace
    • @МалъСкрылевъ 拨打tp = TracePoint.new(:end) do |t| ...,然后直接拨打tp.enable。 TracePoint.trace 只是将这两个动作结合在一起的一种方便方法。
    【解决方案3】:

    不,据我所知没有这样的钩子,但好在你可以自己做。这是一个可能的实现:

    不是超级干净,但它有效:

    puts RUBY_VERSION # 2.4.1
    
    class Father
      def self.engage_super_setup(sub)
        puts "self:#{self} sub:#{sub}"
        sub.class_eval do
          puts "toy:#{@toy}"
        end
      end
    
      def self.super_setup
        if self.superclass.singleton_methods.include?(:engage_super_setup)
          superclass.engage_super_setup(self)
        end
      end
    end
    
    Son = Class.new(Father) do
      @toy = 'ball'
    end.tap { |new_class| new_class.super_setup } # this is needed to:
    # 1. call the super_setup method in the new class.
    # 2. we use tap to return the new Class, so this class is assigned to the Son constant.
    
    puts Son.name # Son
    
    

    输出:

    self:Father sub:#<Class:0x0055d5ab44c038> #here the subclass is still anonymous since it was not yet assigned to the constant "Son"
    toy:ball # here we can see we have acess to the @toy instance variable in Son but from the :engage_super_setup in the Father class
    Son # the of the class has been assigned after the constant, since ruby does this automatically when a class is assigned to a constant 
    

    所以这显然不如钩子干净,但我认为最后我们有一个相当不错的结果。

    如果我们试图对 :inherited 做同样的事情,很遗憾是不可能的,因为 :inherited 甚至在类主体中的执行进入之前就被调用了:

    puts RUBY_VERSION # 2.4.1
    
    class Father
      def self.inherited(sub)
        puts "self:#{self} sub:#{sub}"
        sub.class_eval do
          puts "toy:#{@toy.inspect}"
        end
      end
    
    end
    
    class Son < Father
      puts "we are in the body of Son"
      @toy = 'ball'
    end
    
    puts Son.name # Son
    

    输出:

    self:Father sub:Son # as you can see here the hook is executed before the body of the declaration Son class runs
    toy:nil # we dont have access yet to the instance variables
    we are in the body of Son # the body of the class declaration begins to run after the :inherited hook.
    Son
    

    【讨论】:

      【解决方案4】:

      Rails 有一个subclasses 方法,它可能值得研究一下实现:

      class Fruit; end
      
      class Lemon < Fruit; end
      
      Fruit.subclasses # => [Lemon]
      

      【讨论】:

        【解决方案5】:

        我迟到了,但我想我有一个答案(任何访问这里的人)。

        您可以跟踪直到找到类定义的结尾。我用我称之为after_inherited的方法做到了:

        class Class
          def after_inherited child = nil, &blk
            line_class = nil
            set_trace_func(lambda do |event, file, line, id, binding, classname|
              unless line_class
                # save the line of the inherited class entry
                line_class = line if event == 'class'
              else
                # check the end of inherited class
                if line == line_class && event == 'end'
                  # if so, turn off the trace and call the block
                  set_trace_func nil
                  blk.call child
                end
              end
            end)
          end
        end
        
        # testing...
        
        class A
          def self.inherited(child)
            after_inherited do
              puts "XXX"
            end
          end
        end
        
        class B < A
          puts "YYY"
          # .... code here can include class << self, etc.
        end
        

        输出:

        YYY
        XXX
        

        【讨论】:

        • 这让我不寒而栗。 :-) 干得好,非常奇怪而且非常可怕,但是对于正确的问题......轴。
        • 这不再起作用了.. 不是那么好.. 这是一个惊人的 TracePoint 线索......在这里查看我的问题。 stackoverflow.com/questions/28754070/…
        • set_trace_func 不再有效,请参阅solution using TracePoint
        【解决方案6】:

        看看defined gem。你可以这样做:

        require "defined"
        Defined.enable!
        
        class A
          def self.after_inherited(child)
            puts "A was inherited by #{child}"
          end
        
          def self.defined(*args)
            superclass.after_inherited(self) if superclass.respond_to?(:after_inherited)
          end
        end
        
        class B < A
          puts "B was defined"
        end
        

        输出:

        B was defined
        A was inherited by B
        

        但是self.defined 将在每个类定义后被触发。所以如果你添加以下代码

        class B < A
          puts "B was redefined"
        end
        

        你会看到

        B was defined
        A was inherited by B
        B was redefined
        A was inherited by B
        

        有一些方法可以避免,如果你愿意,我可以向你解释。

        但是,如上所述,可能有更好的方法来解决您的问题。

        【讨论】:

        • 顺便说一句,Ruby 太酷了,我们不需要 Matz 批准来扩展语言 :-)
        • 这会在整个 ruby​​ 代码中执行的每一行之后调用一个方法。我想会大大降低性能
        • @Dominik,你是什么意思? “继承”只在类定义后调用一次。
        • 我的意思是定义的 gem 开始跟踪每个执行的 ruby​​ 代码行,以找出一个类何时重新打开和关闭。此跟踪函数在 Defined.enable 之后在代码中的每个 ruby​​ 行上执行!线。看一下定义好的gem源码
        【解决方案7】:

        在尝试向所有模型自动添加通用验证时,我遇到了同样的问题。问题是,如果模型使用了#set_table_name,那么我添加基于数据库数据类型的验证的代码将会崩溃,因为它根据模型的名称猜测表名(因为 #inherited 被调用#set_table_name 之前)。

        所以就像你一样,我真的在寻找一种方法来让#inherited 在模型中的所有内容都已加载后触发。但我真的不需要走那么远,我所需要的只是在#set_table_name 之后触发的东西。所以它原来是一个简单的别名方法。你可以看到我在这里所做的一个例子: https://gist.github.com/1019294

        在上面的评论中,您说“我正在尝试向 activerecord 模型添加行为,但我需要在搞砸之前完成所有模型自定义”。所以我问你的问题是,如果有你关心的特定模型自定义,那么也许你可以使用别名方法来实现你想要的结果。

        【讨论】:

          【解决方案8】:

          如果您愿意假设您的 Ruby 实现了 ObjectSpaces,您可以在事后查找所有模型实例,然后适当地修改它们。谷歌建议http://phrogz.net/ProgrammingRuby/ospace.html

          【讨论】:

          • 我怀疑 Rails 的类重载不会那么顺利。
          【解决方案9】:

          你可能不走运。但这只是一个警告,而不是确定的答案。

          Ruby 钩住类定义的开头,而不是结尾,因为Class#inherited b/c ruby​​ 类定义没有真正的结尾。他们能 随时重新开放。

          有some talk a couple years ago about adding a const_added trigger,但还没有通过。 From Matz:

          我不会实现所有可能的钩子。所以当有人 有更具体的用法,我会再考虑一下。它会 是const_added,而不是class_added。

          所以这可能会处理你的情况 - 但我不确定(它也可能在开始时触发,当它最终实施时)。

          你想用这个触发器做什么?可能还有其他方法可以做到。

          【讨论】:

          • 我正在尝试向 activerecord 模型添加行为,但我需要在搞砸之前完成所有模型自定义。现在,我只是在模型中设置了我需要的所有内容的位置手动包含扩展模块。
          • 我相信 const_added 也会在创建 const 时触发。具有不同标准语法和元编程接口的 ruby​​ 部分总是让我失望。像 def/define_method 一样。如果class Foo; end 只是 Class.new(superclass, &block) 的语法糖,其中该块将被传递给 class_instance#instance_eval ,则可以只包装 Class#initialize 方法。哦,好吧,另一个星期一,另一个我考虑跳到 Lisp 的日子。
          猜你喜欢
          • 2022-01-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-03-12
          • 2010-11-27
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多