Auto-loading lib files in Rails 4
Asked Answered
U

4

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?

Uund answered 30/9, 2013 at 15:58 Comment(1)
Is the config/initializers folder automatically loaded when a Rails app starts up?Boggle
C
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.

Conakry answered 29/10, 2013 at 5:41 Comment(12)
@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.Uund
@Conakry how can I include files before models are loaded? add the path to autoload it's cool, but how control the order of inclusion? thxTannate
@Tannate "include files before models are loaded", you may manually require your file without using autoload feature.Conakry
@Conakry 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?Tannate
@Tannate nope, and why you manually require and add to autoload_path, what's the situation?Conakry
@Conakry 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^^Tannate
@Tannate Got it, I think you may add these certain files to eager_load_paths, which Rails will eager load on boot.Conakry
@Conakry 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.Tannate
@Tannate Set config.cache_classes = false which is default in development env, which makes Rails reload classes and modules on each reqeust.Conakry
@Conakry If I have files under lib/dir1/dir2/foo.rb , your solution doesn't load them on start.. I am in 4.2Djerba
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: blog.arkency.com/2014/11/…Boycie
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://mcmap.net/q/119791/-can-39-t-modify-frozen-array-typeerror-config-application-rb-42-in-lt-lt-39. You need to use concatenation config.autoload_paths +=.Fraunhofer
H
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.

Hospers answered 25/9, 2014 at 10:27 Comment(6)
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..Djerba
You are wrong, Arup, any subdirectories of app directory are automatically in the autoload_paths array in Rails 4.2.See edgeguides.rubyonrails.org/…Sedimentation
Excepting the app/views directory which does not get added; or rather gets explicitly removed.Supplant
Great answer. Only thing that worked for me on rails 5/api.Stereotomy
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.Cardew
Will have to move dir under app folder to auto load in Rails 5. Just adding the GitHub Discussion Link for reference.Beals
C
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 = "[email protected]"
    end
  end
end

works, but it doesn't load if I hadn't put the class inside a module.

Constipate answered 24/1, 2014 at 5:28 Comment(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?Statistical
S
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

Selfabnegation answered 9/7, 2017 at 21:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.