Determining method's visibility on the fly

后端 未结 3 1669
执笔经年
执笔经年 2021-01-19 02:45

I am writing a method that will define an instance method inside a class; something similar to attr_accessor:

class Foo
  custom_method(:foo)
end


        
相关标签:
3条回答
  • 2021-01-19 03:01

    You can use Module#private_method_defined? to verify if a method is defined as private

    0 讨论(0)
  • 2021-01-19 03:02

    After experimenting with this for a bit, I'm completely baffled. Initially, I thought that Ruby took the default visibility (public, private, or protected) into account when you call Module#define_method. It turns out though that on Ruby versions <= 2.0, that's not the case:

    class Foo
      private
      define_method :foo do
        puts "Foo called!"
      end
    end
    
    Foo.new.foo # Prints "Foo called!"
    

    On Ruby 2.1+, it's even more confusing. Module#define_method seems to take default method visibility into account:

    class Foo
      private
      define_method :foo do
        puts "Foo called!"
      end
    end
    
    Foo.new.foo # NoMethodError: private method `foo' called for #<Foo:0x8cb75ac>
    

    But it only works when you are calling define_method from directly inside the class. Calling a method which then calls define_method doesn't work:

    class Foo
      def self.hello_on name
        define_method name do
          puts "Hello, #{name}!"
        end
      end
    
      private
      hello_on :foo
    end
    
    Foo.new.foo # Prints "Hello, foo!"
    

    Dang it Ruby! Why?

    Okay, this calls for desperate measures...

    module DefaultMethodVisibilityAccessor
      attr_reader :current_default_method_visibility
    
      def public(*args)
        @current_default_method_visibility = :public if args.empty?
        super
      end
      def protected(*args)
        @current_default_method_visibility = :protected if args.empty?
        super
      end
      def private(*args)
        @current_default_method_visibility = :private if args.empty?
        super
      end
    end
    
    class Module
      prepend DefaultMethodVisibilityAccessor
    end
    
    module MethodDefiner
      def hello_on name
        define_method name do
          puts "Hello, #{name}!"
        end
    
        case current_default_method_visibility
        when :public
          public name
        when :protected
          protected name
        when :private
          private name
        end
      end
    end
    

    Usage:

    class Foo
      extend MethodDefiner
      hello_on :foo
      private
      hello_on :bar
    end
    
    Foo.new.foo # Prints "Hello, foo!"
    Foo.new.bar # NoMethodError: private method `bar' called for #<Foo:0x8ec18fc>
    

    There, fixed!

    0 讨论(0)
  • 2021-01-19 03:26

    I think this is impossible, because the scope visibility level set by Module.private is managed at the C virtual machine level and not exposed to Ruby.

    EDIT: and it's only available in the same syntactical scope that it is called, so when you call custom_method it loses the visibility level set inside the class declaration.

    It's set in set_visibility(), and used in vm_define_method(), but I can't find any reference to the corresponding variable being available from Ruby.

    I suggest using some kind of custom parameter to specify the visibility level of your methods.

    0 讨论(0)
提交回复
热议问题