Loading Constants – Ruby VS Rails (Part 1)

Ruby is an Object Oriented Language with many unique characteristics that make it, not only a pleasure to work with, but really powerful too. If you have used constants in other languages you will be surprised how Ruby defines constants, and, actually, how Ruby loads them into memory. On top of that, Ruby on Rails takes it even further and uses a convention to allow constants to be loaded with minimum programmer’s hassle.

Introduction

This post is a long presentation on how constants are treated by Ruby (part I) and then by Ruby on Rails part II. In fact the core of the text is around how constants are defined and how they are resolved. It demonstrates how Ruby does the constant resolution and how this differs from the method Ruby on Rails uses. In order to elaborate on this core subject, we are also unfolding various other satellite details, nevertheless, always around constants.

Why You May Want To Know About Constants

Knowing about constants, how they are defined and how they are being resolved, is a knowledge that will be proven useful when debugging your application. You may come across situations in which you expected a constant to be found, but it was not. Or you may have come to a situation in which you see warnings about your constants being defined multiple times without actually understanding why. Besides that, understanding how Ruby and Ruby on Rails deals with constants will allow you to write cleaner and less code, knowing when to use require vs load or even better avoid any of them altogether.

Table of Contents

Part I

  1. What Is a Constant in Ruby
  2. Ruby Keeps Track Of Constants Defined
  3. Object.constants
  4. Accessing Undefined Constants
  5. Which Piece of Code Raises the NameError?
  6. Another Method To Check that a Constant is Defined Or Not
  7. Removing A Defined Constant
  8. Classes And Modules Are Constants Too
  9. Nesting And Constants
  10. Getting the Nesting Path
  11. How Constants Are Resolved When References Are Nested?
    1. Resolving Constants
      1. Case #1 – Found in Any of Nesting Points
      2. Case #2 – Found in Ancestorsting Point
      3. Case #3 – Found in Object
      4. Case #4 – Constant is Not Resolved
    2. References that are Qualified
      1. Constant Found in the Parent
      2. Constant Found in the Ancestors of Parent
      3. Constant is not looked at at Object
  12. Overriding const_missing on Nested Cases
  13. const_missing for Classes and Modules

Part II

  1. Ruby On Rails – Constant Resolution
  2. Application Entry Point
  3. Ruby Parsing of Files – load vs require
  4. Rails Parsing of Files – load vs require
  5. Qualified Constants – Autoloading With Nesting
  6. Add Folders to Autoload Paths
  7. How does Rails Reloads Constants in Development Environment?
  8. config.eager_load
  9. How Does Rails Attaches The Autoloading Mechanism to Constant Missing Hook?
  10. Watch Out for requires and require_relatives

What is a Constant in Ruby?

Let’s just start from the very basics. Do we all know what a constant is in Ruby?

A constant is like a variable, from one point of view. Like a variable, it is a memory position that has a name and points to another place in memory that holds the actual content.

But, how do we define constants in Ruby? Again, it is the same way that we define variables, by assigning them a value. However, the name needs to start with an uppercase Latin character. Example:

constants

The names of the constants and variables are there for us to make our life easier when programming. So, when we have to access the content a constant or variable is pointing to, we only have to use the name of the constant or the variable. We do not have to use the corresponding hex number of the memory address.

We are so lucky programming in high level programming languages. Long time ago, developers had to program in assembly, and they, yes, believe it, had to reference memory addresses and registers using hex numbers, or even worse, bits and bytes.

On the other hand, constant pointing content, cannot be changed, whereas variable pointing content can. That’s why the former are called constants, whereas the latter are called variables.

In fact, Ruby is a place where you can have lots of freedom and you can actually change the content of a constant, but you will get a warning! It can also try to tell you where the previous definition of the constant is.

Or you can try this:

with constant_redefinition.rb being:

You will get this:

Nice warning and informative text. Look how it tells you that the previous definition of C was on file constant_redefinition.rb on line 1.

