How to implement private inner class in Ruby
Asked Answered
S

2

12

Coming from Java, I am trying to implement LinkedList in Ruby. The usual way I would implement this in Java is have an class called LinkedList and private inner class called Node with each object of LinkedList as Node object.

class LinkedList
  private
  class Node
    attr_accessor :val, :next
  end
end

I do not want to expose the Node class to external world. However with this setup in Ruby, I can access the private Node class object outside the LinkedList class using this -

node = LinkedList::Node.new

I know, with Ruby 1.9, we can use private_constant method to designate Node as private constant. But I am wondering if this is the right way to accomplish this? Also why am I able to create Node objects outside the LinkedList class even though it is declared as private?

Swaney answered 27/9, 2016 at 5:42 Comment(2)
In Ruby you live in a glass house, so you better get used to people seeing your private stuff all the time. The best you can manage is putting up things that say "please don't" as a polite way of getting people to avoid getting ideas.Mighell
BTW, you should accept answers that answer your questions or solve your problem. Look for the green checkmark to the left of answers.Subtemperate
S
16

why am I able to create Node objects outside the LinkedList class even though it is declared as private?

Because in ruby constants ignore "regular" visibility modifiers. They're always public, regardless of which section they're in. To make them private, use private_constant. Call this inelegant design or whatever, but that's how it is.

Also, be warned that even with private_constant, the privateness means very little. Basically, the only thing it does is hide the constant from lists (LinkedList.constants) and direct resolution (LinkedList::Node). If one knows the name, they will be able to access it.

class LinkedList
  class Node
    attr_accessor :val, :next
  end

  private_constant :Node
end

LinkedList.const_get('Node') # => LinkedList::Node
Subtemperate answered 27/9, 2016 at 5:46 Comment(7)
I think you can't do something like this node = LinkedList::Node.new, with private_constant.Douglasdouglashome
@BartekGładys: yes, that's what I was saying.Subtemperate
Ok, that's pretty :)Douglasdouglashome
Thanks @SergioTulentsev. I have not seen many inner class implementations in Ruby. Is there any better (Ruby-ish) way to accomplish this same functionality?Swaney
@WastedPandoo: yeah, the ruby-ish way is to not bother with the private thing. Just mark it as "internal" and "warranty is void if this is used directly".Subtemperate
Even more: LinkedList.public_constant(:Node); LinkedList::Node.new :)Shandy
@Swaney I use to put the comment # 💣 directly before private constant declarations.Shandy
S
4

I know that Sergio's answer is more than enough, but just answering the question:

How to implement private inner class in Ruby

You could also go with:

class LinkedList
  class << self
    class Node
    end

    def some_class_method
      puts Node.name
    end
  end
end

LinkedList.some_class_method        # accessible inside class
#=> #<Class:0x007fe1e8b4f718>::Node
LinkedList::Node                    # inaccessible from outside
#=> NameError: uninitialized constant LinkedList::Node
LinkedList.const_get('Node')        # still inaccessible
#=> NameError: uninitialized constant LinkedList::Node

Of course you'd be able to access Node with

LinkedList.singleton_class::Node
#=> #<Class:0x007fe1e8b4f718>::Node

And it also is available within LinkedList's singleton class constants:

LinkedList.singleton_class.constants
#=> [:Node, :DelegationError, :RUBY_RESERVED_WORDS, :Concerning]

I usually use private_constant, but it is another way to have a private class.

Sizeable answered 27/9, 2016 at 7:17 Comment(5)
Yep, looks nice, but one can easily circumvent this one too: LinkedList.singleton_class::Node # => #<Class:0x007f899f82b1c8>::Node :)Subtemperate
Also LinkedList.singleton_class.constants # => [:Node]Subtemperate
@SergioTulentsev correct, which again confirms that there's nothing "inaccessible" in Ruby :)Sizeable
@SergioTulentsev thanks a lot for comments! Edited the answer with theseSizeable
Defining a class in the eigenclass does not make it private. You should use private_constant methodKaiulani

© 2022 - 2024 — McMap. All rights reserved.