2015-04-07 16 views
20

Вот пример кода:Обнаружение нового видимости методы в методе класса

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.

ответ

0

Попробуйте https://github.com/ruby-prof/ruby-prof

Существует особенность:

вызовов дерево профилей - выводит результаты в формате calltree подходящего для инструмента профилирования KCacheGrind.

Это может помочь вам

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

Я написал больше унифицированного решения, он может узнать область видимости любого вызывающего абонента.

Моя основная идея состояла в том, чтобы определить 2 вещи:

  • объекта вызывающего абонента (self связывание вызывающего абонента) имя
  • метод объекта вызывающего

Я использовал binding_of_caller камень для достижения этой цели.

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

Наконец-то я нашел способ сделать это и изменил свой ответ –

1

Просто, чтобы быть уверенным, я проверил с рубином кодом, но я мог бы быть что-то отсутствует. Я не смог найти способ получить текущую область видимости из класса. Как представляется, если методы видимости (частные, общедоступные или защищенные) объявляются без аргумента имени метода, он будет устанавливать текущую область действия как объявление класса, если мы не объявим другую видимость для последующих операторов.

Вы можете проверить этот код для дальнейшего исследования - 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)}" 
# [] 
Смежные вопросы