Ruby Keeps Track Of Constants Defined

I guess that this comes natural to you. Ruby keeps track of constants defined. Yes. Of course it does. That’s why it can tell you whether a constant is already defined and where.

We will not delve into too much detail about all the meta-data that Ruby keeps for each constant defined. We will only give you some details that are relevant to our discussion. And actually, let’s learn that stuff by doing some examples:

Object.constants

Let’s start by running the following program:

where object_constants.rb is

The output is going to be something like this:

The above proves that when we define a constant at the top level of our programming scope, then it adds it to the Object.constants array as a symbol.

Keep a note that the array constants belongs to the Object which is the class of self (see a little bit later on), i.e. the class of the top level scope when we start writing Ruby commands.

Accessing Undefined Constants

What happens when we try to access a constant that is not defined? Let’s try that:

FILE: access_undefined_constant.rb

C

If you call ruby access_undefined_constant.rb you will get this:

So, this is a NameError. The error message clearly tells you that the C is not defined.

Which Piece of Code Raises the NameError

So, Ruby tries to find the C constant defined inside Object.constants array and it does not find it (or, this is more or less what it does when encountering a constant). What does it do when it cannot find it? It calls the .const_missing() method of the Object class. We can redefine that and verify that this is the method that is actually called:

If you call ruby redefine_object_const_missing.rb, you will get this:

As you can see from the message above, we now get an exception on line 9 of the program, and the NameError returned is the one that is instantiated on line 5.

Lines 3 to 7 above, they open class Object and they redefine the class method .const_missing. Note that self, that receives the C statement on line 9, is an instance of Object class. That’s why when we redefine the Object.const_missing we actually redefine the way self will be treating the constants that are not defined in its class.

This is what I just said with regards to self and Object relationship:

  1. self is instance of Object.
  2. Object is instance of Class, which, basically means, it is a class *.

* Hey! In Ruby a class is actually an object. No? They are instances of Class class.

Another Method To Check that a Constant is Defined Or Not

There is another method that you can use to check whether a constant is defined or not. It is the keyword defined? which takes as argument anything that we want to check whether it is defined or not. It returns nil if something is not defined. It returns a string describing the type of given argument, if it is defined.

Try this:

When you run that you get:

which clearly mentions that C is a defined constant, whereas D is not defined.

Removing a Defined Constant

Generally, you cannot undefine a constant, unless you call the private method #remove_const on the Object instance.

Try this:

You will get:

It worked, didn’t it? The constant C has been removed/undefined after Object.send :remove_const, :C.

Classes And Modules Are Constants Too

Since in Ruby we name classes and modules using uppercase Latin characters, it wouldn’t be a surprise to you if I told you that classes and modules are, among other things, constants too.

Like the other constants, classes and modules need to be defined and then referenced, i.e. used.

Try the following program that defines a class:

When you run that with ruby class_definition.rb, you will see this:

Aha! the class MyClass ... end has defined a new constant which is now included in Object.constants array as :MyClass.

Let’s do the same for a module:

If you run the above with ruby module_definition.rb you will get this:

And what if we define a class or a module using the other (you know which, don’t you) technique to create classes and modules?

Try that:

If you run that with ruby class_definition_technique_2.rb, you will get the same output like you did when your ran ruby class_definition.rb.

You can try with MyModule = Module.new too. You will get similar results.

Nice! The above examples show that defining classes and modules creates constants too.

And don’t forget that you use a class or module definition exactly as you use other constants. You just use their name.

Nesting and Constants

We all know that in Ruby, we can use nesting to group together various definitions that logically belong to the same concept. But how does nesting affect the constants definition? The answer is that the defined constants are qualified by the module they are nested in.

Let’s see that with an example:

If you run the above, you will get this:

The above Ruby program defines 4 constants:

  1. MyModule
  2. MyModule::MyConstant
  3. MyModule::MyModule
  4. MyModule::MyModule::MyConstant

