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

时间:2015-04-07 13:24:28

标签: ruby

这是一个代码示例:

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的位置。

4 个答案:

答案 0 :(得分: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。希望它有所帮助。

答案 1 :(得分: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"

答案 2 :(得分:2)

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

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

我找不到任何直接引用cref-&gt; 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)}"
# []

答案 3 :(得分:0)

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

有特色:

  

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

这可能会对你有所帮助