23

#inheritedclass 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.

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

4

9 回答 9

11

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

您可以跟踪直到找到类定义的结尾。我用我称之为的方法做到了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
于 2011-08-18T04:43:48.977 回答
9

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

Ruby 挂钩类定义的开头,而不是结尾,因为Class#inheritedb/c ruby​​ 类定义没有真正的结尾。它们可以随时重新打开。

几年前有一些关于添加 const_added 触发器的讨论,但还没有通过。 从马茨

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

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

你想用这个触发器做什么?可能有另一种方法可以做到这一点。

于 2009-04-26T13:59:38.623 回答
8

用于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
于 2016-01-01T19:26:33.270 回答
5

看看defined宝石。你可以这样做:

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

有一些方法可以避免这种情况,如果您愿意,我可以向您解释。

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

于 2011-08-17T16:28:02.200 回答
1

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

于 2009-04-26T15:05:59.900 回答
1

Rails 有一个subclasses方法,它的实现可能值得一看:

class Fruit; end

class Lemon < Fruit; end

Fruit.subclasses # => [Lemon]
于 2012-06-01T13:47:28.860 回答
1

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

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

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 甚至在类主体中的执行 entoer 之前就被调用了:

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
于 2019-05-09T17:11:49.397 回答
0

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

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

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

于 2011-06-10T17:38:46.463 回答
0

您可以在所有代码加载后存储要调用的块。

例如,如果您在 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
于 2021-10-28T16:32:24.860 回答