As you can see it works like files and folders. A file can have the same name with another file, as long as the two files exist in different folders. Same goes for constants. MyConstant living in MyModule is different to the one living in MyModule::MyModule.

When using nesting, you actually start defining constants inside the constants array of the nesting elements. Hence, MyModule::MyConstant definition has appended the element :MyConstant inside the MyModule.constants array.

If you add the following lines to the nesting.rb program …

and then run it, you will get these extra lines printed:

Note that MyModule constant is defined both in Object and in MyModule constants. But they are different. The former is the top level root constant, whereas the latter is the nested constant inside the MyModule nesting constant (MyModule::MyModule).

Object nesting constants

Getting the Nesting Path

Module.nesting call gives you the nesting path elements. Try this:

If you run the above, you will get:

So, every time you nest (with a class or module definition), then the current new path is added *at the beginning of the Module.nesting array.

sb-tech-site-technology

How Constants Are Resolved When References Are Nested

It’s clear from the above how the constants are defined, when nested inside other constants definition blocks (classes or modules). But how Ruby resolves a reference to a constant when that one is invoked inside a nesting point?

Resolving Constants

Case #1 – Found in Any of Nesting Points

A constant might be referenced using a relative reference. This means it is not prefixed with ::. Let’s see the following example:

If you run the above, you will see this:

On line 10, the B is a relative reference. Ruby will try to check whether this constant is defined in any of the modules or classes listed in Module.nesting array at the point where the B is referenced. It fails to find this constant defined in MyModuleA::MyModuleB, but it succeeds in finding it in MyModuleA

Note that the fully qualified name of the constant B is MyModuleA::B.

If we move the definition of B inside the MyModuleA::MyModuleB scope, it will be found there:

If we run the above, we will get this:

Note that the fully qualified name of the constant B is MyModuleA::MyModuleB::B.

Case #2 – Found in Ancestors of First Nesting Point

When a constant is not found in any of the nesting points, then Ruby tries to find it in the constants of the ancestors of the first nesting point (class or module).

Let’s see the following example:

If you run the above, you will get this:

B is defined inside MyAncestor and then MyAncestor is included in MyModuleA::MyModuleB. Before we actually include MyAncestorB was not defined in MyModuleA::MyModuleB. Immediately after we included MyAncestorB appeared to be defined in MyModuleA::MyModuleB.

Then, on line 23, we reference B. This is a relative reference and Ruby tries to find where this constant is defined. It does find it on the first nesting point.

In fact, MyAncestor::B and MyModuleA::MyModuleB::B are same constant objects. If one changes the first, it changes the second as well. They have the same object_id.

Case #3 – Found in Object

Earlier on, we have learnt that Ruby first looks to find the constant inside the constants of the nesting points as these are returned by Module.nesting. If constant referenced is not found, then it tries to find it inside the constants of the ancestors of the first item in the Module.nesting, i.e. inside the ancestors of the current nesting point.

What if it is not found there either?

Then it searches the Object constants. This may come to a surprise to you, but, it does not search the ancestors of the other nesting points. It did that only for the current, first, nesting point.

Let’s see the following example:

If you run the above, you will get this:

The above proves the interesting stuff that if a relative reference is not found in any of the nesting points, and neither inside the ancestors of the first/current nesting point, then it is being looked inside the Object constants and the ancestors of the next nesting points are ignored.

You can see that on line 22, we are using the relative reference B from within the nesting point MyModuleA::MyModuleB. This nesting point has the following nesting array: [MyModuleA::MyModuleB, MyModuleA]. But neither MyModuleA::MyModuleB nor MyModuleA define B. Same goes for the ancestors of MyModuleA::MyModuleB, which are: [MyModuleA::MyModuleB, MyAncestorB]. None of them defines B. Watch out also the fact that, the second nesting point, when we reference B, is MyModuleA and its ancestors are [MyModuleA, MyAncestor], one of which already has B in its constants. But this is ignored. The B that is finally referenced is the one coming from the constants of Object.

