Dynamically load configuration tokens using ruby's metaprogramming magic
So, let's say you need to use tokens into your app to access a third party API (like Twitter, Instagram or Facebook). In Ruby gems tutorials it's very common to see the following example code to load these tokens:
module Facebook
CONFIG = YAML.load_file(Rails.root.join("config/facebook.yml"))[Rails.env]
APP_ID = CONFIG['app_id']
SECRET = CONFIG['secret_key']
end
This was extracted from the Koala's gem wiki on GitHub. Or the following one, which was extracted from the README file of the Instagram gem GitHub repo.
Instagram.configure do |config|
config.client_id = "YOUR_CLIENT_ID"
config.client_secret = "YOUR_CLIENT_SECRET"
end
It's a very ugly practice to hardcode things like this, IMHO.
Now, the most recommended and used practice in the wild is to have these API access tokens in a YAML file, so it can be loaded into our app, like the Koala's example code from above. The format is the one that follows:
...
twitter:
consumer_key: YOUR_CONSUMER_KEY
consumer_secret: YOUR_CONSUMER_SECRET
oauth_token: YOUR_OAUTH_TOKEN
oauth_token_secret: YOUR_OAUTH_TOKEN_SECRET
...
As you may know, this file is loaded as a Hash
. So we can access each key and value.
Now, using ruby's metaprogramming magic we can load each key of the file and use it the configuration section of the gem. How? Using the .send()
method.
CONFIG = YAML.load_file(Rails.root.join("config", "settings.yml"))[Rails.env]
Twitter.configure do |twitter|
CONFIG['twitter'].each_key do |key|
twitter.send("#{key}=", CONFIG['twitter'][key])
end
end
The secret is to name each key in the YAML file section exactly as the name of the configuration key of the gem, so it wont be any method_missing
error.
You can find more information about the .send()
method in the Ruby Documentation page.
Written by Carlos Betancourt C.
Related protips
2 Responses
Why wouldn't you just set environment variables inside a ruby file, and initialize it if it exists? Gitignore the file. Set environment variable in production through the server. The YAML just seems like having to do more work to put it into ruby, and then if you gitignore the file so public doesn't have access to your passwords, your app is broken.
I have few comments:
Your latest code is still hardcoded, specially on [Rails.env], you should check https://github.com/laserlemon/figaro, it can automatic load by environment when you call ENV['twitter'] without recall Rails.env
Storing credential information in .yml is old school now. You missed this article, http://12factor.net/config.
I recommend you to use dotenv (.env), you can read https://github.com/bkeepers/dotenv and https://github.com/bkeepers/dotenv-deployment. Read them carefully and you will enrich your knowledges.
cheers ^_^