Load lib files in production - ruby-on-rails

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.

My list of changes after moving to Rails 5:
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.
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
Set config.eager_load = true in all environments to see code loading problems eagerly in dev.
Use Rails.application.eager_load! before playing with threads to avoid "circular dependency" errors.
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 }

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')!

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.

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

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')
...

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.

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
...

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.

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')
...

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

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).

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)

Related

Issue Deploying Rails App to Heroku JWT auth.rb located in the Lib folder?

I'm trying to host an Rails API on Heroku and I'm having an issue when I sign up a user. Say's there's an error on Line 29. Line 29 is
jwt = Auth.encrypt({ user_id: #user_id })
Which makes a call to my Auth class, which isn't located in my project/app folder at all, but located outside in my project/lib/auth.rb. Could that be the issue?
If this works in dev, then yes, the issue is probably that the file is located under lib and Rails hasn't been configured to autoload files from that path.
You can either get rails to autoload files within lib by setting the following in your application.rb file:
config.autoload_paths << Rails.root.join('lib')
Or, alternatively you can add require 'auth' in your user_controller.rb.

ActiveSupport Autoloading not working outside of Rails

Given a file structure of
lib
app/
feature.rb
app.rb
And given the file app.rb that consists of:
require 'active_support'
require 'active_support/dependencies'
module App
extend ActiveSupport::Autoload
autoload :Feature, 'app/feature.rb'
def self.start
p Feature.new
end
end
It complains that LoadError: cannot load such file -- app/feature.rb
I've tried passing the absolute system path, no path, relative path to feature.rb from the module and the relative path from the execution point (one dir above lib).
This appears to be exactly the same thing that Rails is doing in the source.
Seems like this should be pretty intuitive, no?
The solution is the add the files to the Ruby interpreter load path like this: $LOAD_PATH.unshift(File.dirname(__FILE__)).
What that does is basically, allow Ruby to lookup those files. In Rails that is configured already when a new app is generated. When building a gem, in the spec you can add files or dirs to the load path. Outside of those, say a script in this instance, you must manually do that. autoload does not do that by default so you must specify (somewhat confusingly to me) the paths that Ruby can access.
I'm sure someone can clean up this explanation but that is how I understand it.
I don't believe Rails has lib on its autoload paths by default. We've got the following in config/application.rb:
config.eager_load_paths << Rails.root.join('lib')

Why do I get NameError: uninitialized constant simply making a rake db:create on a new machine

I have a rails app on my virtual machine. environment is development and I work on it regularly. I pulled the code from the repo on a new nserver, and always with RAILS_ENV setted to development I called as first command rake db:migrate and I get that one of my class is NameError: uninitialized constant.
How is this possible if my code and environment are the same? Autoload on my virtual machine is configured in the right way if it works.
config.autoload_paths << Rails.root.join('lib')
config.autoload_paths += Dir[Rails.root.join('app', 'api', '**', '**', '*')]
And the class that cannot be found is in app/api/nameofclass/anothername/api.rb.
Using DKIM as a name will be problematic since the autoloader maps that to d_k_i_m/ in the path. Whenever possible avoid creating directories or files with capital letters in them, it causes chaos on case sensitive filesystems.
app/api/domains/dkim/api.rb should contain Domains::Dkim::Api.
If you want to use some non-standard naming system you'll have to force load your stuff manually, the auto-loader won't do it for you.
The auto-load directive should probably be:
config.autoload_paths << Rails.root.join('app', 'api')
You don't want to be including every single file in your autoload path.
Use this instead of your both lines:
config.autoload_paths += %W|lib app/api/**|

Add directory to auto-reload in Rails 5

Version: Rails 5b3
I have a folder located at <rails root>/app/liquid. Inside that are more directories and .rb files. All the .rb files are simple classes with no includes or requires
It's very inconvenient to have to restart the server each time I make a change to a file in this directory, so I want these classes to be automatically reloaded every time I refresh the page. config.eager_load_paths += ["#{Rails.root}/app/liquid"] doesn't seem to work.
Any advice is appreciated!
Edit: To clarify, files in app/liquid autoload and reload as expected. However, any file within a subdirectory (like app/liquid/drops/*.rb) autoload correctly, but do not auto-reload on page refresh
I solved this by adding config.autoload_paths += Dir[ Rails.root.join('app', 'liquid', '**/',) ] to application.rb
Try toggling liquid's cache_classes configuration so that classes are not cached in your development environment:
Liquid.cache_classes = false
source: https://github.com/Shopify/liquid/wiki/liquid-for-programmers#caching-of-classes

Rails loading initializer classes

In my Rails app, I created an initalizer which responsability is to:
Require the service layer of my app
Dir[File.expand_path('services/*.rb',\__FILE__)].each { |file| require file }
Create those services and inject dependencies in it
But now, when I update a file in my project in development my rails server starts complaning that classes are not available anymore.
To make it work I have to delete my tmp directory and run rails server again to have it work again.
How can I work around that ?
Which tmp/ directory ? what is there in it ?
The reboot is not a surprise since you're using initializers. They are loaded once on boot and that's it.
Maybe https://github.com/guard/guard could help you reload the env automaticaly.
Maybe it's not initializers you're looking for ...
The answer depends on what you're trying to achieve. This is more an architectural problem than a code/dev one tough.
I think a better solution to creating an initializer to load those files is to use the following syntax in your application.rb file:
module AppName
class Application < Rails::Application
#other config stuff here
config.eager_load_paths << "path to your services directory"
end
end
This will make rails reload the files for every request in development mode, as well as requiring them normally in production mode.

Resources