Why you Should not use a Class as a Namespace in Rails Applications

This is a guest post by Junichi Ito (@jnchito). Junichi is a Ruby programmer at SonicGarden.jp, translator of Everyday Rails Testing with RSpec, and one of the most popular Ruby writers/bloggers in Japan

TL;DR

If you use a class as a namespace, it can produce a bug that doesn’t always show up on the surface. You should different names for your model class and your namespace in Rails applications.

Hidden bug with a class name as a namespace

You can use both classes and modules as a namespace. But if you use a class, it might lead to unexpected behavior, especially in Rails applications.
For example, you have two classes called Item and Staff:

You want to create two pages, one is for end users and the other is for staffs. End users can see published items only, but staffs can see all items. Then, you create these two controllers:

But Staff::ItemsController sometimes works wrong. It might return only published items. Why? This problem is related to Ruby’s constant lookup rule.

For an explanation, I’ll use a simpler example. If you run the code below in irb, you can refer to Staff::ItemsController without definition:

You can see that Staff::ItemsController returns ItemsController. Here’s why:

  • Top level constants always belong to an Object class, so ItemsController is equal to Object::ItemsController.
  • When Staff::ItemsController is referred to, Ruby cannot find the constant ItemsController in the Staff class. Then, Ruby tries to find it from the Staff class’s ancestors.
  • You can confirm ancestor classes and modules with Staff.ancestors. It returns [Staff, Object, Kernel, BasicObject] in the sample code above.
  • Ruby tries to find ItemsController in the Object class which is next to the Staff class in the ancestors array. Does the Object class have ItemsController? Yes, Ruby could find ItemsController successfully, because Object::ItemsController is already defined.
  • But it might be a wrong lookup, so Ruby gives the warning “toplevel constant ItemsController referenced by Staff::ItemsController.” In fact, this case does not match our intention.

However, when Staff::ItemsController is already defined, Staff::ItemsController can be referred to not by ItemsController, but by Staff::ItemsController because Ruby can find ItemsController in the Staff class:

When the application is run, sometimes Staff::ItemsController can be referred to after definition and sometimes referred to before definition. The former leads to expected behavior and the latter leads to unexpected behavior. This is the hidden bug.

This problem can also occur in Rails applications. When you run Staff.ancestors in the rails console, you can see a lot of classes and modules. But the Object class does exist in ancestors. So this means that Staff::ItemsController can refer to Object::ItemsController when Staff::ItemsController is not defined.

Use different names for your model class and your namespace

To avoid this problem, you should use different names for your model class and your namespace, for example, Staffs::ItemsController.

If Staffs::ItemsController is defined in the app/controllers/staffs directory and the constant Staffs is not defined yet, Rails defines the Staffs module automatically. (This function is called “Automatic Modules”. Please see Rails Guides for details.)

When the namespace is a module, the constant lookup leads to different results from the class namespace, because the module does not have ancestors other than itself. Let us see the results in irb:

This time, Staffs::ItemsController was considered to be an uninitialized constant. This is because the Staffsmodule does not have Object in its ancestors. So Ruby won’t look up Object::ItemsController and the constant lookup fails.

Rails can load Staffs::ItemsController without errors

In irb Staffs::ItemsController is an uninitialized constant and Ruby produces an error, but this scenario is different in Rails applications. Rails implements the const_missing hook and tries to find Staffs::ItemsController in autoload_paths. If Staffs::ItemsController is defined in autoload_paths like app/controllers/staffs/, Rails can load Staffs::ItemsController without any errors. Please see Autoloading and Reloading Constants in Rails Guides for details.

NOTE: The problem described in this post might be fixed in the future

The top-level constant lookup rule in Ruby might be changed in a future version, because the topic “remove top-level constant lookup” is being discussed here:

https://bugs.ruby-lang.org/issues/11547

Please check it out if you are interested.

By Junichi Ito.

Have a Ruby/Rails case to share with the community? Let us know in the comments below!

Your RubyMine Team

This entry was posted in Community and tagged . Bookmark the permalink.

One Response to Why you Should not use a Class as a Namespace in Rails Applications

  1. PikachuEXE says:

    Yup to avoid this I have tried this too:
    ruby
    module Staffs
    class ItemsController
    end
    end

    But in the end I try to do DDD instead:
    ruby
    # Apps::MyWebsite is just a namespace
    # that should never be used by gems
    # You can use whatever that works
    #
    # StaffItemListPage is how you name the page type (not route) internally
    # Naming it is sometimes quite hard
    # Also the URL does NOT represent the page type
    # It can be moved to another URL later
    #
    # I also define one controller class per action like Hanami
    module Apps::MyWebsite::StaffItemListPage::Show
    class ActionController
    end
    end

Leave a Reply

Your email address will not be published. Required fields are marked *