LewlSauce
LewlSauce

Reputation: 5892

Why does rails care about my "development' environment in database.yml if I'm loading with production environment variable?

Here's an example of my database.yml file:

default: &default
  adapter: postgresql
  encoding: unicode
  pool: 200

production:
  <<: *default
  host: <%= ENV['DBHOST'] %>
  username: <%= ENV['DBUSER'] %>
  password: <%= ENV['DBPASS'] %>
  database: <%= ENV['DBNAME'] %>

development:
  <<: *default
  host: <%= Rails.application.credentials.development[:host] %>
  username: <%= Rails.application.credentials.development[:username] %>
  password: <%= Rails.application.credentials.development[:password] %>
  database: <%= Rails.application.credentials.development[:database] %>

However, when trying to run rails c -e production, it fails with the following error:

$ rails c -e production                                                    
(erb):15:in `<main>': Cannot load database configuration:                              
undefined method `[]' for nil:NilClass (NoMethodError)      

If I take the development section out of my database.yml file, then everything runs perfectly fine. For awhile, I've had to either have the credentials for development or production in this file; however, I thought the file was made to have multiple credentials.

What am I doing wrong? This should be pretty simple.

Upvotes: 1

Views: 416

Answers (2)

max
max

Reputation: 102423

Your expectation is completely wrong.

In order to parse the file Rails has to pass the entire file through ERB before its sent through YAML. Rails can just cherry pick the production: section of your YAML file since variable expansion takes place before it is parsed - at that point its just a string buffer.

You can fix the issue by using the safe navigation operator to avoid nil errors:

default: &default
  adapter: postgresql
  encoding: unicode
  pool: 15

production:
  <<: *default
  host: <%= ENV.fetch('DBHOST') %>
  username: <%= ENV.fetch('DBUSER') %>
  password: <%= ENV.fetch('PASS') %>
  database: <%= ENV.fetch('DBNAME') %>

development:
  <<: *default
  <% creds = Rails.application.credentials.development) %>
  host:     <%= creds&.dig(:host) %>
  username: <%= creds&.dig(:username) %>
  password: <%= creds&.dig(:password) %>
  database: <%= creds&.dig(:database) %>

Using Hash#fetch can save you tons of debugging time as it will raise a KeyError instead of letting your app boot up with nil values.

There also really is no need to use 4 different env vars to configure your database. Just set DATABASE_URL

postgresql://hostname/database?username=bob&password=abcd12345

This is how its done on Heroku for example. ENV['DATABASE_URL'] takes preceedence over any settings in database.yml.

This lets you simplefy the whole thing down to:

default: &default
  adapter: postgresql
  encoding: unicode
  pool: 15
  # Hacky per env workaround for Rails 5.2
  <% creds = Rails.application.credentials.try(Rails.env) %>
  host:     <%= creds&.dig(:host) %>
  username: <%= creds&.dig(:username) %>
  password: <%= creds&.dig(:password) %>
  database: <%= creds&.dig(:database) %>

production:
  <<: *default

development:
  <<: *default
  
test:
  <<: *default

Or even:

default: &default
  adapter: postgresql
  encoding: unicode
  pool: 15

production:
  <<: *default

development:
  <<: *default
  
test:
  <<: *default

Which is really all you need for Postgres.app, the Homebrew/Linuxbrew pg and other common Postgres presets. Developers then can customize the settings by setting ENV['DATABASE_URL'] with DirEnv/ShellEnv or through the container if you are using docker. This avoids developer wars and config clashes.

See Configuring Rails Applications.

Upvotes: 1

LewlSauce
LewlSauce

Reputation: 5892

Solved this problem using information in this post: https://stackoverflow.com/a/43747708/1493116

Updated my database.yml file to look more like this instead:

default: &default
  adapter: postgresql
  encoding: unicode
  pool: 15

production:
  <<: *default
  host: <%= ENV['DBHOST'] %>
  username: <%= ENV['DBUSER']%>
  password: <%= ENV['PASS'] %>
  database: <%= ENV['DBNAME'] %>

development:
  <<: *default
  <% if Rails.env.development? %>
  host: <%= Rails.application.credentials.development[:host] %>
  username: <%= Rails.application.credentials.development[:username] %>
  password: <%= Rails.application.credentials.development[:password] %>
  database: <%= Rails.application.credentials.development[:database] %>
  <% end %>

Seems a bit sloppy but it solved my problem.

Upvotes: 0

Related Questions