spring icon indicating copy to clipboard operation
spring copied to clipboard

Spring in Rails 6 tries to load ActiveRecord even when AR is not part of the application

Open p-mongo opened this issue 6 years ago • 9 comments

If I create a skeleton Rails API application, and then remove ActiveRecord from the application, spring still tries to load AR which ultimately fails and prevents generators etc. from working.

This is a regression in Rails 6 - in Rails 5.2 everything works as expected.

I created two applications, which are identical except for version of Rails used.

https://github.com/p-mongo/tests/tree/master/spring-ar-sqlite-5 https://github.com/p-mongo/tests/tree/master/spring-ar-sqlite-6

Generate the app:

rails new --api spring-ar-sqlite-6
cd spring-ar-sqlite

Edit Gemfile, removing reference to sqlite.

Edit config/application.rb, commenting out ActiveRecord and ActiveStorage:

#require "active_record/railtie"
#require "active_storage/engine"

Run:

rails g controller foo

Result:

butler% rails g controller foo
/home/sandbox/.rbenv/versions/2.6.5/lib/ruby/2.6.0/bundler/rubygems_integration.rb:408:in `block (2 levels) in replace_gem': Error loading the 'sqlite3' Active Record adapter. Missing a gem it depends on? sqlite3 is not part of the bundle. Add it to your Gemfile. (LoadError)
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/connection_adapters/sqlite3_adapter.rb:13:in `<main>'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `block in require_with_bootsnap_lfi'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/zeitwerk-2.2.0/lib/zeitwerk/kernel.rb:23:in `require'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/connection_adapters/connection_specification.rb:170:in `spec'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:1044:in `establish_connection'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/connection_handling.rb:51:in `establish_connection'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/railtie.rb:201:in `block (2 levels) in <class:Railtie>'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/lazy_load_hooks.rb:72:in `class_eval'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/lazy_load_hooks.rb:72:in `block in execute_hook'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/lazy_load_hooks.rb:62:in `with_execution_control'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/lazy_load_hooks.rb:67:in `execute_hook'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/lazy_load_hooks.rb:52:in `block in run_load_hooks'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/lazy_load_hooks.rb:51:in `each'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/lazy_load_hooks.rb:51:in `run_load_hooks'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/base.rb:327:in `<module:ActiveRecord>'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/base.rb:27:in `<main>'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `block in require_with_bootsnap_lfi'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/zeitwerk-2.2.0/lib/zeitwerk/kernel.rb:23:in `require'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/application.rb:370:in `active_record_configured?'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/application.rb:287:in `disconnect_database'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/application.rb:111:in `preload'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/application.rb:157:in `serve'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/application.rb:145:in `block in run'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/application.rb:139:in `loop'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/application.rb:139:in `run'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/application/boot.rb:19:in `<top (required)>'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
        from /home/sandbox/.rbenv/versions/2.6.5/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
        from -e:1:in `<main>'

p-mongo avatar Oct 09 '19 18:10 p-mongo

Something in your app is defining ActiveRecord::Base. Spring only try to load it if the constant exists https://github.com/rails/spring/blob/master/lib/spring/application.rb#L370.

rafaelfranca avatar Oct 09 '19 18:10 rafaelfranca

The AR require is triggered from that line, leading me to believe that something configures AR to be autoloaded, but I don't know how to determine what that would be.

In any event "my app" has no code, as stated in the above description. I generated it using the standard Rails generator and removed AR railtie.

p-mongo avatar Oct 09 '19 18:10 p-mongo

oh. Yeah, that is true. ActiveRecord::Base is autoloaded, so of course that code doesn't work. We need to find a way to check if Active Record is loaded without checking for defined?.

rafaelfranca avatar Oct 09 '19 18:10 rafaelfranca

Try this "--skip-active-record" with "rails new"

jasnow avatar Oct 09 '19 20:10 jasnow

rails new --skip-acitve-record --api also omits spring from the generated application. Thus it is a valid workaround but does not fix the problem reported in this issue.

p-mongo avatar Oct 10 '19 06:10 p-mongo

I thought simply adding sqlite to the bundle was a sufficient workaround, but per https://stackoverflow.com/questions/58352612/rails-tasks-and-generators-are-failing/58383659#58383659 the application must define a full AR configuration. If this configuration is loaded, i.e. connection(s) to the database are established, this is a rather heavy workaround considering the AR database won't ever be used by any application code.

p-mongo avatar Oct 14 '19 20:10 p-mongo

I tripped over this as well deploying an application to Heroku. I did not need ActiveRecord, and simply leaving sqlite installed is not compatible with Heroku. It looks like a few indirect dependencies on ActiveRecord precipitated the autoloading. Anyways, the resolution turns out to be relatively straightfoward.

  • I removed sqlite from my Gemfile
  • deleted config/database.yml
  • Manually required the railties that would NOT result in ActiveRecord loading:
--- a/config/application.rb
+++ b/config/application.rb
@@ -1,6 +1,29 @@
 require_relative 'boot'

-require 'rails/all'
+# require 'rails/all'
+
+  # Removed because ActiveRecord:
+  # active_record/railtie
+  #
+  # Removed because precipitates AR load:
+  # action_mailbox/engine
+  # active_storage/engine
+  # action_text/engine
+
+%w(
+  action_cable/engine
+  active_job/railtie
+  action_mailer/railtie
+  action_controller/railtie
+  action_view/railtie
+  rails/test_unit/railtie
+  sprockets/railtie
+).each do |railtie|
+  begin
+    require railtie
+  rescue LoadError
+  end
+end
  • And finally, removed any reference to active_storage and active_record in my config/environments/*.rb files, e.g.:
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -29,7 +29,7 @@ Rails.application.configure do
   end

   # Store uploaded files on the local file system (see config/storage.yml for options).
-  config.active_storage.service = :local
+  # config.active_storage.service = :local

   # Don't care if the mailer can't send.
   config.action_mailer.raise_delivery_errors = false
@@ -40,10 +40,10 @@ Rails.application.configure do
   config.active_support.deprecation = :log

   # Raise an error on page load if there are pending migrations.
-  config.active_record.migration_error = :page_load
+  # config.active_record.migration_error = :page_load

   # Highlight code that triggered database queries in logs.
-  config.active_record.verbose_query_logs = true
+  # config.active_record.verbose_query_logs = true

Hopefully this helps!

coderifous avatar Aug 06 '20 18:08 coderifous

Thanks so much @coderifous ! You really helped me out today

Reeseman avatar Nov 19 '20 01:11 Reeseman

@coderifous active storage is the point in my case. Thank you

thinhbui311 avatar May 25 '21 14:05 thinhbui311