232

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?

Dave Powers
  • 2,051
  • 2
  • 30
  • 34
Yarin
  • 173,523
  • 149
  • 402
  • 512

4 Answers4

550

I think this may solve your problem:

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

Dave Powers
  • 2,051
  • 2
  • 30
  • 34
ifyouseewendy
  • 6,674
  • 1
  • 21
  • 26
  • 1
    @ifyouseewendy- You're exactly right- because **extensions.rb** wasn't following the Rails naming conventions, Rails wouldn't include it in the loading process. I got it working by manually requiring it. – Yarin Oct 31 '13 at 19:38
  • @ifyouseewendy how can I include files before models are loaded? add the path to autoload it's cool, but how control the order of inclusion? thx – Matrix Oct 16 '14 at 15:51
  • @Matrix "include files before models are loaded", you may manually require your file without using autoload feature. – ifyouseewendy Oct 17 '14 at 10:36
  • @ifyouseewendy if I require it in initializer but the file are in autoload_path, it will be reloaded (loaded 2 times) or not? theire is a "require_once" like in php? – Matrix Oct 17 '14 at 10:41
  • @Matrix nope, and why you manually require and add to autoload_path, what's the situation? – ifyouseewendy Oct 17 '14 at 15:57
  • @ifyouseewendy I whant include all files in special folder in "app/", but, I don't want require it one by one, so I add the folder to the path, but certain files have to be include before models to works, so I have a problem^^ – Matrix Oct 17 '14 at 16:12
  • @Matrix Got it, I think you may add these certain files to `eager_load_paths`, which Rails will eager load on boot. – ifyouseewendy Oct 19 '14 at 07:50
  • @ifyouseewendy the problem to this solution is it's loaded only at server start, if I want change data, I have to relaod server, I need a solution with no interuption of service... reloaded each time. – Matrix Oct 19 '14 at 16:55
  • @Matrix Set `config.cache_classes = false` which is default in development env, which makes Rails reload classes and modules on each reqeust. – ifyouseewendy Oct 20 '14 at 04:44
  • @ifyouseewendy If I have files under `lib/dir1/dir2/foo.rb` , your solution doesn't load them on start.. I am in 4.2 – Arup Rakshit Jul 17 '15 at 16:49
  • 5
    This doesn't seem to work in Rails 5 API in production (but does in development). I believe that you need to use `config.eager_load_paths << Rails.root.join('lib')`. However, that has a major downside in that `eager_load_paths` loads everything in tasks as well. I think that lulalala's solution is better. Here's a blog post with more details: http://blog.arkency.com/2014/11/dont-forget-about-eager-load-when-extending-autoload/ – hirowatari Jun 09 '16 at 15:45
  • I'm getting a `can't modify frozen Array` error when appending to `config.autoload_paths <<` in Rails 5.1. This question has the error and solution: https://stackoverflow.com/a/7204837/34315. You need to use concatenation `config.autoload_paths +=`. – gabe Aug 20 '17 at 02:57
34

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.

lulalala
  • 17,572
  • 15
  • 110
  • 169
  • 3
    I am in Rails 4.2. and it doesn't auto load files under `app`, I need to do it manually......or need to put it in the autload path.. – Arup Rakshit Jul 17 '15 at 16:45
  • 6
    You are wrong, Arup, any subdirectories of app directory are automatically in the autoload_paths array in Rails 4.2.See http://edgeguides.rubyonrails.org/autoloading_and_reloading_constants.html – Dr.Strangelove Oct 30 '15 at 08:51
  • Excepting the `app/views` directory which does not get added; or rather gets explicitly removed. – James B. Byrne Dec 16 '15 at 14:21
  • 1
    Great answer. Only thing that worked for me on rails 5/api. – jstafford Feb 29 '16 at 04:51
  • 6
    Just remember that `lib` is meant for code that can be applied to multiple projects and might possibly be extracted to a gem. If not create a more appropriate folder under app search as `services/` or `presenters/` and even subdirs off these. – PhilT Apr 14 '16 at 15:46
  • Will have to move dir under app folder to auto load in Rails 5. Just adding the [GitHub Discussion Link](https://github.com/rails/rails/issues/13142) for reference. – Ashik Salman Mar 02 '18 at 05:34
6

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.

R Milushev
  • 4,295
  • 3
  • 27
  • 35
sameers
  • 4,855
  • 3
  • 35
  • 44
  • 1
    In ruby "matching appropriately" means that the file is located in the file system at `LOAD_PATH/module/class.rb` (underscored) where `LOAD_PATH` is in the load paths used by the Ruby app (autoload_paths in the case of Rails). `lib` has fluctuated from being auto loaded by Rails to not being autoloaded, and in recent versions (>= Rails 3.x) it is not autoloaded. Whatever magic is making this work for you is not recommended. Perhaps it is an old Railscast? – Peter H. Boling Sep 22 '16 at 20:49
0

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

Madx
  • 1