Private class (not class method) in a Ruby module?

10,423

Solution 1

I haven't seen such concept so far in Ruby, but I guess you could simulate that by creating private method which would return a class created as a local variable (remember that in Ruby, a class is an object just like any other, and can be instantiated in a method and returned by it).

BTW, even private methods in Ruby aren't as private as in other languages - you can always access them using send method. But doing that implies you know what you are doing.

Solution 2

The most important thing to realize is that a class is nothing special. It's just an object. Per convention, classes are assigned to constants, but there is nothing that says they have to be.

And since classes are just objects like any other object, you make them private the same way that you make any other object private.

Here are the possibilities I can think of, in the order of increasing privateness:

  1. Just nest them inside a namespace (i.e. module). In Ruby, it is generally expected that all of a library's modules and classes live inside a namespace with the same name as the library (i.e. my_awesome_libraryMyAwesomeLibrary), but in general, everything which is nested below that namespace, is considered private. In fact, besides Test::Unit::TestCase, I cannot think of a single example of a three-level deep namespace that is actually expected to be used by client code.
  2. same as 1., but name it something obvious, like MyAwesomeLibrary::Internal::FfiStruct
  3. same as 1. or 2., and mark it with the :nodoc: RDoc tag.
  4. similar to 3., but use a more modern documentation system like YARD, which actually lets you explicitly mark up private APIs.
  5. Use a method instead of a constant. Methods can be made private. (For consistency's sake, you can have the method name start with an uppercase letter, to make it resemble a constant. There's nothing to prevent that, the snake_case convention is just that: a convention.)
  6. Use an instance variable. They are always private. Note that both private methods and instance variables can be trivially accessed using reflection. send seems to be in more widespread use than instance_variable_get, though, which is why I consider instance variables to have a higher level of privacy than methods.
  7. Really the only way to get actual privacy or encapsulation is using local variables and closures, though. Note however that this might preclude you from using Ruby's module, class or method definition syntax, because those create new scopes. In all cases where you need access to the class, you need to use Module.new, Class.new or Module#define_method.

Ex.:

module MyAwesomeLibrary
  struct = Class.new(FFI::Struct) do
    # ...
  end

  PublicInterface = Class.new do
    define_method(:initialize) do |bar|
      @foo = struct.new(bar)
    end
  end
end

And yes, this is the only way of achieving true 100% information hiding and encapsulation in Ruby.

However, the normal Ruby way would be to simply document the stuff as being private (maybe push it down a level of namespacing) and trust your fellow developers. In the Ruby community, this is sometimes summarized under the Python slogan "We are all consenting adults".

Solution 3

Taking this information straight from this blog post, but since Ruby 1.9.3 you can create a private class within a module using private_constant:

class Person
  class Secret
    def to_s
      "1234vW74X&"
    end
  end
  private_constant :Secret

  def show_secret
    Secret.new.to_s
  end
end
Share:
10,423

Related videos on Youtube

michaelwb11
Author by

michaelwb11

Updated on April 30, 2022

Comments

  • michaelwb11
    michaelwb11 about 2 years

    I'm new to Ruby (experienced with Python, C++ and C). I need to create a class that is only to be used by other classes and methods in a module. In Python, I'd just call it __classname. I'd use an empty typedef in C++. How do I do this in Ruby (or am I barking up the wrong tree and not doing this the "Ruby way"?)

  • michaelwb11
    michaelwb11 almost 14 years
    Damn. I figured it was possible, but I hoped it wouldn't be hackish... Oh well. Guess I'll find a more Ruby-ish way to do it. Thanks though - you answered my question.
  • Mladen Jablanović
    Mladen Jablanović almost 14 years
    You should wait for other answers, I may as well be wrong. BTW, what's that you want to achieve with private classes? Oh, another thing: instance vars are always private in Ruby, and in order to expose them, you need to create accessor methods, but no one considers that hackish. :)
  • michaelwb11
    michaelwb11 almost 14 years
    Actually I think doing that /is/ a bit hackish - it's one of the things I don't like about Ruby. The other is the stupid implicit function call - I really wish I could just refer to a function by name without calling it, without using a symbol or lambda. I'm dynamically interfacing with an external library using FFI, and I wanted to keep the FFI C 'struct's away from the usable module.
  • oligan
    oligan almost 14 years
    "We are all consenting adults" is a slogan from the Python community, not the Ruby community.
  • Jörg W Mittag
    Jörg W Mittag almost 14 years
    I didn't mean to imply that it belongs to the Ruby community, only that it is used there. But you are right, the wording is misleading. Twenty years of learning English apparently still isn't enough :-)
  • Mladen Jablanović
    Mladen Jablanović almost 14 years
    IMHO, adding a language construct for a feature which can be easily covered by existing ones (such as attr_accessor method) would be hackish. I appreciate Ruby very much for that kind of simplicity, although I would remove even more keywords (like completely excess for). And regarding function calls, I agree with you there. BTW, I am pretty sure one could "wrap" making class private into a single method call pretty easy, so at least it shouldn't look hackish. :)