Skip to navigation

Consolidating your app’s constants

Published May 18, 2007 - Updated May 22, 2007

Sprinkling constants doth maketh a mad-man

You’ve a bunch of configuration constants in your application (email addresses, host addresses, etc) and you’d like a nice, cozy place to keep them, and a method of overriding the defaults for each environment.

There’s a million ways to skin this cat, but here’s the simple approach I took in my latest app.

...and yes, a constant that changes is not a constant, but I’ll pretend you didn’t say that.

1st update: I originally had a typo: the variables should be declared with @@, not @.

2nd update: It’s active_support, not activesupport!

Creatin tha module

Firstly, we’ll set up a new module to store our config, let’s call it MyAppConfig and it can live in RAILS_ROOT/config/my_app_config.rb. In it, we’ll create a standard Ruby module:


module MyApp
  module Config
  end
end

Hookin it in

To allow each environment to customise the config we’ll want to set this up before Rails::Initializer#run is called within environment.rb (see my previous article Environments and the Rails Initialisation Process for more info).

We’ll simply require the file after boot in our environment.rb:

require File.join(File.dirname(__FILE__), 'boot')

require 'my_app_config'

Rails::Initializer.run do |config|
  # ...

Next we’ll add a configurable variable, with a default, to our module:

module MyApp
  module Config
    @@paypal_host = "https://www.sandbox.paypal.com" 
  end
end

This works just fine, apart from the fact you can’t access the instance variable outside of the module:

