ActiveModel: Autoloading Digression

Having just returned from vacation I now continue my read through of the Ruby on Rails source code by looking at Active Model.

With this series of posts I’m going to do things a little differently, you see, I’ve already read all of Active Model but haven’t written anything yet. I want to take a more structured approach as I write these articles which will hopefully make it a good entry point for others looking for a more thorough overview of how Rails works.

Sound good? Alright, let’s dig in!

What is ActiveModel?

If you are relatively new to Ruby on Rails, it is possible that you may be asking yourself whether I have a typo in my header and actually meant Active Record.

No worries, Active Model is more of an abstract interface that is meant to describe the common pattern of interfacing with Action Pack, that is, the V and C parts of MVC. Active Record thus implements Active Model’s interfaces as well as add a host of unique functionality.

Active Model provides a known set of interfaces for usage in model classes. They allow for Action Pack helpers to interact with non-Active Record models, for example. Active Model also helps with building custom ORMs for use outside of the Rails framework. Rails Docs

So what you will see here with Active Model is a common set of things that need to be implemented by every ORM designer, such as Mongoid and MongoMapper, as well as Gems that have a storage related purpose (like Paperclip and Carrierwave).

This means our study of Active Model will be a good first step to being able to make sense of the pretty sprawling code base which is Active Record.

ActiveModel Module

The table of contents, if you will, is found in the top level file that defines this module. Within we find a DSL of sorts, for requiring the different component modules through an autoload mechanism defined in Active Support.

Here it is:

module ActiveModel
  extend ActiveSupport::Autoload

  autoload :AttributeMethods
  autoload :BlockValidator, 'active_model/validator'
  autoload :Callbacks
  autoload :Conversion
  autoload :Dirty
  autoload :EachValidator, 'active_model/validator'
  autoload :ForbiddenAttributesProtection
  autoload :Lint
  autoload :Model
  autoload :Name, 'active_model/naming'
  autoload :Naming
  autoload :SecurePassword
  autoload :Serialization
  autoload :TestCase
  autoload :Translation
  autoload :Validations
  autoload :Validator

  eager_autoload do
    autoload :Errors

  module Serializers
    extend ActiveSupport::Autoload

    eager_autoload do
      autoload :JSON
      autoload :Xml

  def eager_load!

Autoload Digression

The autoload mechanism is pretty neat actually so let’s take a look at that first.

module ActiveSupport
  module Autoload
    def autoload(const_name, path = @_at_path)
      unless path
        full = [name, @_under_path, const_name.to_s].compact.join("::")
        path = Inflector.underscore(full)

      if @_eager_autoload
        @_autoloads[const_name] = path

If @_eager_autoload is set (which happens in an eager_autoload block, we throw the constant and path to load it into the @_autoloads hash.

      super const_name, path

    def eager_autoload
      old_eager, @_eager_autoload = @_eager_autoload, true
      @_eager_autoload = old_eager

    def eager_load!
      @_autoloads.values.each { |file| require file }


Finally, when we call eager_load! it only loads the modules we have specifically designated in eager blocks.

What isn’t immediately clear is how it handles those that are not eager autoloaded. This is found in Active Support:

module ActiveSupport #:nodoc:
  module Dependencies #:nodoc:
    def hook!
      Object.class_eval { include Loadable }
      Module.class_eval { include ModuleConstMissing }
      Exception.class_eval { include Blamable }
    module ModuleConstMissing #:nodoc:
      def const_missing(const_name)
        from_mod = anonymous? ? ::Object : self
        Dependencies.load_missing_constant(from_mod, const_name)
    def load_missing_constant(from_mod, const_name)
      # cool code which looks for circular dependencies and other
      # possible cases and loads the file otherwise, remembering those it
      # cannot load, read it sometime :)

While there is a whole lot more code than what I’ve snipped here, this should give you a trail to follow to understand how it works.

Basically we hook into Object, Module, and Exception and include these Active Support dependency resolution modules. In the case of Module, we include ModuleConstMissing, which has some very interesting logic in it that ultimately decides whether the file can be loaded, is unable to be loaded, or requires other files to be loaded along with it. It’s an interesting read.

Very clever bit of lazy loading of constants, nicely done Rails!

That is enough for today, next up, the table of contents of Active Model.

Published under programming