Обнаруживать видимость нового метода в методе класса
Вот пример кода:
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
private area?
В приведенном выше примере эта информация может использоваться для того, чтобы сделать example_method
общедоступным или частным в зависимости от места, где был вызван create_method
.
Ответы
Ответ 1
Хотя он немного взломан, возможно:
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
Я написал больше унифицированного решения, чтобы узнать область видимости любого вызывающего.
Моя основная идея состояла в том, чтобы определить 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 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"
Ответ 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 -
fooobar.com/questions/264169/...
Итак, это упрощенное решение, я придумал -
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)}"
# []
Ответ 4
Попробуйте https://github.com/ruby-prof/ruby-prof
Есть функция:
Профиль дерева звонков - вывод результатов в формате calltree для инструмента профилирования KCacheGrind.
Это может помочь вам