In Progress
Unit 1, Lesson 21
In Progress

Classes and Constants

Video transcript & code

Let's define a class to represent a point in 2D space.

point = Class.new

It would probably be good if that class had X and Y coordinates.

point = Class.new do
  attr_reader :x, :y
  def initialize(x, y)
    @x = x
    @y = y
  end
end
point.class # => Class

We can instantiate new instances:

p1 = point.new(5,7)
# => #<#<Class:0x9fc06f8>:0x9ff789c @x=5, @y=7>

We can also inherit from this new class by passing the parent class to Class.new:

circle = Class.new(point) do
  attr_reader :radius
  def initialize(x, y, radius)
    super(x,y)
    @radius = radius
  end
end

c1 = circle.new(3,5,10) 
# => #<#<Class:0x974c620>:0x974c5a8 @x=3, @y=5, @radius=10>

Of course, if we want one of these classes to be available outside the current scope, we should assign it to a constant instead of a local variable. Constants, as you know, start with capital letters.

Point = Class.new do
  attr_reader :x, :y
  def initialize(x, y)
    @x = x
    @y = y
  end
end

OK, so what is all this about? Well, in my experience many novice Ruby programmers don't fully understand is what is happening when a class is declared. What I'm trying to illustrate here is that in Ruby, a class is just an object. And that when we name a class, we're simply assigning that class to a constant, just as we might assign a number or a string to a constant.

UltimateAnswer = 42
Point = Class.new

Now, by convention we reserve camel-cased constants for classes and modules. But functionally there's no difference between an all-caps constant and one that's camel-cased.

Actually, it's not completely true that assigning a class to a constant is exactly the same as assigning any other value to a constant. There is one special rule for classes or modules. The very first time a class object is assigned to a constant, Ruby modifies the class to take the name of the constant it has just been assigned to. We can see that when we assign a class to a local variable, and then assign that variable to a constant, it takes on the name of the constant. Since both the local variable and the constant point to the same object, the change is reflected for both of them. This change of name is a one-time, permanent event. If we assign the class object to another constant, it retains its original name. And even if we reassign the constant it is named for, it still retains the name.

point = Class.new
point.name                      # => nil
point                           # => #<Class:0x98ad7d0>
Point = point
Point.name                      # => "Point"
Point                           # => Point
point.name                      # => "Point"
point                           # => Point
Point = nil
point.name                      # => "Point"

Normally we don't have to think too hard about stuff like this. But knowing it can help us understand the code we read as well as write. For instance, remember in previous episodes how we've often used Struct by assigning the result of Struct.new to a constant?

Point = Struct.new(:x, :y)

That might make a little more sense now.

Of course, in most cases we can just stick with the usual way of declaring classes, using the class keyword. But now when you see it, you'll realize that it's pretty much just a shorthand for creating a new class object and assigning it to a constant.

class Point
  # ...
end

That's it for now. Happy hacking!

Responses