当前的Ruby方法是通过super调用的吗?

时间:2016-01-12 06:23:26

标签: ruby metaprogramming subclass super ruby-2.3

在运行时的方法中,有没有办法知道该方法是否已通过子类中的super调用? E.g。

module SuperDetector
  def via_super?
    # what goes here?
  end
end

class Foo
  include SuperDetector

  def bar
    via_super? ? 'super!' : 'nothing special'
  end
end

class Fu < Foo
  def bar
    super
  end
end

Foo.new.bar # => "nothing special"
Fu.new.bar  # => "super!"

我如何撰写via_super?,或者,如有必要,via_super?(:bar)

5 个答案:

答案 0 :(得分:4)

可能有更好的方法,但一般的想法是Object#instance_of?仅限于当前的类,而不是层次结构:

module SuperDetector
  def self.included(clazz)
    clazz.send(:define_method, :via_super?) do
      !self.instance_of?(clazz)
    end
  end
end

class Foo
  include SuperDetector

  def bar
    via_super? ? 'super!' : 'nothing special'
  end
end

class Fu < Foo
  def bar
    super
  end
end

Foo.new.bar # => "nothing special"
Fu.new.bar  # => "super!"

<小时/> 但请注意,这并不要求孩子明确super。如果孩子没有这样的方法且使用了父母的方法,via_super?仍会返回true。我不认为除了检查堆栈跟踪或代码本身之外,还有一种方法只能捕获super个案。

答案 1 :(得分:3)

优秀的@ndn方法的附录:

module SuperDetector
  def self.included(clazz)
    clazz.send(:define_method, :via_super?) do
      self.ancestors[1..-1].include?(clazz) &&
        caller.take(2).map { |m| m[/(?<=`).*?(?=')/] }.reduce(&:==)
        # or, as by @ndn: caller_locations.take(2).map(&:label).reduce(&:==)
    end unless clazz.instance_methods.include? :via_super?
  end
end

class Foo
  include SuperDetector

  def bar
    via_super? ? 'super!' : 'nothing special'
  end
end

class Fu < Foo
  def bar
    super
  end
end

puts Foo.new.bar # => "nothing special"
puts Fu.new.bar # => "super!"

这里我们使用Kernel#caller来确保所调用方法的名称与超类中的名称匹配。如果不是直接后代(caller(2)应该更改为更复杂的分析),这种方法可能需要一些额外的调整,但你可能会明白这一点。

UPD 感谢@Stefan对其他答案的评论,并使用unless defined进行了更新,以便在FooFu {{1}时同时使用}。

UPD2 使用祖先来检查超级而非直接比较。

答案 2 :(得分:3)

这是一种更简单(几乎无关紧要)的方法,但您必须同时传递当前类和方法名称:(我还将方法名称从via_super?更改为called_via?

module CallDetector
  def called_via?(klass, sym)
    klass == method(sym).owner
  end
end

使用示例:

class A
  include CallDetector

  def foo
    called_via?(A, :foo) ? 'nothing special' : 'super!'
  end
end

class B < A
  def foo
    super
  end
end

class C < A
end

A.new.foo # => "nothing special"
B.new.foo # => "super!"
C.new.foo # => "nothing special"

答案 3 :(得分:2)

修改改进,遵循Stefan的建议。

module SuperDetector
  def via_super?
    m0, m1 = caller_locations[0].base_label, caller_locations[1]&.base_label
    m0 == m1 and
    (method(m0).owner rescue nil) == (method(m1).owner rescue nil)
  end
end

答案 4 :(得分:1)

my other@mudasobwa's@sawa's答案以及递归支持之间的最终混合:

module SuperDetector
  def self.included(clazz)
    unless clazz.instance_methods.include?(:via_super?)
      clazz.send(:define_method, :via_super?) do
        first_caller_location = caller_locations.first
        calling_method = first_caller_location.base_label

        same_origin = ->(other_location) do
          first_caller_location.lineno == other_location.lineno and
            first_caller_location.absolute_path == other_location.absolute_path
        end

        location_changed = false
        same_name_stack = caller_locations.take_while do |location|
          should_take = location.base_label == calling_method and !location_changed
          location_changed = !same_origin.call(location)
          should_take
        end

        self.kind_of?(clazz) and !same_origin.call(same_name_stack.last)
      end
    end
  end
end

唯一不起作用的情况(AFAIK)就是你在基类中有间接递归,但我没有想法如何用解析代码来处理它。

相关问题