laserlemon / figaro

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

Undesired interaction between figaro/active_record for rake db tasks (rake runs twice for db tasks) #225

Open posiczko opened 8 years ago

posiczko commented 8 years ago

Not sure how to report this, but active record's (4.2.x) default behavior has some undesired side-effects on rake tasks when used with figaro. The rake db:* tasks run twice in the dev environment when RAILS_ENV is not set:

The following configuration:

application.yml

development:
  MYSQL_DATABASE: rails_dev
  MYSQL_USER: rails_dev
  MYSQL_HOST: 127.0.0.1
  ...
test:
  MYSQL_DATABASE: rails_test
  MYSQL_USER: rails_test
  MYSQL_HOST: 127.0.0.1

database.yml

development:
  adapter: mysql2
  database: <%= ENV['MYSQL_DATABASE'] %>
  username: <%= ENV['MYSQL_USER'] %>
  password: <%= ENV['MYSQL_PASSWORD'] %>
  host: <%= ENV['MYSQL_HOST'] %>

test:
  adapter: mysql2
  database: <%= ENV['MYSQL_DATABASE'] %>
  username: <%= ENV['MYSQL_USER'] %>
  password: <%= ENV['MYSQL_PASSWORD'] %>
  host: <%= ENV['MYSQL_HOST'] %>

results in rake tasks being run twice when RAILS_ENV is not specified:

(assuming one's db does not have rails_dev and rails_test defined)

$ bundle exec rake db:create --trace
** Invoke db:create (first_time)
** Invoke db:load_config (first_time)
** Execute db:load_config
** Execute db:create
rails_dev already exists

This is more obvious in the case when you are trying to drop the dbs:

$ bundle exec rake db:drop --trace
** Invoke db:drop (first_time)
** Invoke db:load_config (first_time)
** Execute db:load_config
** Execute db:drop
Database 'rails_dev' does not exist

$ bundle exec rake db:drop --trace
** Invoke db:drop (first_time)
** Invoke db:load_config (first_time)
** Execute db:load_config
** Execute db:drop
Database 'rails_dev' does not exist
Database 'rails_dev' does not exist

This behavior is caused by the active record lib/active_record/tasks/database_tasks.rb trying to be super-extra helpful and setting environments to an array containing both development and test, instead of leaving it alone:

  def each_current_configuration(environment)
        environments = [environment]
        # add test environment only if no RAILS_ENV was specified.
        environments << 'test' if environment == 'development' && ENV['RAILS_ENV'].nil?

Net result is that the rake tasks are run twice due on development environment when RAILS_ENV is not specified, due to how figaro is hooking into rails.

A workaround is to specify RAILS_ENV on the command line or in your environment, but what sane person would do that:

$ RAILS_ENV=development bundle exec rake db:create --trace
** Invoke db:create (first_time)
** Invoke db:load_config (first_time)
** Execute db:load_config
** Execute db:create
$ RAILS_ENV=development bundle exec rake db:drop --trace
** Invoke db:drop (first_time)
** Invoke db:load_config (first_time)
** Execute db:load_config
** Execute db:drop

So there you have it. I haven't looked into figaro's internals to figure out whether or not this is fixable.

Hopefully this report will save folks some time and hair from being pulled out in frustration.

hesalx commented 8 years ago

It is not run twice. It is run once for each environment (dev & test). But from database.yml perspective all properties have the same values as where loaded into ENV at the moment of application initialization with the exact environment in that exact moment (e.g. development). In your example it drops rails_dev for dev env and then drops rails_dev for test env.

The problem is that figaro respects Twelve-Factor App, and Rails does not, it tries to be in two envs in a single run. Or does, in some way... Actually, if you set RAILS_ENV the things become clear.

Actually, there should not be environments in the database.yml when you're using figaro. Because "environmental" configs are defined by an explicit external set of environment variables. I'm hardly sure that Rails would like this.

augustosamame commented 8 years ago

Thanks for the explanation. So how do I solve this without having to run rake tasks like this each time?

RAILS_ENV=development bundle exec rake db:create

pedroadame commented 7 years ago

Same problem for me, and executing RAILS_ENV=development bundle exec rails db:create also tries to create the db twice. @arantir @laserlemon have you any solution?

ingolfured commented 6 years ago

Having the same problem, any workaround?

ingolfured commented 6 years ago

I ended up just using the test environment. Rails itself tries to be too helpful and runs for both envs (always does the test one)