thoughtbot / factory_bot_rails

Factory Bot ♥ Rails

Home Page:https://thoughtbot.com/services/ruby-on-rails

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Perform DB migration will make factory_bot looking for methods in the wrong place

xofred opened this issue · comments

Description

Perform DB migration will make factory_bot looking for methods in the wrong place

Reproduction Steps

Demo https://github.com/xofred/factory_bot_rails_issue_demo

  1. Create a Rails project
  2. Create a namespaced foo rake task under lib/tasks, with a method named bar
# lib/tasks/foo.rake
namespace :foo do
  def bar(a, b, c)
  end
end
  1. Install factory_bot_rails
  2. Create a dummy model and factory, with a column named bar
rails g model dummy bar
  1. Migrate the DB
rails db:migrate

Expected behavior

Step 5 should not raise an error

Actual behavior

Factory bot is looking for methods in the wrong place

/Users/justin/projects/factory_bot_rails_issue_demo/lib/tasks/foo.rake:2:in `bar'
/Users/justin/projects/factory_bot_rails_issue_demo/test/factories/dummies.rb:3:in `block (2 levels) in <main>'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/factory_bot-6.1.0/lib/factory_bot/syntax/default.rb:18:in `instance_eval'

The trace

The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
rails aborted!
ArgumentError: wrong number of arguments (given 0, expected 3)
/Users/justin/projects/factory_bot_rails_issue_demo/lib/tasks/foo.rake:2:in `bar'
/Users/justin/projects/factory_bot_rails_issue_demo/test/factories/dummies.rb:3:in `block (2 levels) in <main>'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/factory_bot-6.1.0/lib/factory_bot/syntax/default.rb:18:in `instance_eval'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/factory_bot-6.1.0/lib/factory_bot/syntax/default.rb:18:in `factory'
/Users/justin/projects/factory_bot_rails_issue_demo/test/factories/dummies.rb:2:in `block in <main>'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/factory_bot-6.1.0/lib/factory_bot/syntax/default.rb:37:in `instance_eval'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/factory_bot-6.1.0/lib/factory_bot/syntax/default.rb:37:in `run'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/factory_bot-6.1.0/lib/factory_bot/syntax/default.rb:7:in `define'
/Users/justin/projects/factory_bot_rails_issue_demo/test/factories/dummies.rb:1:in `<main>'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/bootsnap-1.4.8/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:59:in `load'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/bootsnap-1.4.8/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:59:in `load'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:285:in `block in load'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:257:in `load_dependency'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:285:in `load'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/factory_bot-6.1.0/lib/factory_bot/find_definitions.rb:20:in `block (2 levels) in find_definitions'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/factory_bot-6.1.0/lib/factory_bot/find_definitions.rb:19:in `each'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/factory_bot-6.1.0/lib/factory_bot/find_definitions.rb:19:in `block in find_definitions'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/factory_bot-6.1.0/lib/factory_bot/find_definitions.rb:15:in `each'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/factory_bot-6.1.0/lib/factory_bot/find_definitions.rb:15:in `find_definitions'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/factory_bot_rails-6.1.0/lib/factory_bot_rails/railtie.rb:22:in `block in <class:Railtie>'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/activesupport-5.2.4.3/lib/active_support/lazy_load_hooks.rb:69:in `block in execute_hook'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/activesupport-5.2.4.3/lib/active_support/lazy_load_hooks.rb:62:in `with_execution_control'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/activesupport-5.2.4.3/lib/active_support/lazy_load_hooks.rb:67:in `execute_hook'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/activesupport-5.2.4.3/lib/active_support/lazy_load_hooks.rb:52:in `block in run_load_hooks'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/activesupport-5.2.4.3/lib/active_support/lazy_load_hooks.rb:51:in `each'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/activesupport-5.2.4.3/lib/active_support/lazy_load_hooks.rb:51:in `run_load_hooks'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/railties-5.2.4.3/lib/rails/application/finisher.rb:75:in `block in <module:Finisher>'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/railties-5.2.4.3/lib/rails/initializable.rb:32:in `instance_exec'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/railties-5.2.4.3/lib/rails/initializable.rb:32:in `run'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/railties-5.2.4.3/lib/rails/initializable.rb:61:in `block in run_initializers'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/railties-5.2.4.3/lib/rails/initializable.rb:60:in `run_initializers'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/railties-5.2.4.3/lib/rails/application.rb:361:in `initialize!'
/Users/justin/projects/factory_bot_rails_issue_demo/config/environment.rb:5:in `<main>'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/bootsnap-1.4.8/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/bootsnap-1.4.8/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/bootsnap-1.4.8/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/bootsnap-1.4.8/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/bootsnap-1.4.8/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:291:in `block in require'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:257:in `load_dependency'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:291:in `require'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/railties-5.2.4.3/lib/rails/application.rb:337:in `require_environment!'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/railties-5.2.4.3/lib/rails/application.rb:520:in `block in run_tasks_blocks'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/railties-5.2.4.3/lib/rails/commands/rake/rake_command.rb:23:in `block in perform'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/railties-5.2.4.3/lib/rails/commands/rake/rake_command.rb:20:in `perform'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/railties-5.2.4.3/lib/rails/command.rb:48:in `invoke'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/railties-5.2.4.3/lib/rails/commands.rb:18:in `<main>'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/bootsnap-1.4.8/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/bootsnap-1.4.8/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/bootsnap-1.4.8/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/bootsnap-1.4.8/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/bootsnap-1.4.8/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:291:in `block in require'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:257:in `load_dependency'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:291:in `require'
/Users/justin/projects/factory_bot_rails_issue_demo/bin/rails:9:in `<top (required)>'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/spring-2.1.1/lib/spring/client/rails.rb:28:in `load'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/spring-2.1.1/lib/spring/client/rails.rb:28:in `call'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/spring-2.1.1/lib/spring/client/command.rb:7:in `call'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/spring-2.1.1/lib/spring/client.rb:30:in `run'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/spring-2.1.1/bin/spring:49:in `<top (required)>'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/spring-2.1.1/lib/spring/binstub.rb:11:in `load'
/Users/justin/.rvm/gems/ruby-2.6.3/gems/spring-2.1.1/lib/spring/binstub.rb:11:in `<top (required)>'
/Users/justin/projects/factory_bot_rails_issue_demo/bin/spring:15:in `require'
/Users/justin/projects/factory_bot_rails_issue_demo/bin/spring:15:in `<top (required)>'
bin/rails:3:in `load'
bin/rails:3:in `<main>'
Tasks: TOP => db:migrate => db:load_config => environment
(See full trace by running task with --trace)

System configuration

(We use factory_bot_rails 4.0 and rails 5.1.6.2 with ruby 2.6.5 in production, same error)
factory_bot_rails version: 6.1.0
factory_bot version: ~> 6.1.0
rails version: 5.2.4.3
ruby version: 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin18]

Dirty fix

Remove factory_bot_rails from development but test only. DB migration works, and rspec with factory works, too.

The only inconvenient is

  • either manually create factory file for new model,
  • or RAILS_ENV=test rails g model dummy bar

Hello @xofred

Thanks for reporting this. I can confirm that I was able to reproduce the issue, but I am not sure changing factory_bot or factory_bot_rails is the right approach here. I believe this to be a result of your usage of rake (or perhaps an issue with rake itself, if you want to see it that way). Let me try to explain why:

Any method defined in a rake task is global, even if they are defined inside a rake namespace. This can lead to some "interesting" results. Let's say we have namespace :a with task :run_bar, which prints something:

namespace :a do
  task :run_bar do
    bar
  end

  def bar
    puts "A"
  end
end

When I run it, I get the following:

% bundle exec rake a:run_bar
A

Now, let's say I define another namespace that has a method called bar in it:

namespace :b do
  task :run_bar do
    bar
  end

  def bar
    puts "B"
  end
end

If I run it, I'll get:

% bundle exec rake b:run_bar
B

That's what we expected! But... what if we run a:run_bar again?

% bundle exec rake a:run_bar
B

Oops. Now, even though there is a method called bar in namespace a, it is using the one defined in namespace b.
So this is why FactoryBot gets confused when it's invoked in a migration (which is for all intents and purposes, a rake task).

My suggestion is that you define your methods elsewhere (perhaps in a module or class) and call them from your rake tasks. That should solve your problem.

Please let me know if you have any questions :)

Thanks for the explanation. Unfortunately, there are a lot of rake tasks like that in our legacy projects. So, I think I will have to keep the dirty fix approach😅