#85 YAML Configuration File
Dec 23, 2007 | 7 minutes |
Tools
Application configuration shouldn't be spread throughout your code base. Instead a much better place to put it is an external YAML file. See how to do that in this episode.
- Download:
- mp4Full Size H.264 Video (11.2 MB)
- m4vSmaller H.264 Video (7.3 MB)
- webmFull Size VP8 Video (23.4 MB)
- ogvFull Size Theora Video (15.9 MB)
I'm using the following in `config/initializers/app_config.rb`:
require 'ostruct'
require 'yaml'
# Load application configuration
config = OpenStruct.new(YAML.load_file("#{RAILS_ROOT}/config/application.yml"))
::AppConfig = OpenStruct.new(config.send(RAILS_ENV))
Now I can use it in my application like this
AppConfig.perform_authentication
i do something very similar to this with my .yml files. if you don't want to commit your username/password in your repository like me, you can use a capistrano task like
task :create_config_config do
set :a_username, Capistrano::CLI.password_prompt('admin_username: ')
set :a_password, Capistrano::CLI.password_prompt('admin_password: ')
contents = render_erb_template(File.dirname(__FILE__) + "/templates/config.yml.erb")
put contents, "#{shared_path}/config/config.yml"
end
I've been doing this for a while, too. This is what I do to get ERB in my YAML (like in fixtures). It also uses ostruct as described by thom above.
require 'ostruct'
raw_config = File.read("#{RAILS_ROOT}/config/config.yml")
erb_config = ERB.new(raw_config).result
config = YAML.load(erb_config)[RAILS_ENV]
Site = OpenStruct.new(config)
I can now do stuff like
foo_path: <%= RAILS_ROOT %>/tmp/foo
Why do we need to use this method when Rails 2.0 has initializers? I can declare all my configuration under initializers directory, right?
I could have s3_config, mailer settings etc in its own initializer.
if i add the first line:
APP_CONFIG = YAML.load_file("#{RAILS_ROOT}/config/config.yml")[RAILS_ENV]
then:
=> Booting Mongrel (use 'script/server webrick' to force WEBrick)
=> Rails application starting on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
** Starting Mongrel listening at 0.0.0.0:3000
** Starting Rails with development environment...
Exiting
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/yaml.rb:133:in `load': syntax error on line 7, col 11: `' (ArgumentError)
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/yaml.rb:133:in `load'
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/yaml.rb:144:in `load_file'
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/yaml.rb:143:in `open'
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/yaml.rb:143:in `load_file'
from /Users/michaelvoigt/Documents/private/projects/kraeftemessen/trunk/kraeftemessen/rails/kraeftemessen/config/environment.rb:84
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require'
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/rubygems/custom_require.rb:27:in `require'
from /Library/Ruby/Gems/1.8/gems/activesupport-2.0.2/lib/active_support/dependencies.rb:496:in `require'
... 23 levels...
from /Library/Ruby/Gems/1.8/gems/rails-2.0.2/lib/commands/server.rb:39
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require'
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/rubygems/custom_require.rb:27:in `require'
from script/server:3
I have foregot the require 'yaml', SORRY!
What happened to Convention over Configuration? So if you have a bunch of lil configuration options, whats the point from deviating away from what Rails gives us for configuration options? I'm lost...
I've always wanted to find a way to not have any passwords in my SVN. But, even with your suggestion, how would I do that when e.g. using deprec (the cap deply taks available at deprec.org)?
The only solution I can see is completely ignoring the config.yml in the SVN and keeping it far away from the app directory (as something like /var/www/apps/appname/current is replaced on each deploy). So, should one put it in /etc/ instead, or is there a nicer solution?
With HTTP Basic authentication, can you also use OpenID? A screencast on that would be tremendously helpful. Thanks for the great work!
I love these kinds of railscasts - being recipes instead of re-iterations of stuff you can find in the API etc. Keep 'em coming!
What about putting configuration in the database? I know wordpress does something like this and I'd be interested in what the Rails community thinks about it.
Since we are loading configs on server startups,
Is there anyway to change & reload config file/table without server restarts ?
This is a pretty common app configuration pattern but for me it needs some rubification. I do pretty much the same thing but don't use a constant Hash, rather a class to manage the settings:
In lib/configuration.rb:
class Configuration
@@settings = YAML::load_file('config/myconfig.yml')[RAILS_ENV]
class MissingConfigOptionError < StandardError; end
def self.method_missing(key)
raise MissingConfigOptionError("#{key.to_s} is not in the config file") unless @@settings.include?(key.to_s)
@@settings[key.to_s]
end
end
That's it, there is nothing else to do. Create a config/myconfig.yml file with all the same stuff as in this railscast:
In config/myconfig.yml:
development:
foo: "bar"
production:
foo: "yummy"
To fetch and use the settings anywhere in your code, model, controller or view do:
x = Configuration.foo # => "bar"
Customize to suit. Maybe store the settings with mem_cache in a production environment and have a rake task to reload it or handle missing config options differently with perhaps a reasonable default (not a good idea IMHO) or add a test for the existence of a given key.
You could also make this a plugin without a problem by putting it in vendor/plugins/my_config_hotness/init.rb instead of lib/configuration.rb. Six and one half dozen.
Enjoy.
PS. This is not my idea. Someone a lot smarter than I put me on to it but went even further to allow for nested groups:
development:
foo: "bar"
bar:
foo: "yummy"
x = Configuration.bar.foo # => "yummy"
But I can't for the life of me find the page where I saw it done and I guess I failed to bookmark it :(
Oh, I forgot to mention that the config file doesn't get loaded until the first time you try to access a setting (lazy loading). If you don't fetch a config setting value with Configuration.xxx it adds zero overhead. Ruby gives you this for free.
Can this be used with Helpers?
If so, what am I missing?
I'm getting a template error when using a method defined in application_helper.rb to return the key in that appears to be nil.
You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.[]
Thanks.
Yes it does work with helpers provided that you actually load the correct part of the yaml file.
Thank again, Ryan!
I've started naming my initializer "_site.rb". The initializers are loaded alphabetically, and you want this one to be first, if you ever use the configuration values in other initializers (e.g. initializers/mail.rb for mailer settings).
I am experiencing the same difficulty as No. 11: Michael Dec 25, 2007.
In 12 Michael Dec 25 he states "I have forgotten the require 'yaml', SORRY!"
Can you tell me what he is referring to and where to place the 'require yaml'
Thank you all WHAT A GREAT RESOURCE!
I've followed the RailsCast and have settings that look like this:
development:
images_path: \\sysimages\2009\
when the initializer runs, it fails with an error that suggest that it doesn't allow for the backward slashes. How do store a string that is a path in a YAML file that Ruby can load? Thanks in advance
I need to store settings by environment but started off simply with my config file at: \config\config.yml. The only setting I had was this:
attachment_volume: \\attach\transfers
With this, cod I'm able to load the file within my initializer and reference it just fine from calling code within an ActiveRecord model class. So far so good.
Since this setting is a server location that varies by environment, I tried to use the by-environment structure and changed my config file to this:
development:
attachment_volume: \\attach\transfers
test:
attachment_volume: \\attach\test\transfers
I get the following error during that indicates it didn't like the second instance of the colon on the second line. Here is the real error: Uncaught exception: syntax error on line 1, col 20: ` attachment_volume: \\atlas\transfer\greenlight\predevelopment\attachments'
Here is full error stack:
Exiting C:/Ruby/lib/ruby/1.8/yaml.rb:133:in `load'
C:/Ruby/lib/ruby/1.8/yaml.rb:133:in `load'
C:/Ruby/lib/ruby/1.8/yaml.rb:144:in `load_file'
C:/Ruby/lib/ruby/1.8/yaml.rb:143:in `open'
C:/Ruby/lib/ruby/1.8/yaml.rb:143:in `load_file'
C:/TFS/GreenLight/DEV/server/config/initializers/load_config.rb:2
C:/TFS/GreenLight/DEV/server/vendor/rails/activesupport/lib/active_support/dependencies.rb:142:in `load_without_new_constant_marking'
C:/TFS/GreenLight/DEV/server/vendor/rails/activesupport/lib/active_support/dependencies.rb:142:in `load'
C:/TFS/GreenLight/DEV/server/vendor/rails/activesupport/lib/active_support/dependencies.rb:521:in `new_constants_in'
C:/TFS/GreenLight/DEV/server/vendor/rails/activesupport/lib/active_support/dependencies.rb:142:in `load'
./script/../config/../vendor/rails/railties/lib/initializer.rb:550:in `load_application_initializers'
./script/../config/../vendor/rails/railties/lib/initializer.rb:549:in `each'
./script/../config/../vendor/rails/railties/lib/initializer.rb:549:i
n `load_application_initializers'
./script/../config/../vendor/rails/railties/lib/initializer.rb:167:in `process'
./script/../config/../vendor/rails/railties/lib/initializer.rb:112:in `send'
./script/../config/../vendor/rails/railties/lib/initializer.rb:112:in `run'
C:/TFS/GreenLight/DEV/server/config/environment.rb:16
C:/Ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
C:/Ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
C:/TFS/GreenLight/DEV/server/vendor/rails/activesupport/lib/active_support/dependencies.rb:153:in `require'
C:/TFS/GreenLight/DEV/server/vendor/rails/activesupport/lib/active_support/dependencies.rb:521:in `new_constants_in'
C:/TFS/GreenLight/DEV/server/vendor/rails/activesupport/lib/active_support/dependencies.rb:153:in `require'
C:/Ruby/lib/ruby/gems/1.8/gems/mongrel-1.1.5-x86-mswin32-60/bin/../lib/mongrel/rails.rb:147:in `rails'
C:/Ruby/lib/ruby/gems/1.8/gems/mongrel-1.1.5-x86-mswin32-60/bin/mongrel_rails:113:in `cloaker_'
C:/Ruby/lib/ruby/gems/1.8/gems/mongrel-1.1.5-x86-mswin32-60/lib/mongrel/configurator.rb:149:in `call'
C:/Ruby/lib/ruby/gems/1.8/gems/mongrel-1.1.5-x86-mswin32-60/lib/mongrel/configurator.rb:149:in `listener'
C:/Ruby/lib/ruby/gems/1.8/gems/mongrel-1.1.5-x86-mswin32-60/bin/mongrel_rails:99:in `cloaker_'
C:/Ruby/lib/ruby/gems/1.8/gems/mongrel-1.1.5-x86-mswin32-60/lib/mongrel/configurator.rb:50:in `call'
C:/Ruby/lib/ruby/gems/1.8/gems/mongrel-1.1.5-x86-mswin32-60/lib/mongrel/configurator.rb:50:in `initialize'
C:/Ruby/lib/ruby/gems/1.8/gems/mongrel-1.1.5-x86-mswin32-60/bin/mongrel_rails:84:in `new'
C:/Ruby/lib/ruby/gems/1.8/gems/mongrel-1.1.5-x86-mswin32-60/bin/mongrel_rails:84:in `run'
C:/Ruby/lib/ruby/gems/1.8/gems/mongrel-1.1.5-x86-mswin32-60/lib/mongrel/command.rb:212:in `run'
C:/Ruby/lib/ruby/gems/1.8/gems/mongrel-1.1.5-x86-mswin32-60/bin/mongrel_rails:281
C:/TFS/GreenLight/DEV/server/vendor/rails/activesupport/lib/active_support/dependencies.rb:142:in `load_without_new_constant_marking'
C:/TFS/GreenLight/DEV/server/vendor/rails/activesupport/lib/active_support/dependencies.rb:142:in `load'
C:/TFS/GreenLight/DEV/server/vendor/rails/activesupport/lib/active_support/dependencies.rb:521:in `new_constants_in'
C:/TFS/GreenLight/DEV/server/vendor/rails/activesupport/lib/active_support/dependencies.rb:142:in `load'
C:/TFS/GreenLight/DEV/server/vendor/rails/railties/lib/commands/servers/mongrel.rb:64
C:/Ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
C:/Ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
C:/TFS/GreenLight/DEV/server/vendor/rails/activesupport/lib/active_support/dependencies.rb:153:in `require'
C:/TFS/GreenLight/DEV/server/vendor/rails/activesupport/lib/active_support/dependencies.rb:521:in `new_constants_in'
C:/TFS/GreenLight/DEV/server/vendor/rails/activesupport/lib/active_support/dependencies.rb:153:in `require'
C:/TFS/GreenLight/DEV/server/vendor/rails/railties/lib/commands/server.rb:49
C:/Ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
C:/Ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
./script/server:3
C:/Ruby/lib/ruby/gems/1.8/gems/ruby-debug-ide-0.4.4/lib/ruby-debug.rb:101:in `debug_load'
C:/Ruby/lib/ruby/gems/1.8/gems/ruby-debug-ide-0.4.4/lib/ruby-debug.rb:101:in `debug_program'
C:/Ruby/lib/ruby/gems/1.8/gems/ruby-debug-ide-0.4.4/bin/rdebug-ide:82
C:\Ruby\bin\rdebug-ide:19:in `load'
C:\Ruby\bin\rdebug-ide:19
-e:2:in `load'
-e:2
Uncaught exception: syntax error on line 1, col 20: ` attachment_volume: \\atlas\transfer\greenlight\predevelopment\attachments'
Has anyone been able to get the by environment structure load to work? Please advise? Thanks in advance.
Thanks for the great screencast!
I recently started using configatron for this kind of configuration:
http://github.com/markbates/configatron/tree/master
Highly recommended!
settingslogic does work for that too
http://github.com/binarylogic/settingslogic
Thanks for the great example
Threw it into a gem if anyone is interested
http://rubygems.org/gems/config-file-loader
In Rails 3 application path is Rails.root instead of RAILS_ROOT.
Also, don't forget that Rails.env is used instead of RAILS.ENV in rails 3!
Rails 3 do
I also set up my config file with defaults that are loaded into each environment to keep things DRY.
defaults: &defaults development: <<: *defaults test: <<: *defaults production: <<: *defaults
Thanks for the information.
It helps me to solve the issue of
"NameError: uninitialized constant RAILS_ROOT"
and
"NameError: uninitialized constant RAILS_ENV"
@Semyon thank you for putting this comment. There's no way I would have figured this out.
You saved me hours of digging around. Thank you a billion trillion thank-you-based-thingers.
Maybe I'm stupid or maybe I'm just a newbie (this for sure), I've tried to put the costant in the environment.rb file and use it from the application but I get an "uninitialized constant" error, I understand YAML files are a better place to put configuration but why I get this error? (I'm using Ruby 1.9.2 and Rails 3.1.3, probably something changed since the screencast)
Thank you very much and sorry for the noise ;)
OK...the "problem" was I have to restart the server and the constant "appears" :)
I think this is till a great solution, but I'd like to suggest you consider using the following in ./config/application.rb instead of a separate initializer:
Source:
http://quickleft.com/blog/simple-rails-app-configuration-settings
Thanks for that nice episode. Again, really helpful!