On the following diagram, you can see the 4 points B was searched for. The first 3 ones failed and, finally, B has been found in Object.

Relative Reference

Case #4 – Constant is Not Resolved

Now that we have learnt how relative references to constants are resolved, let’s see another example in which the reference is not resolved.

If you run the above, you will get this:

B is not found because it is not defined in any of the nesting points: [MyModuleA::MyModuleB, MyModuleA], neither in any of the ancestors of MyModuleA::MyModuleB, a.k.a. MyAncestorB, and neither on Object level. That’s why we see the NameError raised.

References that are Qualified

Constant Found in the Parent

qualified constant reference is a reference that is qualified by another class or module relative reference. Look at the following example:

If you run the above, you will get this:

The reference MyModuleB::B on line 7, is a qualified reference. The reference to B constant is prefixed by the MyModuleB relative reference, which makes it a qualified reference.

The MyModuleB is considered to be the parent module. The constant B is being looked at at the parent and it is found there.

Constant Found in the Ancestors of Parent

When the qualified reference is not found in the parent class or module, then it is looked at the ancestors of that. Look at the following example:

If you run the above, you will get 1 again.

The line 12 is a qualified reference of B. This is not found in the parent MyModuleB, but it is found in one of the ancestors of MyModuleB, inside the MyAncestor.

Constant is not looked at at Object

If the qualified reference is not found in parent, neither in any of its ancestors, then it is not looked at at Object level.

If you try the following example:

you will get this:

which proves that the constant B is not looked at the Object level constants.

Overriding const_missing on Nested Cases

What if we wanted to treat in a special way the missing of constants when referenced from MyModuleA::MyModuleB? We would have to redefine the const_missing method.

In the following example we override the const_missing and then try to run the program again:

If you run the above program, you will get this:

Oooooooops! B is missing!

As, you can see, the implementation on lines 16-18 above is being used. This does not raise an error when a constant is missing. It just prints a message.

const_missing for Classes and Modules

Earlier, we have seen how we can override the Object const_missing method. And, above, we saw how we can override the const_missing for a particular nesting point.

In any case, you can always override the const_missing for classes and the const_missing for modules separately.

Look at the following example:

If you run the above, you will get this:

When on line 16 the program is referencing B, this is not found and the method Class#const_missing is called. On the other hand, on line 20 the program is referencing C. This is not found and the method Module#const_missing is called.

Cool! This proves that we can override the Module and Class const_missing methods with different implementations.

Some Ruby object model refresh again: Above, we redefine the instance methods #const_missing for the classes Class and Module. Hence, any class or module instance, like MyClass and MyModule will have their const_missing behaviour redefined. In Ruby a defined class is actually an instance of the Class class. And a defined module is an instance of the Module class.

Ruby object module

Part I Closing Note

On the first part of this 2-parts blog post, we have explained how Ruby treats constants, how they are organized and, most importantly, how a referenced constant is being resolved. Also, we have learnt how we can override the default constant missing behaviour.

On the second part, we will switch to Ruby on Rails and see how constant references are resolved, basically using a technique which is called autoloading.

Thanks for reading part I and I hope that part II will prove useful to you as much as this one.

Also, don’t forget that your comments below are more than welcome. I am willing to answer any questions that you may have and give you feedback on any comments that you may post. I would like to have your feedback because I learn from you as much as you learn from me.

About the Author

Panayotis Matsinopoulos works as Development Lead at Simply Business and, on his free time, enjoys giving and taking classes about Web development at Tech Career Booster.

Ready to start your career at Simply Business?

Want to know more about what it’s like to work in tech at Simply Business? Read about our approach to tech, then check out our current vacancies.

Panos G. Matsinopoulos

This block is configured using JavaScript. A preview is not available in the editor.