Eager loading of rails lib - ruby-on-rails

It looks like this issue will be solved in Rails 4:
http://blog.plataformatec.com.br/2012/08/eager-loading-for-greater-good/
but until then, I'm wondering how to eager-load modules/classes in my /lib.
In IRB it appears that they are loaded on-demand the first time I try to access:
Foo::Bar.constants
=> []
Foo::Bar::Service
=> Foo::Bar::Service
Foo::Bar.constants
=> [:ServiceBase, :Service]
I have several other classes in that module, and my code depends on being able to look them up using Foo::Bar.const_defined? at runtime - how do I ensure all Foo::Bar's classes get loaded at startup?
I'm already using config.autoload_paths += %W(#{config.root}/lib) in application.rb.

Putting this in root/config/initializers/eager.rb should load all .rb files in that folder:
Dir["#{Rails.root}/lib/*.rb"].each {|file| load file}

For me putting this in application.rb solved the problem
config.eager_load_paths += Dir["#{config.root}/lib/**/"]

Use eager_load_paths combined with ActiveSupport::Reloader's to_prepare hook inside development.rb:
config.eager_load_paths += Dir["app/models/stimodel/**/*.rb"]
ActiveSupport::Reloader.to_prepare do
Dir["app/models/stimodel/**/*.rb"].each { |f| require_dependency("#{Dir.pwd}/#{f}") }
end
Adding your paths to eager_load_paths make sure that Rails loads them when it starts up. To make sure that Rails reloads our models if we do any changes or add new files, we also need to hook into the Reloader's to_prepare hook and manually require the dependency there.

Related

Rails cache_classes needs to be off to load new classes

In my Rails application I have some classes under the /lib folder:
/lib/ems
/lib/ems/client.rb
/lib/ems/credentials.rb
/lib/ems/v2/base.rb
/lib/ems/v2/policy.rb
And all classes are autoloaded in my application with:
config.autoload_paths += Dir["#{config.root}/lib/**/"]
If I call: Rails.application.instance_variable_get(:"#_all_autoload_paths")
I can clearly see all my classes are being loaded correctly and they work fine in development.
However when in non-development environments, I get the NameError undefined constant for some of the classes under my /lib folder (not all of them, just some of them).
What I noticed is that if turn off class caching like so:
config.cache_classes = false
Then those classes will work fine and can be used without any issue... so it seems that it's not a problem with the autoloading, but rather a caching problem...
If I turn the cache_classes back on again, it then fails straight away.
How can I make it so that Rails can see these classes without having to turn this off?
You need to add the directory to the eager_load_paths as well.
config.autoload_paths += Dir["#{config.root}/lib/**/"]
config.eager_load_paths += Dir["#{config.root}/lib/**/"]
See Don't forget about eager_load when extending autoload paths from Arkency for an excellent (if somewhat dated) rundown on the details.

Auto-loading lib files in Rails 4

I use the following line in an initializer to autoload code in my /lib directory during development:
config/initializers/custom.rb:
RELOAD_LIBS = Dir[Rails.root + 'lib/**/*.rb'] if Rails.env.development?
(from Rails 3 Quicktip: Auto reload lib folders in development mode)
It works great, but it's too inefficient to use in production- Instead of loading libs on each request, I just want to load them on start up. The same blog has another article describing how to do this:
config/application.rb:
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]
However, when I switch to that, even in development, I get NoMethodErrors when trying to use the lib functions.
Example of one of my lib files:
lib/extensions.rb:
Time.class_eval do
def self.milli_stamp
Time.now.strftime('%Y%m%d%H%M%S%L').to_i
end
end
Calling Time.milli_stamp will throw NoMethodError
I realize others have answered similar questions on SO but they all seem to deal with naming conventions and other issues that I didn't to have to worry about before- My lib classes already worked for per-request loading, I just want to change it to per-startup loading. What's the right way to do this?
I think this may solve your problem:
in config/application.rb:
config.autoload_paths << Rails.root.join('lib')
and keep the right naming convention in lib.
in lib/foo.rb:
class Foo
end
in lib/foo/bar.rb:
class Foo::Bar
end
if you really wanna do some monkey patches in file like lib/extensions.rb, you may manually require it:
in config/initializers/require.rb:
require "#{Rails.root}/lib/extensions"
P.S.
Rails 3 Autoload Modules/Classes by Bill Harding.
And to understand what does Rails exactly do about auto-loading?
read Rails autoloading — how it works, and when it doesn't by Simon Coffey.
Though this does not directly answer the question, but I think it is a good alternative to avoid the question altogether.
To avoid all the autoload_paths or eager_load_paths hassle, create a "lib" or a "misc" directory under "app" directory. Place codes as you would normally do in there, and Rails will load files just like how it will load (and reload) model files.
This might help someone like me that finds this answer when searching for solutions to how Rails handles the class loading ... I found that I had to define a module whose name matched my filename appropriately, rather than just defining a class:
In file lib/development_mail_interceptor.rb (Yes, I'm using code from a Railscast :))
module DevelopmentMailInterceptor
class DevelopmentMailInterceptor
def self.delivering_email(message)
message.subject = "intercepted for: #{message.to} #{message.subject}"
message.to = "myemail#mydomain.org"
end
end
end
works, but it doesn't load if I hadn't put the class inside a module.
Use config.to_prepare to load you monkey patches/extensions for every request in development mode.
config.to_prepare do |action_dispatcher|
# More importantly, will run upon every request in development, but only once (during boot-up) in production and test.
Rails.logger.info "\n--- Loading extensions for #{self.class} "
Dir.glob("#{Rails.root}/lib/extensions/**/*.rb").sort.each do |entry|
Rails.logger.info "Loading extension(s): #{entry}"
require_dependency "#{entry}"
end
Rails.logger.info "--- Loaded extensions for #{self.class}\n"
end

how do I unload & load a class?

I've got a class in /lib that I'm mucking with and testing via the console. I'm making changes to the class (adding a debugger line for instance) and using reload! but the new or removed line is not reflected in the version of the code that the console is running.
Tried these:
config.cache_classes = false
reload!
config.autoload_paths += Dir["#{config.root}/lib/**/"]
config.autoload_paths += Dir["#{config.root}/lib/service_processors"]
config.autoload_paths << 'lib'
I'm running a saved script that creates an instance of the class. I tried just loading the class at the top of my script, but that seemed to cause some unintended consequences.
So, how do I completely unload a class and then reload it? I'm thinking the script will force an unload of the class, then load the class via the file name.
Not sure it that is the problem, but in your application.rb you need to add the following line:
config.autoload_paths += %W( #{config.root}/lib )
And secondly, to make sure that classes/modules are found correctly on reload, the naming has to follow Rails conventions. This means that snake-casing a module or class name should give the filename, and different namespaces (or nesting) should be in different folders.
Some examples to make this more clear :)
class SomeClass --> /lib/some_class.rb
class SomeHTTPStuff --> /lib/some_http_stuff.rb
class API::Stuff --> /lib/api/stuff.rb
HTH.
Invalid after Rails tag added
Well, to load a class:
load "lib/class.rb"
To reload:
load "lib/class.rb"
Those settings are irrelevant.

What is the best way to load files from the lib folder that add methods to ~existing~ classes in Rails 3?

I am using config.autoload_paths in a way very similar to this related question to load classes from the lib directory in a Rails 3 project.
Specifically, I've added these lines to the config/application.rb file:
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]
However, this method is not working for me for existing classes. When I add a file like lib/extensions/string.rb:
class String
def foo
puts "foo"
end
end
I get an undefined method 'foo' for "":Stringerror. Through various searches I've got the sense that this problem has to do with the lazy loading of these files. I tried using config.eager_load_paths but was not able to get that to work.
I'm doing exactly what you are describing in my application, and the only difference is that I also have an initializer called extensions.rb with the following code:
Dir.glob('lib/extensions/*').each { |f| require f }

