Configuration for Rails, the Right Way

I still see people promoting various gems and plugins to handle miscellaneous configuration elements for your application. One little known secret is that Rails 3 allows you to define your own configuration elements trivially.

In this case, I wanted to use the nifty wkhtmltopdf utility to create a PDF. I was able to call the binary just fine with Homebrew on OSX but found that I had to use a custom binary checked into git for our production environment on Heroku. So I created a configuration variable to store where wkhtmltopdf could be found in the current environment.

First, we define a default value for all environments in config/application.rb:

module Configurator
  class Application < Rails::Application
    # By default, let OSX resolve the path to the binary
    config.wkhtmltopdf = "wkhtmltopdf"
  end
end

Then we override the default setting as necessary in config/environments/:

Configurator::Application.configure do
  # Settings specified here will take precedence over those in config/application.rb

  # Point Heroku explicitly to the binary we need to use
  config.wkhtmltopdf = "#{Rails.root}/bin/wkhtmltopdf"
end

Lastly, we access the configuration element in our code:

  cmd = [Configurator::Application.config.wkhtmltopdf, url, tmpfile.path]

Yes, that’s it. Just use Rails’s environment support and config to store your own configuration elements. They’re trivial to set, trivial to access and require no third-party gems or custom text files.

This entry was posted in Everything Else. Bookmark the permalink.
  • http://jasoncrawford.org jasoncrawford

    One issue with this is that I find myself wanting to group my config elements together. E.g., say I’m writing an app that uses Urban Airship, and I have my own client object that encapsulates their API. I want to configure our app key and secret for dev and prod environments. Using this method I *could* do:

    config.urban_airship_application_key = “abcdefg”
    config.urban_airship_master_secret = “0123456”

    But what I’d rather do is something mirroring, e.g., ActionMailer configuration:

    config.urban_airship.application_key = “abcdefg”
    config.urban_airship.master_secret = “0123456”

    Yeah, I know, it’s just a dot instead of an underscore, but conceptually it’s tidier.

    That doesn’t work as is, so what I’ve ended up doing is defining cattrs on classes that I want to configure, and then doing this:

    config.after_initialize do
    UrbanAirshipClient.application_key = “abcdefg”
    UrbanAirshipClient.master_secret = “0123456”
    end

    Then inside the class, I define the cattrs and use them:

    class UrbanAirshipClient
    cattr_accessor :application_key, :master_secret

    # code just refers to @@application_key and @@master_secret
    end

    Keeps config params bundled with the classes they affect.

    That’s my solution so far; would love to hear better/alternate solutions.

  • Anonymous

    When I started a green-field Rails 3 project a year ago, everyone was explaining how Configuration was an enormous and scary problem to be solved. I was getting pitched everything from Gems, to NoSQL DB’s, to RDBMS’s, etc. In looking at what they had in place, I mentally pieced together the chronology of their former solutions. In its first incarnation, years back, it was simply a SQL table of key-value pairs. (I was pleased to see key-value approach rather than eternally growing columns.) The problem was there was no RAM persistence. This was getting hit every time a Config value was referenced in code. Some time elapses, and someone realized how crippling that was to their app, and decided to solve the problem with MemCacheD! [MemCacheD itself was not the problem. It was how it was incorrectly applied.] They loaded all the config values from SQL into MemCacheD upon app startup. They implement yet-another, entirely separate persistence mechanism to manage and maintain. This was horribly confusing to every single dev that ever worked on the project. Naturally, new devs would find themselves changing values in the Config DB, but seeing no change after they restarted the application. I can’t count the # of hours wasted on that alone. That system is still in place, and I witnessed the same thing happen to 2 new devs this year.

    Anyway, to all these various “solutions” people were pitching me, I replied, “The most reliable, leanest, and fastest data-storage mechanism already exists in every programming language…. [dramatic pause] RAM, and an object.”

    All I could think was, “Are developers aware that one prime function of an object is to hold data?” They’ve done that since objects were invented in the 80’s. Even without objects, RAM itself has been the easiest thing to access since the Eniac.

    I wanted things in YAML so it was a little more human readable, and could be edited with the ease of editing a file. I wanted to make it polymorphic based on enviro. And I wanted to have dynamic portions [Rails.root, etc], so I pass it through the ERB parser. Here’s config/config.yml:


    # All environments:
    defaults: &defaults
    some_key: some_value
    another_key: another_value
    upload_dir: /media

    # Enviro-Specific:
    development:
    <<: *defaults
    www_user: _www
    www_group: adm
    upload_dir: /public/uploads

    production:
    <<: *defaults
    www_user: www-data
    www_group: admin

    Then I wrote a tiny initializer to load this and make it a global object:


    class Settings
    raw_config = File.read("#{::Rails.root.to_s}/config/config.yml")
    erb_config = ERB.new(raw_config).result
    settings = YAML.load(erb_config)[::Rails.env]

    if settings
    settings.each { |name, value|
    instance_variable_set("@#{name}", value)
    self.class.class_eval { attr_reader name.intern }
    }
    end
    end

    That’s it. Now all config values are available everywhere as:


    Settings.upload_dir

    Sits in RAM. Super-fast. Loaded once. No MemCacheD to magically know exists and somehow know to restart. No SQL.

    • narkoz

      Every time after changes in config you must restart app that way. Not good.

      • Anonymous

        With the solution in the post you have to restart the app as well, no?

        • narkoz

          Some gems (like settingslogic) allow you to reload settings without restarting the app.

          • Qweqwewq

            With Unicorn’s preload_app you can restart your app with 0 downtime, just send USR2 signal to the master process.

      • http://www.reedglaw.com Reed G. Law

        I’m using @iq9’s solution and it works great. For testing, you can change/delete a setting like so: Settings.instance_variable_set(:@setting_name, false). You could also add a class method to reload the settings.

    • thecleaninglady

      What file is the Settings class saved in? Would that be app/models/settings.rb?

      • iq9

        config/initializers/config.rb

  • http://www.facebook.com/profile.php?id=20801613 Jay Levitt

    I’ve been doing much the same thing, but rather than creating my own Configurator module, I used the existing Rails app’s module, so I get:

    Rails.application.config.facebook_app_id

    Feels slightly cleaner.

    • Svoop

      I prefer an extra level to make collisions with future Rails development less probable:

      Rails.application.config.x.facebook_app_id

      Think of the “x” as a prefix similar to extra mail headers.

      • http://www.facebook.com/huukhoanguyen Khoa Nguyen

        …/gems/railties-3.1.3/lib/rails/railtie/configuration.rb:78:in `method_missing': undefined method `x’ for # (NoMethodError)

  • Anonymous

    This doesn’t feel clean to me for a couple of reasons:
    * Everything else you configure in application.rb is kinda standard Rails configuration, so I wouldn’t want to include my application-specific stuff in there.
    * I want to be able to specify passwords and other sensitive information and not check those in to my version control system.
    * I prefer defining my configs in YAML rather than code, so even non-technical people could edit them.
    * I don’t want my configs to be split up in several files.

    I wrote my own app config (like Configatron) a while ago and have been improving and reusing it ever since. It sits in lib/app_config.rb and loads config/app_config.yml.

  • craig wickesser

    I like using Settings Logic (https://github.com/binarylogic/settingslogic). I put all config into a YAML file (which is setup with dev, test and prod blocks) and then load it up. Then I can just call “Settings.facebook_app_id”, or whatever.

    I like this b/c all of my config is in one file that I can choose to keep out of version control if I need to. I can even load more than one file if I need to.

    And, adding a single gem as a dependency isn’t an issue…my normal rails apps use lots of gems anyways.

    However, I do appreciate Mike pointing out the ability to use the built-in Rails config, never thought about that approach :)

  • Ssws

    вуву

  • Pingback: Last Week’s Top Ruby News: Rails 3.1.3, autoload deprecated, and conferences

  • Pingback: Chris Oliver » Rails Tip #1: Clean Configuration Values

  • http://twitter.com/pederbl Peder Linder

    What would you do with a configuration string that contains non-ascii characters, e.g. the name of the CEO? I guess you could set “encoding: utf-8″ for the application.rb file but I don’t like the idea of having files with different encoding specifications in my project.

  • http://kindkid.myopenid.com/ Chris

    Hi Mike,
    Good call. I’ve tried this approach and liked it, except for dealing with accidental code checkins of secret api keys etc. Like others, I too have written my own gem, which has worked well for my team. https://github.com/kindkid/constantinople

    • Mike Perham

      Chris, I love the mapping in Constantinople from top-level namespace to yml filename:

      Constantinople.foo.bar -> config/foo.yml

      Simple but powerful idea.

  • Pingback: Community News Round Up | New Relic blog

  • http://gerhardlazu.com gerhardlazu

    I feel having app-specific configs available through the user environment under which the app runs makes a lot more sense. Heroku got this right. If you’re using Chef or Puppet to prep servers for app deployment (you are, right?), that’s the only place where you want the actual values stored. An app is no longer this single Rails instance which contains all its configs, but a myriad of small services that work independently and require to know only so much. With this approach, if you change a value and re-cook the servers, they make the new values available instantly, not even a USR2 required.

  • http://www.span6.com Khurram Virani
  • Pingback: Trevor Turk — Links for 12-9-2011

  • Pingback: Ruby On Rails (2): Links, News and resources « Angel “Java” Lopez on Blog

  • Pingback: Ruby’s Singleton and Custom Rails Application Configuration | Coding Daily

  • http://twitter.com/RobertReiz Robert Reiz

    I used the example from iq9. That worked pretty good. But the settings.rb in the “config/initializers” directory is called AFTER application.rb, environment.rb, development.rb, test.rb and production.rb. If I want to use the Settings.xxx in one of this files, it didn’t worked. I just put the Settings class directly into the “environment.rb” before “MyApp::Application.initialize!”. In that way I can use the Settings.xxx in all environments files, too.

  • Martin

    Nice article! Thanks Mike.

  • Pingback: Include a Module Based on Rails Environment – candland.net