How Can a Nested Class Access a Method in The Outer Class in Ruby

How can a nested class access a method in the outer class in Ruby?

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.)

In Ruby, how can inner class access variables/methods defined in outer class?

In Ruby, how can inner class access variables/methods defined in outer class?

Ruby does not have a concept of "inner" or "outer" classes. It sounds like you are coming from a language like BETA, Scala, or Newspeak, but Ruby is not BETA, Scala, or Newspeak. As a general rule, any programming language works exactly how the specification for the programming language says it works, not how the specification for some other programming language says it works. BETA, Scala, and Newspeak have nested classes, but Ruby has not.

In Ruby, you can lexically nest a class definition, but that does not create a nested class. If you nest the definition of a class inside the definition of another class, that does not create any sort of relationship whatsoever between those two classes. None. You cannot "access variables/methods" of one class from the other class because there is no relationship between those two classes you could follow in order to get at those variables/methods.

The only thing a nested class definition does, is namespace the constant defined by the inner class definition to the outer class. That's it. There is no relationship between the two classes, the only relationship is between the constant and the class.

It is just not possible.

The way to create relationships between classes in Ruby is inheritance, not nesting (because there is no nesting). The way to create relationships between objects in Ruby is association, aggregation, or composition, not nesting.

Ruby is flexible enough to implement something that behaves like a "proper" BETA-style inner class. Remember, an inner class is "nested" inside an object instance of the outer class. We can emulate that using an instance variable:

class Outer
attr_reader :Inner

def outer_method
__callee__
end

private

attr_writer :Inner

def initialize
self.Inner = Class.new(self.class) do
def inner_method
[__callee__, outer_method]
end
end
end
end

outer = Outer.new
inner = outer.Inner.new
p inner.inner_method

Please note that I made the inner class inherit from the outer class: that is the way to do behavior sharing in Ruby. You need to have some form of inheritance or association relationship. The nesting does not create a relationship.

How to properly access instance variables in one inner-class from another inner-class?

Really important concept here. "Inner class" does not do what you think it is. There is absolutely no relationship or special behavior between the two classes, other than namespacing. Basically, just treat the inner class like it is defined totally separately, because that's how it behaves.

One thing you can do, though, is pass the GameBoard constructor a reference to the Game instance, so you can access the instance variables:

(note that i am only showing the parts relevant for the answer, and am ommitting the other bits of your code for the sake of being succinct):

class GameBoard
attr_reader :game
def initialize(game)
@game = game
end
end

class Game
def initialize(player1_name, player2_name)
@player1 = Player.new(player1_name, 'X')
@player2 = Player.new(player2_name, 'O')

# Note I pass the Game instance as self here:
@gameboard = GameBoard.new(self)
end
end

Then inside the GameBoard instance methods, you can do something like this:

game.player1.moves[game.player1.turn]

game here is the method generated by attr_reader (referring to the @game instance variable set in initialize).

how to access a class variable of outer class from inner class in ruby

The only way to access this class variable is via an accessor method

class A
def self.lock
@@lock ||= Monitor.new
end

class B
def method
A.lock.synchronize
puts "xxxxx"
end
end
end
end

Call method of outer class instance from inside module

Given that in this situation, there will only ever be one instance of Current or similar, this can be accomplished by creating a class instance upon Location initialization, and using attr_reader to access it.

Here's working code, based on the original post.

location.rb

module DarkSky
# other methods are here, as this is for a Gem
class Location
attr_reader :current

def initialize(location)
@location = location
@current = Current.new self # here's the "hidden" initialization with the instance as a parameter
end

def full_data
# return common data between `Current` and `Tomorrow` classes
{
currently: {
temperature: 42
}
}
end
end
end

current.rb

module DarkSky
class Location
class Current
def initialize(location)
# keep a reference to the location (for the shared data)
@location = location
end

def apparent_temperature
@location.full_data[:currently][:temperature]
end
end
end
end

This allows for what appears to be a namespace, but in reality is just a getter for the class instance, which then lets you get the individual methods.

Access outer class accessor from inner class

B is its own self-contained class. It is not associated or connected with any of the other classes or modules except to say that an instance of class B happens to be created inside of InstanceMethods::test. Declaring class B inside of the definition for module InstanceMethods limits the scope of B such that it's not visible outside of InstanceMethods, but it doesn't connect B and InstanceMethods in any way.

What you need is to make the module's variable visible inside B. You can implement an initialize method for B that takes a parameter, and InstanceMethods::test can use b = B.new(@monkey) to pass the value to B (make sure B::initialize stores the value as an instance variable).

When to use nested classes and classes nested in modules?

Other OOP languages have inner classes which cannot be instantiated without being bound to an upper level class. For instance, in Java,

class Car {
class Wheel { }
}

only methods in the Car class can create Wheels.

Ruby doesn’t have that behaviour.

In Ruby,

class Car
class Wheel
end
end

differs from

class Car
end

class Wheel
end

only in the name of the class Wheel vs. Car::Wheel. This difference in name can make explicit to programmers that the Car::Wheel class can only represent a car wheel, as opposed to a general wheel. Nesting class definitions in Ruby is a matter of preference, but it serves a purpose in the sense that it more strongly enforces a contract between the two classes and in doing so conveys more information about them and their uses.

But to the Ruby interpreter, it’s only a difference in name.

As for your second observation, classes nested inside of modules are generally used to namespace the classes. For instance:

module ActiveRecord
class Base
end
end

differs from

module ActionMailer
class Base
end
end

Although this is not the only use of classes nested inside of modules, it is generally the most common.



Related Topics



Leave a reply



Submit