Adding a directory to the load path in Rails?

As of Rails 2.3, what's the right way to add a directory to the load path so that it hooks into Rails' auto-reloading mechanisms?
The specific example I'm thinking of is I have a class that has several sub-classes using STI and I thought it would be a good idea to put them in a sub-directory rather than clutter the top-level. So I would have something like:
#app/models/widget.rb
class Widget < ActiveRecord::Base
add_to_load_path File.join(File.dirname(__FILE__), "widgets")
end
#app/models/widgets/bar_widget.rb
class BarWidget < Widget
end
#app/models/widgets/foo_widget.rb
class FooWidget < Widget
end
It's the add_to_load_path method that I'm looking for.
In the current version of Rails (3.2.8), this has been changed in the application.rb file.
The code is currently commented out as:
# Custom directories with classes and modules you want to be autoloadable.
# config.autoload_paths += %W(#{config.root}/extras)
Will need to update the autoload_paths value. Attempting to modify the the former load_paths variable causes this error.
/configuration.rb:85:in `method_missing': undefined method `load_paths' for #<Rails::Application::Configuration:0xac670b4> (NoMethodError)
for an example, for each path to add to autoload_paths config, add a line similar to the following:
config.autoload_paths += %W(#{config.root}/app/validators)
config.autoload_paths accepts an array of paths from which Rails will autoload constants. Default is all directories under app.
http://guides.rubyonrails.org/configuring.html
From commentor (hakunin) below:
If the directory is under app/, you don't need to add it anywhere, it should just work by default (definitely in 3.2.12). Rails has eager_load_paths that acts as autoload_paths in development, and eager load in production. All app/* directories are automatically added there.
For older versions of Rails:
You can do this in your environment.rb config file.
config.load_paths << "#{RAILS_ROOT}/app/widgets"
--
For Rails 3, see answers bellow
In Rails 5 you don't have to explicitly load folders from within the app directory anymore. All folders placed inside are directly available. You don't have to touch any of the config files. But it seems as if there are some issues with Spring.
The new workflow therefore is:
create a new folder and class inside the /app directory
run spring stop on the command line
check the autoload-paths with bin/rails r 'puts ActiveSupport::Dependencies.autoload_paths' on the command line. The new folder should now be listed.
run spring start on the command line
In Rails 3, you can set this in config/application.rb, where this sample is provided by default:
# Add additional load paths for your own custom dirs
# config.load_paths += %W( #{config.root}/extras )
On Rails 5 you need to add the following code to environment.rb:
# Add the widgets folder to the autoload path
Rails.application.configure do
config.autoload_paths << "#{Rails.root}/app/widgets"
end
Another update for rails 3 -- activesupport 3.0.0:
Instead of:
ActiveSupport::Dependencies.load_paths << "#{RAILS_ROOT}/app/widgets"
You may need to do this:
ActiveSupport::Dependencies.autoload_paths << "#{RAILS_ROOT}/app/widgets"
I found I needed to do this after config block-- no access to config object anymore.
This did the trick
ActiveSupport::Dependencies.load_paths << "#{RAILS_ROOT}/app/widgets"
In config/application.rb add config.autoload_paths << "#{config.root}/models/widgets".
File should look like this:
module MyApp
class Application < Rails::Application
config.autoload_paths << "#{config.root}/models/widgets"
end
end
I know this works for Rails 4 and 5. Probably others as well.
If you want to add multiple directories:
config.autoload_paths += Dir[Rails.root / "components/*/app/public"]
(this is an example for packwerk autoload)

Resources