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
I have implemented that by adding custom_method function to the Module module, and defining the method with define_method, which works fine. But I cannot figure out how to take into account visibility attributes from the class. For example, in the following class
class Foo
  custom_method(:foo)
private
  custom_method(:bar)
end
the first generated method (foo) must be public, and the second one (bar) must be private. How do I do that? Or, how do I find the context in which my custom_method is called: private, public, or protected?
Thanks!
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With