DEV Community

Cover image for Proposal for making private method work on constants too
Lucian Ghinda
Lucian Ghinda

Posted on • Originally published at allaboutcoding.ghinda.com

Proposal for making private method work on constants too

There was a proposal on the Ruby bug tracker a while back about making the private method work also on constants:

Screenshot of the proposal at Ruby Bug Tracker

See https://bugs.ruby-lang.org/issues/17171

That proposal is now closed with concerns about backward compatibility.

We should reopen this discussion and try to find ways around backward compatibility. Let me explain why I think so:

A possible code in Ruby

This might be a possible code design found in a Ruby project:

class MyObject 
  DEFAULT_VALUE = 'active'
  private_constant :DEFAULT_VALUE

  def a_public_method
    # doing something important here
  end
end
Enter fullscreen mode Exit fullscreen mode

When reading this file, we first read DEFAULT_VALUE then we find out it is a private constant and then we go to read the actual public interface. I would assume that usually the information architecture that we would want when reading this file would be something like this:

  1. Public methods

  2. Public constants

  3. Private methods

  4. Private constants

Let's take two possible cases for reading this file:

  • Trying to understand what this object does: in this case I want to see first the public method(s) and then go down inside private methods if I want to expand my knowledge and understand some fine grained details.

  • Trying to understand what is the content of a public constant called from some other place: In this casde I want to see first the public constant

Anyhow no matter the objective I almost never want to read first the private methods or private constants.

Going to private constants: I don't think there is a good case to be made for putting a private constant in the public space of the object. The constant is private because it should be used only inside the current object in its either public or private methods.

So maybe the following code design is a better fit from the point of information architecture:

class MyObject 
  def a_public_method
    # doing something important here
  end

  private 

    DEFAULT_VALUE = 'active'
    private_constant :DEFAULT_VALUE
end
Enter fullscreen mode Exit fullscreen mode

Of course in this case while the intention of the information is communicated pretty clear and the code is organised in public interface to the top and private interface to the bottom, there is a duplicate information there:

private and private_constant

But moreso it is a confusing thing that when first learning Ruby you just have to learn it: the private method does not affect constants so you have to use private_constant.

I think a better (in the sense of simplicity and expectations) code design would be to write:

class MyObject 
  def a_public_method
    # doing something important here
  end

  private 
    DEFAULT_VALUE = 'active'
end
Enter fullscreen mode Exit fullscreen mode

And make Ruby private method to affect also constants. So MyObject::DEFAULT_VALUE should throw NameError .

Arguments in favor of this feature

Principle of least surprise

The main argument for making the private method work for me would be based on the principle of least surprise: seeing a code like the one above will make everybody think that DEFAULT_VALUE is a private constant. So it should work that way.

Removing boilerplate

The second argument is that it will remove a boilerplate code: the private_constant method should be used in a small number of situations. It seems to me that it should remain in the language but for specific usage.

Removes the need to know a trick (maybe still the principle of least surprise)

When encountering the method private the expectation is that what follows is something private. Writing a constant there is now making it public, disrupting the flow of thoughts about the visibility of what is in that section of the code.

Compatibility concerns

I know that the previous feature request was closed with a concern for breaking compatibility by Matz. Which is a valid concern and I appreciate so much the case he puts into making our life easier when upgrading.

But I do wonder if in this case the worrying is real.

What is the main case of implementing breaking changes? When someone added the constant in the private section or called the private method for it and then used it as being public.

Something like this:

class MyObject 
  def a_public_method
    # doing something important here
  end

  private 
    DEFAULT_VALUE = 'active'
end

class AnotherObject 
  def some_method
    MyObject::DEFAULT_VALUE
  end
end
Enter fullscreen mode Exit fullscreen mode

I do wonder two things:

  1. How common is this way of writing code: putting the declaration of a private constant in the private section of an object?

  2. And if this exists how common it is for the constant defined like that to be accessed outside that object?

My main assertion is that if someone writes a constant in the private area of an object, their intention (even if it is not actually enforced during execution) is that they want to say or show that constant is private.

Calling that constant from other objects is breaking this contract. It is for me the same as calling send or __send__ on a private method. It can be done but that breaks the contract for that object. But if want to achieve the same for cosntants we already have const_get that will get any constant from an object.

Since we have this repo now Gem CodeSearch I will try to see at least for gems if I can find a way to search for this specific case (using a constant defined as private - but not with private_constant into another object).

New functionality

I think making the private work on constants will open the case for making a class defined inside another class private.

Take this code for example:

class MyObject
  class AnotherObject
    def hello
      puts "I am here"
    end
  end

  def a_public_method
    AnotherObject.new.hello
  end
end

MyObject.new.a_public_method
Enter fullscreen mode Exit fullscreen mode

I think AnotherObject remains public here unintentionally.

Most of the time this code should be:

class MyObject
  class AnotherObject
    def hello
      puts "I am here"
    end
  end

  private_constant :AnotherObject

  def a_public_method
    AnotherObject.new.hello
  end
end

MyObject.new.a_public_method
# => 'I am here'

MyObject::AnotherObject.new.hello
# => private constant MyObject::AnotherObject referenced (NameError)
Enter fullscreen mode Exit fullscreen mode

But people usually forget to add the private_constant here. And even so this goes to the first point where when reading this file, I first read an object that is private.

I think making the private method affect constants, will open the case to write something like this:

class MyObject
  def a_public_method
    AnotherObject.new.hello
  end

  private
    class AnotherObject
      def hello
        puts "I am here"
      end
    end
end

MyObject.new.a_public_method
# => 'I am here'

MyObject::AnotherObject.new.hello
# => private constant MyObject::AnotherObject referenced (NameError)
Enter fullscreen mode Exit fullscreen mode

So we could have private objects that can be defined with private method and be accessible only to the object where they are defined.

What's next

I am talking mostly from the perspective of someone that uses the language, so I for sure don't know all the possible problems this change will create. But I think we should have another conversation about this. And see if we can make it work.


Enjoyed this article?

👉 Join my Short Ruby Newsletter for weekly Ruby updates from the community and visit rubyandrails.info, a directory with learning content about Ruby.

👐 Subscribe to my Ruby and Ruby on Rails courses over email at learn.shortruby.com- effortless learning about Ruby anytime, anywhere

🤝 Let's connect on Ruby.social or Linkedin or Twitter where I post mainly about Ruby and Ruby on Rails

🎥 Follow me on my YouTube channel for short videos about Ruby

Top comments (0)