21

Here's a code example:

class Foo
  def self.create_method
    def example_method
      "foo"
    end
  end

  private

  create_method
end

Foo.public_instance_methods(false) # => [:example_method]

Is it possible to detect that class method create_method was called from class Foo private area?

In the above example, that information could be used to make example_method public or private depending on the place where create_method was invoked from.

4 Answers4

3

Just to be sure, I double checked with ruby code, but I might be missing something. I couldn’t find any way to get the currently declared visibility scope from the class. As it appears if visibility methods (private, public or protected) are declared without method name argument it will set the current scope as a class wide declaration unless we declare other visibility scope on the subsequent statements.

You can check this code for further investigation - https://github.com/ruby/ruby/blob/c5c5e96643fd674cc44bf6c4f6edd965aa317c9e/vm_method.c#L1386

I couldn’t find any method which directly refers to cref->visi, You can check this code for the reference - https://github.com/ruby/ruby/blob/48cb7391190612c77375f924c1e202178f09f559/eval_intern.h#L236

Here is also the similar answer from one of the earliest post on Stackoverflow - https://stackoverflow.com/a/28055622/390132

So this is the simplified solution, I came up with -

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)}"
# []
Community
  • 1
  • 1
nhm tanveer
  • 682
  • 4
  • 10
2

Though it’s a bit hacky, it is possible:

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

Here we check the visibility of caller by case block. Please note, that you can’t simply move the case into another helper method without any modifications, since it relies on caller. Hope it helps.

Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
2

I wrote more unified solution, it's able to find out a visibility scope of any caller.

My main idea was to identify 2 things:

  • caller object(self binding of caller)
  • method name of caller object

I used binding_of_caller gem to achieve this.

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

Add some methods to test:

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

Test

# 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"
Alexander Karmes
  • 2,438
  • 1
  • 28
  • 34
0

Try https://github.com/ruby-prof/ruby-prof

There is feature :

Call tree profiles - outputs results in the calltree format suitable for the KCacheGrind profiling tool.

That might help you

Avdept
  • 2,261
  • 2
  • 26
  • 48