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')
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)
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.
$ rails --version
Rails 4.0.1
I want to be able to have a separate manifest file, root_dir/lib/javascripts/application_lib.js, so I can add lines like require_tree . to recursively search .js files under lib/
But I can't figure out how.
In my application.rb file
config.assets.paths << "#{Rails.root}/app/assets/videos"
config.assets.paths << "#{Rails.root}/lib/assets/"
config.assets.initialize_on_precompile = false
Interestingly if I put a pile in root_dir/lib/assets/javascripts/greeting.txt, greeting.txt is sourced (localhost:3000/assets/greeting.txt finds the file).
But if I put it in root_dir/lib/assets/stylesheets/greeting.txt, it cant find it. Which means that http://guides.rubyonrails.org/asset_pipeline.html#asset-organization is wrong.
I even had to add the root/lib/ to my config.assets.paths, it didn't source /lib/ automatically.
Also, I tried making another manifest file under root/lib/javascripts/application.js, but it is not being read as a manifest file and all the recurive files underneath root/lib/javascripts/test/greeting.txt is not being sourced....
How do I recursively source javascript and stylehseets under the lib/ or vendor/ directory ? The only elegant way I can think of is making a new manifest file under lib/javascripts and etc but that hasn't been working...
Thanks!
After many googling I've tried adding both of these to my config and yet reloading doesn't happen:
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]
I make changes to files under /lib but refreshing in Pow doesn't reload the changes
What am I missing?
You'll want to restart pow after changing files in /lib because Rails initializes objects here as it starts up and doesn't load them again after having started up (so changes while the server is up won't make a difference). To overcome this, you have a few options:
You may want to look at using require_dependency.
You can restart Pow by touching the restart.txt file: touch ~/.pow/restart.txt
For a more convenient solution if you're changing your lib folder a
lot, add anvil to handle your restarts
easily.
If you don't want to do this, just add your classes to the /app path
into a new folder.