这是一个代码示例:
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 代码,但我可能遗漏了一些东西。我找不到任何方法从类中获取当前声明的可见性范围。看起来,如果在没有方法名称参数的情况下声明可见性方法(私有、公共或受保护),它将把当前作用域设置为类范围声明,除非我们在后续语句中声明其他可见性作用域。
您可以检查此代码以进行进一步调查 - 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)}"
# []
虽然有点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
。希望有帮助。
我写了更统一的解决方案,它能够找出任何调用者的可见范围。
我的主要想法是确定两件事:
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"
尝试https://github.com/ruby-prof/ruby-prof
有一个特点:
调用树配置文件 - 以合适的调用树格式输出结果 用于 KCacheGrind 分析工具。
这可能对你有帮助
我为此让自己成为了一个助手,这可能对其他人有用。它仅用于调试,所以我只是将其打入
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