检测类方法中新方法的可见性

问题描述 投票:0回答:5

这是一个代码示例:

class Foo
  def self.create_method
    def example_method
      "foo"
    end
  end

  private

  create_method
end

Foo.public_instance_methods(false) # => [:example_method]

是否可以检测到类方法

create_method
是从类
Foo
私有区域调用的?

在上面的示例中,该信息可用于使

example_method
公开或私有,具体取决于调用
create_method
的位置。

ruby
5个回答
3
投票

为了确定,我仔细检查了 ruby 代码,但我可能遗漏了一些东西。我找不到任何方法从类中获取当前声明的可见性范围。看起来,如果在没有方法名称参数的情况下声明可见性方法(私有、公共或受保护),它将把当前作用域设置为类范围声明,除非我们在后续语句中声明其他可见性作用域。

您可以检查此代码以进行进一步调查 - https://github.com/ruby/ruby/blob/c5c5e96643fd674cc44bf6c4f6edd965aa317c9e/vm_method.c#L1386

我找不到任何直接引用cref->visi的方法,您可以查看此代码以供参考 - https://github.com/ruby/ruby/blob/48cb7391190612c77375f924c1e202178f09f559/eval_intern.h#L236

这也是 Stackoverflow 上最早的帖子之一的类似答案 - https://stackoverflow.com/a/28055622/390132

所以这是简化的解决方案,我想出了 -

class Foo
  def self.create_method
    def example_method
      "foo"
    end

    visibility = if self.private_method_defined? :test_method
                   :private
                 elsif self.public_method_defined? :test_method
                   :public
                 elsif self.protected_method_defined? :test_method
                   :protected
                 end

    send visibility, :example_method
  end

  private

  # As Ruby doesn't associate visibility flag along with the caller
  # reference rather with the actual method which are subsequently
  # declared. So we can take that as an advantage and create a test method
  # and later from :create_method scope check that particular method
  # visibility and change the generated method visibility accordingly.
  # Create a test method to verify the actual visibility when calling 'create_method' method
  def test_method; end

  create_method
end

puts "Public methods: #{Foo.public_instance_methods(false)}"
# []
puts "Private methods: #{Foo.private_instance_methods(false)}"
# [:test_method, :example_method]
puts "Protected methods: #{Foo.protected_instance_methods(false)}"
# []

2
投票

虽然有点hacky,但这是可能的:

class Foo
  def self.create_method
    define_method :example_method do
      visibility =  case caller(0).first[/block in (\w+)'/, 1].to_sym
                    when ->(m) { Foo.private_methods.include? m }
                      :private
                    when ->(m) { Foo.protected_methods.include? m }
                      :protected
                    when ->(m) { Foo.public_methods.include? m } 
                      :public
                    else :unknown
                    end
      puts "Visibility: #{visibility}"
    end
  end

  private_class_method :create_method
end

Foo.send :create_method
Foo.new.example_method

#⇒ Visibility: private

这里我们通过

case
块检查调用者的可见性。请注意,您不能简单地将案例移至另一个辅助方法而不进行任何修改,因为它依赖于
caller
。希望有帮助。


2
投票

我写了更统一的解决方案,它能够找出任何调用者的可见范围。

我的主要想法是确定两件事:

  • 调用者对象(
    self
    调用者绑定)
  • 调用者对象的方法名称

我使用 binding_of_caller gem 来实现这一点。

class Foo
  class << self
    def visibility_scope
      binding_of_caller = binding.of_caller(1)
      caller_method = binding_of_caller.eval('__method__')
      caller_object = binding_of_caller.eval('self')

      # It's asking if caller is a module, since Class is inherited from Module
      if caller_object.is_a?(Module)
        return visibility_scope_for(caller_object.singleton_class, caller_method)
      end


      # First we should check object.singleton_class, since methods from there are called before
      #   class instance methods from object.class
      visibility = visibility_scope_for(caller_object.singleton_class, caller_method)
      return visibility if visibility

      # Then we check instance methods, that are stored in object.class
      visibility = visibility_scope_for(caller_object.class, caller_method)
      return visibility if visibility

      fail 'Visibility is undefined'
    end

    private

    def visibility_scope_for(object, method_name)
      %w(public protected private).each do |scope|
        if object.send("#{scope}_method_defined?", method_name)
          return scope
        end
      end
      nil
    end
  end
end

添加一些方法进行测试:

class Foo
  class << self
    # This method is private in instance and public in class
    def twin_method
      visibility_scope
    end

    def class_public_method
      visibility_scope
    end

    protected

    def class_protected_method
      visibility_scope
    end

    private

    def class_private_method
      visibility_scope
    end
  end

  def instance_public_method
    self.class.visibility_scope
  end

  protected

  def instance_protected_method
    self.class.visibility_scope
  end

  private

  def twin_method
    self.class.visibility_scope
  end

  def instance_private_method
    self.class.visibility_scope
  end
end

# singleton methods
foo = Foo.new
foo.singleton_class.class_eval do 
  def public_singleton_method
    Foo.visibility_scope
  end

  protected

  def protected_singleton_method
    Foo.visibility_scope
  end

  private

  def private_singleton_method
    Foo.visibility_scope
  end
end

class Bar
  class << self
    private

    def class_private_method
      Foo.visibility_scope
    end
  end

  protected

  def instance_protected_method
    Foo.visibility_scope
  end
end

测试

# check ordinary method
Foo.class_public_method
 => "public"
Foo.send(:class_protected_method)
 => "protected"
Foo.send(:class_private_method)
 => "private"
Foo.new.instance_public_method
 => "public"
Foo.new.send(:instance_protected_method)
 => "protected"
Foo.new.send(:instance_private_method)
 => "private"

# check class and instance methods with the same name
Foo.twin_method
 => "public"
Foo.new.send(:twin_method)
 => "private"

# check methods from different objects
Bar.send(:class_private_method)
 => "private"
Bar.new.send(:instance_protected_method)
 => "protected"

# check singleton methods
foo.public_singleton_method
 => "public"
foo.send(:protected_singleton_method)
 => "protected"
foo.send(:private_singleton_method)
 => "private"

0
投票

尝试https://github.com/ruby-prof/ruby-prof

有一个特点:

调用树配置文件 - 以合适的调用树格式输出结果 用于 KCacheGrind 分析工具。

这可能对你有帮助


0
投票

我为此让自己成为了一个助手,这可能对其他人有用。它仅用于调试,所以我只是将其打入

Method
上的猴子补丁中:

class UnboundMethod
  def visibility
    if owner.private_method_defined?(name)      then :private
    elsif owner.protected_method_defined?(name) then :protected
    elsif owner.public_method_defined?(name)    then :public
    else raise
    end
  end
end

class Method
  def visibility = unbind.visibility
end

p Object.method(:new).visibility # => :public
p Object.instance_method(:initialize).visibility # => :private
© www.soinside.com 2019 - 2024. All rights reserved.