22
def class A
  def a
    raise "hi" #can't be reached
  end

  class B
    def b
      a() #doesn't find method a.
    end
  end
end

I want to invoke a from b and raise the exception. How can I?

Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
nes1983
  • 15,209
  • 4
  • 44
  • 64

7 Answers7

28

Ruby doesn't have nested classes.

The only way to inherit behavior is, well, via inheritance.

If you want your code to work, you need to use a language which supports nested classes. While this is an incredibly neat and powerful feature, I unfortunately know of only two languages that have nested classes:

  • BETA, the language which introduced nested classes (and its successor gbeta)
  • Newspeak

I don't know of any other.

Java has a construct called nested classes, but they have some unfortunate design limitations.

In your example above, it's not the class B that is nested inside A, it is the constant B that is nested inside A. Think about this:

C = A::B

Now, the class is available under two names: A::B and C. It should be immediately obvious that C is global and not nested inside A. (Well, actually, C is nested inside Object, because there aren't really global constants either, but that's beside the point.) But since C and A::B are the same class, it obviously cannot be both nested and not nested. The only logical conclusion is that the class itself isn't nested.

The defining feature of nested classes is that method lookup goes along two dimensions: up the inheritance chain, and outwards through the nesting. Ruby, like 99.9% of all OO languages, only supports the former. (In some sense, nested classes inherit not only the features of their superclass, but also the features of their surrounding class.)

Community
  • 1
  • 1
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • Thanks for adding the clarifying comment, +1 from me. – Michael Kohl Feb 04 '11 at 22:50
  • 2
    @Michael Kohl: Those aren't nested classes, those are namespaced constants. The first sentence gets it right. What I suppose has happened is that the second sentence originally read something like "nested constants referring to classes", and they thought it sounded a little forced, so they shortened it to "nested classes" without realizing their mistake. Kind of like everybody knows that Ruby doesn't have class methods, but we still call them "class methods", because the correct formulation "instance methods of the class object's singleton class" is so damn inconvenient. Unfortunately, this – Jörg W Mittag Feb 04 '11 at 22:54
  • 1
    ... kind of shortcut jargon can lead to significant confusion for outsiders and newbies. Just have a look at all the questions here on StackOverflow that ask "I know how to do X (mocking, overriding, overwriting, aliasing, wrapping, stubbing) with normal methods, but how do I do it with class methods?" Because of the shortcut jargon we use, they don't realize that they actually already know the answer to their own question, because *there is no such thing as a class method*. – Jörg W Mittag Feb 04 '11 at 22:57
  • @Jörg: Yes, for this reason I though the OP was actually asking about "nested constants referring to classes", which led to my answer below. – Michael Kohl Feb 04 '11 at 23:09
  • 1
    Doesn't the world need less inheritance, not more? – Andrew Grimm May 25 '11 at 23:39
13

This is just for the lulz:

class A
  def a
    puts "hello from a"
  end

  class B
    def b
      Module.nesting[1].new.a()
    end
  end
end
horseyguy
  • 29,455
  • 20
  • 103
  • 145
5

I typically do something like this:

class A
  def a
    puts "hi"
  end

  def createB
    B.new self
  end

  class B
    def initialize(parent)
      @parent=parent
    end

    def b
      @parent.a
    end
  end
end

A.new.createB.b
Bradley
  • 75
  • 1
  • 3
3

If you want then nested class to extend the outer class, then do so:

class Outer

  class Inner < Outer
    def use_outer_method
      outer_method("hi mom!")
    end
  end

  def outer_method(foo)
    puts foo
  end

end

foo = Outer::Inner.new
foo.use_outer_method        #<= "hi mom"
foo.outer_method("hi dad!") #<= "hi dad"
rbb
  • 172
  • 1
  • 7
2

Well depending on your circumstances there is actually a solution, a pretty easy one at that. Ruby allows the catching of method calls that aren't captured by the object. So for your example you could do:

def class A
  def a
    raise "hi" #can't be reached
  end

  class B
    def initialize()
      @parent = A.new
    end

    def b
      a() #does find method a.
    end

    def method_missing(*args)
      if @parent.respond_to?(method)
        @parent.send(*args)
      else
        super
      end
    end
  end
end

So then if you do this:

A::B.new().b

you get:

!! #<RuntimeError: hi>

It is probably an easier way to make something like a SubController that only handles certain activities, but can easily call basic controller methods (You would want to send in the parent controller as an argument in the initializer though).

Obviously this should be used sparingly, and it can really get confusing if you use it all over the place, but it can be really great to simplify your code.

FuzzyJulz
  • 2,714
  • 1
  • 16
  • 18
1

You can use methods like module_parent, module_parent_name, module_parents from ActiveSupport to get outer modules, eg.:

class A
  def self.a; puts 'a!' end
  
  class B
    def self.b; module_parent.a end # use `parent` if rails < 6.0
  end
end

A::B.b #=> a!
Lev Lukomsky
  • 6,346
  • 4
  • 34
  • 24
0

Was a supposed to be a class method for class A?

class A
  def self.a
    raise "hi"
  end
  class B
    def b
      A::a 
    end
  end
end

A::B.new.b

If you want to keep it as an instance method, you'll obviously have call to it on an instance, like for example A.new.a.

Michael Kohl
  • 66,324
  • 14
  • 138
  • 158