Friday, October 12, 2012

Rails 3 and Module Autoloading

Rails 3.2.8 at least won't complain if a module is defined in the wrong place where it will not be autoloaded. This can be a problem.

In app/controllers/application_controller.rb, I had:

class ApplicationController < ActionController::Base
  include Snafu::Controller
end
in app/controllers/foo/bar_controller.rb:
class Foo::BarController < ApplicationController
  acts_as_snafu
end
in app/controllers/snafu/config.rb:
# note: at first I tried putting this into but unless it is named same as module on root path it won't auto re-load, which means we we made changes to app/controllers/snafu/controller.rb then the Snafu::OPTIONS.each do |key| reference to OPTIONS constant would fail to resolve whenever we made a request to Foo::BarController after that change.

module Snafu
  OPTIONS = [:test]
  # ...
end
in app/controllers/snafu/controller.rb:
require 'snafu/config' # this is a bad sign because it is in a module that is part of the app that needs to be in the right place so it can be autoloaded

module Snafu
  module Controller
    extend ActiveSupport::Concern

    module ClassMethods
      def acts_as_snafu(options = {})
        include ActsAsSnafu
      end
    end
   
    module ActsAsSnafu
      extend ActiveSupport::Concern

      included do
        Snafu::OPTIONS.each do |key|
          # ..
        end
      end

       #..
    end
  end
end

As you see, requiring a module defined in the app may be a bad sign, because it might be that it wasn't autoloaded, which means it won't get loaded later when it needs to be.

This resulted in the request to the implementing controller failing to find missing constant Snafu::OPTIONS after we would make a change to app/controllers/snafu/controller.rb.

This was fixed by moving app/controllers/snafu/config.rb to app/controllers/snafu.rb. At first I changed the require 'snafu/config' to require 'snafu' in the module app/controllers/snafu/controller.rb, and then got warning: already initialized constant because app/controllers/snafu.rb was being loaded twice on Rails start during autoload. Removing require 'snafu' app/controllers/snafu/controller.rb then was the final fix.

If you are wondering why the module overkill- we had moved from using a Gem to using those same modules in our application code because keeping working on both at the same time required too much overhead.

No comments: