152

I've upgraded one of my apps from Rails 4.2.6 to Rails 5.0.0. The Upgrade Guide says, that the Autoload feature is now disabled in production by default.

Now I always get an error on my production server since I load all lib files with autoload in the application.rb file.

module MyApp
    class Application < Rails::Application
        config.autoload_paths += %W( lib/ )
    end
end

For now, I've set the config.enable_dependency_loading to true but I wonder if there is a better solution to this. There must be a reason that Autoloading is disabled in production by default.

Timur Shtatland
  • 12,024
  • 2
  • 30
  • 47
Tobias
  • 4,523
  • 2
  • 20
  • 40
  • crazy thing, and docs still tell you to do auto_load. I was very confused what is going wrong in production env for a new app. And since I started learning with Rails 5 I didn't read migration guide. I filed a doc issue to hopefully get this resolved: https://github.com/rails/rails/issues/27268 – akostadinov Dec 05 '16 at 09:58
  • 2
    amazingly, I have two files in `lib` dir, one file is easily available in Runtime, but another has to be required manually :D – Shiva Jan 01 '17 at 14:59
  • @Tobias What solution did you end up with? – geoboy Jul 25 '18 at 23:44
  • @geoboy I group code (like `Validators`) in folders directly in the app/ directory since code there is auto loaded. – Tobias Jul 27 '18 at 11:41
  • it's about **proper file path and class definition** here is what work for me in Rails 5.2: File path: `app/services/paylinx/paylinx_service.rb` Class definition: `module Paylinx class PaylinxService end end`. I tried these `autoload_paths` stuff. doesn't work for me. – NamNamNam Jan 27 '19 at 08:43

12 Answers12

177

My list of changes after moving to Rails 5:

  1. Place lib dir into app because all code inside app is autoloaded in dev and eager loaded in prod and most importantly is autoreloaded in development so you don't have to restart server each time you make changes.
  2. Remove any require statements pointing to your own classes inside lib because they all are autoloaded anyway if their file/dir naming are correct, and if you leave require statements it can break autoreloading. More info here
  3. Set config.eager_load = true in all environments to see code loading problems eagerly in dev.
  4. Use Rails.application.eager_load! before playing with threads to avoid "circular dependency" errors.
  5. If you have any ruby/rails extensions then leave that code inside old lib directory and load them manually from initializer. This will ensure that extensions are loaded before your further logic that can depend on it:

    # config/initializers/extensions.rb
    Dir["#{Rails.root}/lib/ruby_ext/*.rb"].each { |file| require file }
    Dir["#{Rails.root}/lib/rails_ext/*.rb"].each { |file| require file }
    
Lev Lukomsky
  • 6,346
  • 4
  • 34
  • 24
  • 9
    So how does one uses the ```lib``` folder now? I mean moving ```lib``` dir into ```app``` dir seems kind of like a workaround. – Martin Svoboda May 20 '17 at 22:17
  • 5
    `/app/lib/`placed a file/class and it's NOT autoloading. tested in rails 5.1, new project – Tim Kretschmer Jun 21 '17 at 12:43
  • 38
    It's worth noting that you need to stop spring. I moved everything to app/lib/ and then wasted a little time wondering why I still couldn't use my classes from the console. spring stop ftw :) – jacklin Aug 13 '17 at 21:43
  • 1
    Where would the following line go `Rails.application.eager_load!` – Steven Aguilar Jul 11 '18 at 18:12
  • @Lev: When I do that, I see the following error in `dev`: `NameError - uninitialized constant ApplicationController::JsonWebToken:` Do you know what might be going on? – geoboy Jul 25 '18 at 23:20
  • @geoboy that's when the file is not loaded. it looks like the file is searched first in the root context as in `::ModuleOrClass` and then the current context, say, `ApplicationController::ModuleOrClass` where it's called. Your error is showing the second result. – Hiro Nov 26 '18 at 10:06
  • 3
    This may work but it isn't the best solution. The folder structure is semantic as well. Things in `lib` have a different perceived closeness to the project than things in the `app` directory. Several of the other answers are better than this one. – CWitty Jun 19 '19 at 14:05
  • Good suggestion for config.eager_load = true, but bad suggestion on moving lib into app. – Damien Roche Aug 21 '19 at 15:02
  • @CWitty `lib` is semantic for gems, but for Rails app it's rather unexpected to to have some ruby code in `lib` and all other code in `app` like `controllers`/`mailers`/`jobs`/`interactors`/`policies`/`serializers`/`decorators`/`helpers`/... – Lev Lukomsky May 21 '21 at 20:08
  • If lib is supposed for code agnostic to the app, where is it supposed to place additional classes related to project but not to a model? (e.g Algorithms tied to the app) ? – sparkle Aug 04 '21 at 09:28
117

I just used config.eager_load_paths instead of config.autoload_paths like mention akostadinov on github comment: https://github.com/rails/rails/issues/13142#issuecomment-275492070

# config/application.rb
...
# config.autoload_paths << Rails.root.join('lib')
config.eager_load_paths << Rails.root.join('lib')

It works on development and production environment.

Thanks Johan for suggestion to replace #{Rails.root}/lib with Rails.root.join('lib')!

notapatch
  • 6,569
  • 6
  • 41
  • 45
Michał Zalewski
  • 3,123
  • 2
  • 24
  • 37
  • 3
    Works like a charm. I didn't like the syntax so changed it to `config.eager_load_paths << Rails.root.join('lib')`. – 3limin4t0r Dec 28 '17 at 10:42
  • 2
    To me that was the best answer. My project began on Rails 5.2 from scratch and the folder /lib was still created outside the /app folder. I didn't see a good reason to move it. – Samir Haddad Aug 31 '18 at 17:32
  • 1
    Yep, this works! Seems Rails devs really enjoy causing lib loading issues :D until next time! – Damien Roche Aug 21 '19 at 15:03
  • 1
    To Rails 5.2 uses `config.eager_load_paths += [Rails.root.join('lib')]` instead because `config.eager_load_paths` is a frozen array – William Wong Garay Feb 11 '20 at 15:10
  • @WilliamWongGaray config.eager_load_paths is read-only when you try to modify it in initializer. When you add paths in `application.rb` it will work using both methods. – Michał Zalewski Feb 23 '20 at 15:42
38

Autoloading is disabled in the production environment because of thread safety. Thank you to @Зелёный for the link.

I solved this problem by storing the lib files in a lib folder in my app directory as recommended on Github. Every folder in the app folder gets loaded by Rails automatically.

