

Configuring Rails Environments - zimkies
http://eng.joingrouper.com/blog/2014/09/02/configuring-rails-environments/

======
dhh
Rails will have custom configuration built-in from 4.2, but you can also just
use the gem today:
[https://github.com/dhh/custom_configuration](https://github.com/dhh/custom_configuration)

Then you can centralize all your configuration in config/environments/* and
config/initializers/ _. The best practice being that you set the configuration
in config /environments/_ and you read that configuration point and do
something with it in config/initializers/*.

IMO, much nicer than messing with ENVs.

~~~
gingerlime
My first thought after reading the OP, was to link to your blog post[0]. It
was a real eye-opener for us, and we stopped using this `if
Rails.env.production?` (anti)pattern in favour of configuration. Much cleaner.

I think a key aspect to make this successful is to make configurations
'inherit' from each other (as you explained on the post, using `require
Rails.root.join("config/environments/production")`). It really makes our
staging environment configuration only very slightly different from production
and is very DRY.

[0] [https://signalvnoise.com/posts/3535-beyond-the-default-
rails...](https://signalvnoise.com/posts/3535-beyond-the-default-rails-
environments)

------
jwiley
\- "You cannot update your server’s configuration without committing and
deploying new code"

I admit while I agree with many parts of 12-factor, I have never full
understood the rational for this. What is the rationale of moving from
versioned, concise config files, to un-versioned ENV variables?

\- "Due to Ruby’s lack of type checking, the following code has a full code
path that is not tested."

Add this to your specs: Rails.stub(:env).and_return('staging') ?

\- "Bundler won’t install ‘production’ grouped gems on your staging server"

Use identical gems under staging, or override Capistrano's bundle install

\- "It’s easy to make mistakes when configuring code by environment."

How about defining a new config value in staging.rb?
Your::Application.config.mount_admin

I may be missing something, but how can ENV variables scale beyond a few
config variables? I can see an argument that we should all be striving for
fleets of micro-services that will only require a few, but many codebases
cannot and won't ever realistically meet this goal.

~~~
josegonzalez
No one says your env vars need be unversioned. For instance, at SeatGeek we
version all env vars in our chef cookbook repository as databags and reference
them in the application infrastructure.

It does require more fancy footwork when deploying new features that need
environment variables, but it also lets us share our codebase with non-
technical users and the occasional freelancer/potential FT hire without
needing to scrub confidential information.

~~~
jwiley
So why not just move the confidential stuff to ENV, and use the traditional
Rails methods? What does ENV vs config get you?

------
josho
At this point I think they are ready for a minor refactoring to name their
code with what it's actually doing, ie. they aren't wrapping env, they are
configuring their system, so let's have a Configuration class. For getting
discrete values, this could look like:

    
    
        num_retires = Configuration.retry_attempts_count
    

which could fail immediately if the ENV['RETRY_ATTEMPTS_COUNT'] is not set.

While for configuring boolean values, we could do something like:

    
    
        show_confidant if Configuration.display_confidential_info?
    

Which could handle the extra care around true/false ENV parameter parsing by
taking advantage of the question mark in the method name convention that Ruby
has.

For bonus points wrap that into the existing __Rails.application __mechanism,
so that it 's an obvious place for new developers on the project to go to find
app parameters.

~~~
scoot
This pretty much describes Figaro:
[https://github.com/laserlemon/figaro](https://github.com/laserlemon/figaro)
(apart from being baked into Rails, that is...)

~~~
josho
Nice. I should have known to spend 5 minutes looking for a gem instead of
writing a comment. I suppose the parent could have done the same before
writing their ENV wrapping and a blog post. ;)

------
doubleg
These points (fail-fast, types and one-stop-config) were exactly what
motivated me to write the ENVied-gem
([https://github.com/eval/envied#envied-](https://github.com/eval/envied#envied-)).

~~~
zimkies
Great gem, thanks for sharing!

------
tel
To solve these kinds of problems and make more "12-factor"y Haskell apps I
once wrote env-parser [0][1] though I haven't pushed it to release in a while.

Env-parser does a few things to make handling ENV easier. First, it, through
virtue of Haskell's IO-concentration properties, forces you to pre-load your
relevant environment variables and thus fail-fast due to misspellings and poor
parses. Second, it tries to force you to interpret the ENV strings as typed
values so that there's a limitation of the syntactic space allowable in ENV
files.

Finally, and most importantly, it uses an "applicative functor structure" so
that the _exact_ code which parses your ENV can be used to produce
documentation automatically. This is nice because it will _always_ be in-sync
with your program (modulo errors in my own coding of the library).

If anyone is interested in using this in their Haskell code, I'd be more than
happy to push out the newer version. It has much nicer parsers and better
reporting.

[0] [http://hackage.haskell.org/package/env-
parser](http://hackage.haskell.org/package/env-parser) [1]
[https://github.com/tel/env-parser-talk](https://github.com/tel/env-parser-
talk)

~~~
cschneid
That sounds great - push it up! Is there a github repo for it (hackage is
downish at the moment).

~~~
tel
tel/env-parser-talk has the most updated code. I'd love some discussion on
feature engineering before I push it. Want to email me or leave an issue on
that repo? I'll probably get some time this weekend to knock it out.

------
sjtgraham
Despite using ENV all the time, this article made me actually question what
the hell it actually is, i.e. how it is implemented. It quacks very muck like
a Hash, so after reading this I thought all these solutions a bit ceremonious.
Why not just set ENV's default proc to raise a KeyError to behave like
Hash#fetch:

    
    
       ENV.default_proc = ->(_,key) { raise KeyError.new "Key not found: #{key.inspect}" }
    

Alas, ENV is not a Hash and doesn't have default_proc. It's an instance of
Object, i.e. literally Object.new, decorated with extra methods, see
[https://github.com/ruby/ruby/blob/d738e3e15533e0f500789faaed...](https://github.com/ruby/ruby/blob/d738e3e15533e0f500789faaedcef9ed9ca362b9/hash.c#L3922-3973).
Wow, TIL.

Anyway, a real simple way to have ENV[] behave like ENV.fetch is to define a
method on ENV's metaclass:

    
    
      def ENV.[](key)
        fetch key
      end
    

This worked fine in irb but use fetch, intent is clearer.

------
ashtuchkin
Be careful when using environment to store sensitive info (keys, etc) - they
are visible to other processes of the same user and also can be sent as part
of crash report. See
[http://security.stackexchange.com/q/49725](http://security.stackexchange.com/q/49725)

------
jimmyfalcon
I cannot help but find it funny that domain names are such as a rare commodity
that many startups have domains that are different from their names like:

joingrouper getsomething getsomethingapp ... you get the point.

I don't think this is actually a good idea to name your startup this way. Paul
graham had a good blog on startup names (see note). SEO space is polluted,
people naturally can't remember what your URL is. While it might be ok for
mobile apps, I certainly don't think this works for webbased apps.

Thoughts?

Note:
[http://aux.messymatters.com/pgnames.html](http://aux.messymatters.com/pgnames.html)

------
ollysb
I always keep this in my boot.rb

    
    
        SAFE_ENV = Proc.new do |name|
          ENV[name].tap{ |value| raise "Environment variable #{name} is missing" unless value }
        end
    

and then for any required env vars I use SAFE_ENV instead of ENV e.g.

    
    
        SAFE_ENV['AWS_KEY'] # throws error if missing
        ENV['AWS_KEY'] # silently continues

------
reconbot
I love this, I recently wrote about it too. It allows for a much easier time
for new developers coming on to your project, and I think it makes everyone
else have an easier time too.

[http://www.wizard.codes/code-review-cleaning-up-the-
environm...](http://www.wizard.codes/code-review-cleaning-up-the-environment/)

------
thebenedict
I like it (and am doing something similar on a hobby django project), but will
this be hard to maintain? Say you're spinning up a new non-heroku server or
three. Are there good tools for moving the env variables without making
mistakes?

~~~
secstate
Here's how I have come to address this issue (fyi, I'm using django-
configurations and my own starter django project configured with it + ansible
roles for deployments:
[http://github.com/powellc/outline](http://github.com/powellc/outline) ):

Your production app settings live in a separate repository that is kept much
more secure than the other project files. These exist as host_vars files that
you can simply drop in the host_vars directory in your ansible configuration
and run the deploy script.

A sample host_vars can be included for either setting up a dev vagrant box or
even your staging setup, if you don't have any really secret API keys.

That way you can move things around, spin up new boxes or what have you,
simply by creating new host_vars files or moving them around.

