laserlemon / figaro

Simple Rails app configuration
MIT License
3.77k stars 287 forks source link

Dynamically load development or test default values #266

Open laserlemon opened 7 years ago

laserlemon commented 7 years ago

Using a Rails app as an example, a developer may want to load certain default values when Rails.env.development? and others when running the test suite and Rails.env.test?. Both of these cases should use the same Envfile. So what's the most intuitive way to load different default values based on the environment, keeping in mind that Figaro 2.0 is dropping the explicit Rails dependency?

Some initial thoughts:

# Envfile
defaults ".env.yml", key: -> { Rails.env }
string :foo
# .env.yml
development:
  foo: bar
test:
  foo: baz

# Envfile
defaults ".env.yml"
defaults ".env.test.yml", if: -> { Rails.env.test? }
string :foo
# .env.yml
foo: bar
# .env.test.yml
foo: baz

I'd like to be explicit about what's being loaded and avoid hidden conventions. Think about Bundler and how you source "https://rubygems.org" at the top of every Gemfile. The Bundler team could have made that the default source since it is 95% of the time, but they opted for clarity.

I'd love to get others' thoughts on these two ideas and suggestions for more ideas. Thank you!

shekibobo commented 7 years ago

Priority of variables loaded, from lowest to highest:

  1. Defaults declared in the Envfile, i.e. string :foo, default: "Foo"
  2. Values declared in .env.yml files. Last declaration wins, overriding previous declarations of the same named variable.
  3. ENV variables declared on the system.

With this in mind, I don't think defaults is a confusing method name. I'd suggest something that suggests the precedence of the files being loaded:

source ".env.yml"
source ".env.test.yml", if: -> { Rails.env.test? }

load ".env.yml"
load ".env.test.yml", if: -> { Rails.env.test? }

provide ".env.yml"
provide ".env.test.yml", if: -> { Rails.env.test? }

defaults makes it unclear what values will actually be loaded.

I think the strategy for loading really depends on if you expect value files to act like cascading stylesheets, where you can provide overrides in special conditions, then multiple DSL source calls makes the most sense. If you expect variables to be more mutually exclusive and independent, then I think a single YAML file with keys makes more sense. I have concerns in either situation.

source ".env.yml", key: :base
source ".env.yml", key: :test, if: -> { Rails.env.test? }

I guess it all depends on what your goals are, but I think separating the idea of defaults and sourced variables is important for the clarifying the ideas in this discussion.

laserlemon commented 6 years ago

Update: After mulling this over some more, I'm leaning toward a single YAML configuration file with the ability to key the file according to the application environment. To define the application environment (since Figaro will no longer be Rails-dependent), I'm adding the environment method to the DSL. For example:

# env.rb
environment key: "APP_ENV", default: "development"

defaults "env.yml"

string :stripe_publishable_key
string :stripe_secret_key
decimal :price, default: "9.99"
# env.yml
stripe_publishable_key: "pk_test_NlsxCBp2TPGWyQeHUFw4uj4L"
stripe_secret_key: "sk_test_iHBheVvfdegliMwizMi2uiOe"

production:
  stripe_publishable_key: "pk_live_uoExPgxfIG7bBXMAMyqHfB9b"
  stripe_secret_key: "sk_live_KbhHZzgpzWIEsOoBOnatXUjo"

As for the defaults method naming, I hear you. I'm not sold on it either. I'm not sold on the alternatives set forth yet either. I'll keep rolling that one around.

When it comes to committing multiple default values for a given key, let's say for the development and test environments while keeping the production value out of source control, there are a couple of options:

  1. Load multiple YAML configuration files, some committed and some not.
  2. Allow environment-dependent default values in the envfile.

I'm not sure what to do about this one just yet either. I don't like the idea of multiple YAML configuration files because it's harder to track down what values are where. Plus it's more likely that something that's not supposed to get committed does get committed. Here's what option 2 might look like in practice:

# env.rb
environment key: "APP_ENV", default: "development"

defaults "env.yml"

string :hello, default: { development: "developer", test: "computer" }

Anyway, just dumping some thoughts here. Feel free to chime in!