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
- Create a Rails project
- Create a namespaced
foo
rake task under lib/tasks, with a method namedbar
# lib/tasks/foo.rake
namespace :foo do
def bar(a, b, c)
end
end
- Install
factory_bot_rails
- Create a
dummy
model and factory, with a column namedbar
rails g model dummy bar
- 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😅