简体   繁体   中英

Calling protected class method from instance method in Ruby

I've been having this bothering recurring theme; let's just say, I have a class which defines an instance method and a protected class method. The instance method must call the class method. In order to do so, I kind of have to break the visibility rule and use the dangerous 'send' function. Something like this:

class Bang
    def instance_bang
      self.class.send(:class_band)
    end

    protected
    def self.class_bang
      puts "bang"
    end
end

I find this awful, since the class method should be used inside the class scope, therefore should remain visible and callable within it, right? Is there an alternative way to use class methods in instance methods with needing to rely on the "send" function and therefore not break visibility?

UPDATE:

Following Sergio Tulentsev's response (thx for the correction), I'll update my concern with a code snippet that sums up my concerns of the method visibility being taken into account while still inside the scope where it has been defined.

class Bang
  def instance_bang
    private_bang = 1
    self.private_bang(private_bang)
  end
  private
  def private_bang(p)
    puts "bang"
    p
  end
end

Calling Bang.new.instance_bang will raise an Exception unless you use send on that private_bang call (this time I checked it :) ).

EDIT: Answering the updated question

It is forbidden to call private methods with explicit receiver. You either have to use implicit receiver ( private_bang , without self ) or use send . Please see my another answer for more information.

By the way, the original question is about calling class instance methods from instance methods. Your clarification doesn't include that. But if that's still true, you have to use self.class.send or make the method public (so that you can use explicit receiver).

Let's consider a private class method (since protected class methods don't make sense ).

We know it's possible for an instance to call a private method on itself, as long as it isn't using an explicit receiver ( self.call_something_private ). It seems you also expect that an instance can call a private class method on its own class, but that is not the case.

Let's look at a way to do this without using send .

The private and protected macros only affect instance methods of the current scope, not class methods. Here are three ways to rewrite your original code:

class Bang
  def instance_bang
    self.class.class_bang
  end

  # declare method visibility after
  def self.class_bang
    puts "bang"
  end
  private_class_method :class_bang

  # inline
  private_class_method def self.class_bang
    puts "bang"
  end

  # class scope
  class << self
    # the private macro works here because we're inside the class scope
    private

    def class_bang
      puts "bang"
    end
  end
end

So now we want to expose an interface on the class to call class_bang , but only if it's called by an instance of Bang .

class Bang
  def instance_bang
    self.class.invoke_class_bang(self)
  end

  class << self
    private

    def class_bang
      puts "bang"
    end

    public

    # we ask the receiver to pass itself as an argument ...
    def invoke_class_bang(receiver)
      # ... so that we can check whether it's
      class_bang if receiver.is_a?(Bang)
    end
  end
end

That's not a very nice looking solution though. Here's a sneakier way:

class Bang
  def initialize
    def self.instance_bang() self.class.method(:class_bang).call end
  end

  class << self
    private
    def class_bang
      puts "bang"
    end
  end
end

"The class method should be used inside the class scope, therefore should remain visible and callable within it, right?" Yes, that's correct, and that's the behavior Ruby exhibits. (As a point of clarification, instance scope is not "within" class scope. They are, appropriately, separate.)

The non- send solution is to subclass or reopen the class to add a public class method to access the protected class method.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM