Подтвердить что ты не робот

Обнаруживать видимость нового метода в методе класса

Вот пример кода:

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.

4b9b3361

Ответ 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.

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