irb> MyApp::Config.paypal_host
NoMethodError: undefined method `paypal_host' for MyApp::Config:Module

To access and modify the instance variable we need a set of good ‘ol accessors. If this were a class, not a module, we’d sprinkle an attr_accessor and be on our way, but as this is a module we have to define the methods ourself.

module MyApp
  module Config
    @@paypal_host = "https://www.sandbox.paypal.com" 
    def self.paypal_host
      @@paypal_host
    end
    def self.paypal_host=(paypal_host)
      @@paypal_host = paypal_host
    end
  end
end
irb> MyApp::Config.paypal_host
=> "https://www.sandbox.paypal.com"

Sprinklin the sexy

Ok, so far it’s not looking too pretty is it? Don’t worry… good ‘ol ActiveSupport ships with a nice little helper, mattr_accessor, which provides exactly the same functionality as Ruby’s attr_accessor but for modules. Unfortunately the author chose for it a life of seclusion, with a tattoo of :nodoc:, but that’s never stopped us brave venturers before.

ActiveSupport isn’t loaded until the Rails::Initializer#run method is executed, so we’ll also need to require 'activesupport' before we can use mattr_accessor:

require 'active_support'

module MyApp
   module Config
    mattr_accessor :paypal_host
    @@paypal_host = "https://www.sandbox.paypal.com" 
  end
end

So now we have our environment.rb requiring this module and we can access it anywhere in our Rails application with a simple MyApp::Config. paypal_host.

Overidin the defaults

To override config vars in different environments you can simply call its mattr_writer. For example, we’ll want to use the live paypal server in production, so we add the following line to the bottom of RAILS_ROOT/config/environments/production.rb:

MyApp::Config.paypal_host = "https://www.paypal.com"

and voila! That’s all there is to it.

So you wanna get funky

...and here’s some exercises for the reader (all of which are a purely academic pursuit I’d most probably not bother using):

  • Extending Module with mattr_accessor_with_default
  • Extending Rails::Config to provide a my_app method, so instead of MyApp::Config.paypal_host in your environments you could just use my_app.paypal_host
  • Implement MyApp.config(&block) for block-style configuration

Archived comments

Comments were previously allowed on articles. Though no new comments are being accepted you can see the old comments below.

  1. Gabe da Silveira

    I dig this approach. In order to get an app up and running quickly I recently had to do this:

    ssl_required :checkout, :process_payment if RAILS_ENV == ‘production’

    Mostly because I just didn’t have time to get Apache proxying to my development mongrel for local SSL testing.

    But after seeing this I’m thinking making SSL a configurable option would be a reusable approach since any of the three environments may or may not support SSL in any given scenario.

  2. Tim Lucas

    Gabe: Exactly. Not to mention if you added more environments, such as staging. In this app I used the config to specify what protocol to use for default_url_options in my ActionMailer’s:

    
    module TicketSystem
      module Config
        # Protocol used when generating URLs in ActionMailers
        mattr_accessor :mailer_protocol
        @@mailer_protocol = "http" 
      end
    end
    
  3. maxm

    And if you’re too lazy to repeat the attribute name, throw this in at the end:

    
    module Config
     @@some_var="some value" 
    
      class_variables.each do |v|
        mattr_accessor v[(2..-1)]
      end
    end
    
  4. Tim Lucas

    mr max, dat, is sexy.

  5. Dr J

    I’m getting an error trying to start my mongrel process because it is choking on the activesupport include line:

    require 'activesupport' /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:21:in `require__': no such file to load -- activesupport (LoadError) from /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:21:in `require' from /home/cjolicoeur/rp_badge_signup/trunk/config/../config/my_app_config.rb:1 from /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:21:in `require__' from /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:21:in `require' from /home/cjolicoeur/rp_badge_signup/trunk/config/environment.rb:19 from /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:21:in `require__' from /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:21:in `require' from /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/mongrel/rails.rb:155:in `rails' ... 8 levels... from /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/mongrel/command.rb:211:in `run' from /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/bin/mongrel_rails:243 from /usr/bin/mongrel_rails:18:in `load' from /usr/bin/mongrel_rails:18

    any thoughts?

  6. Tim Lucas

    Whoops sorry Dr J, that should read require 'active_support'

  7. EP

    Thanks for this. It was very helpful!

  8. Greg Clarke

    Very helpful post Tim. The info here plus your “Environments and the Rails initialisation process” helped me my solve my config problems and that has made my day. Thanks very much.

  9. Justin Winkler

    I’ve tried this method, both with rails v1.2.3 and v2.0.2, and in both instances, my module is populated as expected with my first hit to a controller, but in all subsequent hits all the properties on the Module are nil’d our, or set to whatever is defined in the Module, essentially losing any “override” set in my specific environment file. Did you run into anything like this?

  10. Justin Winkler

    Found the issue; it’s due to the config.cache_classes=false within development.rb. The problem is that the Module containing the configuration is reloaded with every request, but the values set within development.rb are not. I really like your approach, but it seems difficult to use in development due to this issue. Would be interesting to see if somehow the Module could be cached, or if the the development configuration could be placed in some location that would cause it to be reloaded along with the Module itself, rather than declaring development configuration directly on the Module. Keep up the good work, very good information! :)

  11. Tim Lucas

    @Justin Winkler did you “require” the file from environment.rb? Rails 2 only reload files that have been auto-required, anything manually required shouldn’t be reloaded on every request.

  12. Justin Winkler

    You are correct sir! :) I was not aware that’s how rails reloaded classes. Thanks much!

  13. Samir

    Thanx. Works like a charm. Much appreciated!

  14. Dave H

    Worked for me too, thanks. The n00b mistake I made was that my app has the same name as some of my models, and so when I followed the naming convention I ended up with several name conflicts. I resolved the conflict by naming the Modules something other than the Model name. Thanks!

More reading…

Previously: Breaking hearts

Next up: 2007’s Mad Month of Events

Articles

toolmantim

I'm Tim Lucas, a user experience designer and developer currently in Sydney Australia.

I run a web application design and development company Agency Rainford, present on various technical topics, snap the occasional photo, tweet my going-ons, share teh codes and post other tid-bits to the tumble.

Most recently I published LAN hacking with Bananajour (June 11, 2009)

Shoot an email to and say hello.

Powered by tim tams and coffee