Tobias
  • 4,523
  • 2
  • 20
  • 40
  • 8
    If you don't want to dig through long discussion thread on Github, you can find distilled explanation here: http://collectiveidea.com/blog/archives/2016/07/22/solutions-to-potential-upgrade-problems-in-rails-5/ – Ernest Oct 09 '16 at 09:49
  • 8
    I used `config.eager_load_paths << "#{Rails.root}/lib"`, that's better IMO to follow recommended rails app structure. – akostadinov Dec 05 '16 at 09:52
  • 4
    Putting lib in `app/lib` is recommended by rails members https://github.com/rails/rails/issues/13142#issuecomment-275549669 – eXa Jan 30 '17 at 23:45
  • 6
    This completely ruins what the purpose of `lib` is. I'd wait for tenderlove or DHH to chime in. In the meantime, I'd (personally) recommend sticking with @Lev Lukomsky's answer. – Josh Brody Sep 06 '17 at 03:15
  • @JoshBrody My opinion now is that you shouldn't need the `/lib` directory at all. Third party libs are most of the time gems and if not there should be a gem created. For other files, I create specific folders in the `/app` directory. For example `validators`. – Tobias Sep 06 '17 at 07:30
  • I agree! I think that all business logic should belong in `/app`; I guess what I'm getting at is if you're writing a Rescue extension or have some non-domain-specific objects that extend ActiveModel, where should that belong? My first instinct tells me `lib` – Josh Brody Sep 06 '17 at 08:17
  • I think (after getting it wrong on many projects) that `/lib` is meant for functionality that could be separate gems (don't require anything in app or the environment or database) but aren't (yet) -- a kind of staging point. However I find there is always app-related utility functionality that doesn't quite fit in any of the existing `/app/*` structures -- that goes well in `/app/lib` and then these are good candidates for refactoring (abstracting into app, or pulling apart into something else) – Tim Diggins Jan 22 '18 at 09:12
24

There must be a reason that Autoloading is disabled in production by default.

Here is a long discussion about this issue. https://github.com/rails/rails/issues/13142

Roman Kiselenko
  • 43,210
  • 9
  • 91
  • 103
  • 2
    This discussion is the best, though a lengthy read, source of information on the subject that I've come across. – Jason Jun 21 '20 at 13:51
13

This allows to have lib autoreload, and works in production environment too.

P.S. I have changed my answer, now it adds to both eager- an autoload paths, regardless of environment, to allow work in custom environments too (like stage)

# config/initializers/load_lib.rb
...
config.eager_load_paths << Rails.root.join('lib')
config.autoload_paths << Rails.root.join('lib')
...
srghma
  • 4,770
  • 2
  • 38
  • 54
  • 2
    Could you expand on why this fixes the issue? – Stuart.Sklinar Jan 29 '18 at 16:54
  • @Stuart.Sklinar this allows to have lib autoreload, and works in production environment too. P.S. I have changed my answer, now it adds to both eager- an autoload paths, regardless of environment, to allow work in custom environments too (like stage) – srghma Jan 29 '18 at 20:15
  • 1
    Could you expand (In your answer)? Code only answer's don't really help anyone understand why it should be done "that way" - I should add I'm not a Ruby dev, just helping clear up SO. Adding some commentary to a "code only answer" would give it some actual context. – Stuart.Sklinar Jan 29 '18 at 20:17
  • 1
    @Stuart.Sklinar sure – srghma Jan 29 '18 at 20:24
9

Just change config.autoload_paths to config.eager_load_paths in config/application.rb file. Because in rails 5 autoloading is disabled for production environment by default. For more details please follow the link.

 #config.autoload_paths << "#{Rails.root}/lib"
  config.eager_load_paths << Rails.root.join('lib')

It works for both environment development and production.

Jitendra Rathor
  • 607
  • 8
  • 11
5

In some sense, here is a unified approach in Rails 5 to centralize eager and autoload configuration, in the same time it adds required autoload path whenever eager load is configured otherwise it won't be able to work correctly:

# config/application.rb
...
config.paths.add Rails.root.join('lib').to_s, eager_load: true

# as an example of autoload only config
config.paths.add Rails.root.join('domainpack').to_s, autoload: true
...
pocheptsov
  • 1,936
  • 20
  • 24
2

For anyone struggled with this like me, it's not enough to just place a directory under app/. Yes, you'll get autoloading but not necessary reloading, which requires namespacing conventions to be fulfilled.

Also, using initializer for loading old root-level lib will prevent reloading feature during development.

2

I agree that some dependencies belong in lib and some may belong in app/lib.

I prefer to load all files I've chosen to put in lib for all environments, hence I do this in config/application.rb immediately after requiring the bundle but before opening the MyApplicationName module.

# load all ruby files in lib
Dir[File.expand_path('../../lib/**/*.rb', __FILE__)].each { |file| require file }

This doesn't depend on Rails.root (which isn't defined yet), and doesn't depend on eager loading (which may be off for an environment).

eprothro
  • 1,087
  • 11
  • 16
1

The only thing that worked for me is adding the nested lib path in eager load paths AND adding a require_dependency in a config.to_prepare block.

# application.rb
...
config.to_prepare do
  require_dependency("#{Rails.root}/lib/spree/core/product_filters.rb")
end

config.eager_load_paths << Rails.root.join('lib').join('spree').join('core')
...
Tashows
  • 545
  • 6
  • 21
  • 1
    This worked for me as well, whereas just modifying `eager_load_paths` did not. It does seem to me however that the require_dependency could benefit from using the join methods as well, i.e. `require_dependency(Rails.root.join('lib').join('spree').join('core').join('product_filters.rb'))` – Amos Joshua Jan 04 '22 at 09:45
0

Moving the lib folder to app helped solve a problem, my Twitter api would not run in production. I had "uninitialized constant TwitterApi" and my Twitter API was in my lib folder. I had config.autoload_paths += Dir["#{Rails.root}/app/lib"] in my application.rb but it didn't work before moving the folder.

This did the trick

Laurie
  • 162
  • 1
  • 11
-8

to summarize Lev's answer: mv lib app was enough to have all my lib code autoloaded / auto-reloaded.

(rails 6.0.0beta3 but should work fine on rails 5.x too)

localhostdotdev
  • 1,795
  • 16
  • 21