Ruby on Rails 3 - Reload lib directory for each request
Asked Answered
B

11

62

I'm creating a new engine for a rails 3 application. As you can guess, this engine is in the lib directory of my application.

However, i have some problems developing it. Indeed, I need to restart my server each time I change something in the engine.

Is there a way to avoid this ?

Can I force rails to completely reload the lib directory or a specific file and his requirements for each request ?

Thanks for your help :)

Bald answered 19/7, 2010 at 15:48 Comment(0)
S
37

TL;DR

  • put this in config/application.rb

    config.eager_load_paths += ["#{Rails.root}/lib"]

  • remove require statements for your lib files

Go!


Let me explain in detail.

I don't know why this answer is accepted, since it doesn't help with reloading lib folder on each request. First I thought that it works for Rails 2, but the question clearly states that it was for Rails 3 and the release date of 3.0.0 is before the date of the answer.

Other answers seem over-complicated or don't provide a real solution.

I decided to investigate things a little, because it was bothering me and I've even found out that people have a workaround for this and it involves saving lib files inside app/models in development and then moving it to /lib when done. We can do better, right?


My solution is tested against:

  • Rails 3.0.20
  • Rails 3.1.12
  • Rails 3.2.13
  • Rails 4.0.0.rc1

Put this into your config/application.rb:

# in config/application.rb
config.eager_load_paths += ["#{Rails.root}/lib"]

That's it!™

Make sure you put it here since it will not work if you put it in config/environments/development.rb, for example.

Make sure your remove all the require statements for your /lib code since require statements will also cause this solution to not work.


This code implicitly requires your code, so if you do environment checks (which are unnecessary) and instead of the above code, you decide to write something like this:

# in config/application.rb
config.eager_load_paths += ["#{Rails.root}/lib"] if Rails.env.development?

you should watch out on the old require statements, since they are still required on all the non-development environments, in this scenario.

So if you still decide to do environment checks, make sure you do inverse checks for require statements. Otherwise you'll get bitten!

require "beer_creator" unless Rails.env.development?

You might think that writing entire paragraph about something that's unnecessary is also unnecessary, but I think that warning people about something that's necessary when doing something unnecessary is also necessary.

If you would like to know more about this topic, check out this little tutorial.

Slype answered 18/5, 2013 at 14:10 Comment(7)
That was a while back buck you can also use load without the eager_load_path. Create helper that take the string as a parameter and use load if env is dev and require if env is prod. If you try that please let me know if it works :)Bald
Doesn't seem to work on Rails 3.0.20 with or without the extra '}' at the end of the string.Demiurge
This didn't work for me on 3.2.13 -- instead, after adding the eager_load_paths, all my other files (controllers + models) stopped getting reloaded between requests also! (Goes back to normal if I remove that line.) Bizarre. I finally got it working with this answer: https://mcmap.net/q/276676/-rails-reloading-quot-lib-quot-files-without-having-to-restart-server-duplicateSharanshard
Are you able to get this to work in Rails 4.1? I have a question (with bounty) about this here, but have not found an answer yet.Armagh
removing the require statements lead to the following error NameError (uninitialized constant ...Retorsion
Is this going to cause them to reload in production mode as well? What if you only want it in development mode?Exchangeable
Correct answer. Confirmed this works on Rails 6.I also had subdirectories in my lib/ directory that I had to explicitly add to config.eager_load_paths, or else they would not be hot-reloaded. Any classes that you have loaded in using a require statements or require_relative statement will not be hot-reloadable.Venice
E
50

I couldn't get any of the above to work for me so I dug in the Rails code a bit and came up with this:

New file: config/initializers/reload_lib.rb

if Rails.env == "development"
  lib_reloader = ActiveSupport::FileUpdateChecker.new(Dir["lib/**/*"]) do
    Rails.application.reload_routes! # or do something better here
  end

  # For Rails 5.1+
  ActiveSupport::Reloader.to_prepare do
    lib_reloader.execute_if_updated
  end

  # For Rails pre-5.1 
  ActionDispatch::Callbacks.to_prepare do
    lib_reloader.execute_if_updated
  end

end

Yes, I know it's disgusting but it's a hack. There might be a better way to trigger a full reload, but this works for me. My specific use case was a Rack app mounted to a Rails route so I needed it to reload as I worked on it in development.

Basically what it does is it checks if any files in /lib have changed (modified timestamp) since last loaded and then triggers a reload if they change.

I might also mention I have this in my config/application.rb

config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]

Which just by default makes sure everything in my lib directory gets loaded.

Yays!

Extremity answered 6/12, 2010 at 16:49 Comment(8)
Excellent - works great if you are subclassing from a gem. I used this with Grape (eg /lib/appname/api.rb starts with Appname::API < Grape::API) and it works (while the autoload_paths fix doesn't).Paroxysm
@pbhogan The method signature for FileUpdateChecker#initialize has changed a bit in Rails 3.2.x. Get rid of true and everything should work fine again.Optometrist
This worked for me with the Grape gem also, thanks @nfm. I did however have to change the FileUpdateChecker syntax slightly to: ActiveSupport::FileUpdateChecker.new([], Dir["lib/**/*"])Hydrous
Can someone edit this post and explain how to reload files from the lib directory? The current example only reloads routes (if I'm reading it correctly), which doesn't answer the question.Pastelki
@Tyler Collier Have you tried it? It reloads all files in lib/**/*.Tongue
@Thilo, maybe not. I thought I had, but based on how I phrased it, perhaps I only read the answer without trying it? I do remember thinking it wouldn't work because it just updated the autoload_paths. Let's assume it does work, thanks.Pastelki
This seems to work (thanks!), but why does Rails.application.reload_routes! reload the app?Blackmarketeer
In Rails 5.1+ ActionDispatch::Callbacks.to_prepare is deprecated in favor of ActiveSupport::Reloader.to_prepareMassotherapy
S
37

TL;DR

  • put this in config/application.rb

    config.eager_load_paths += ["#{Rails.root}/lib"]

  • remove require statements for your lib files

Go!


Let me explain in detail.

I don't know why this answer is accepted, since it doesn't help with reloading lib folder on each request. First I thought that it works for Rails 2, but the question clearly states that it was for Rails 3 and the release date of 3.0.0 is before the date of the answer.

Other answers seem over-complicated or don't provide a real solution.

I decided to investigate things a little, because it was bothering me and I've even found out that people have a workaround for this and it involves saving lib files inside app/models in development and then moving it to /lib when done. We can do better, right?


My solution is tested against:

  • Rails 3.0.20
  • Rails 3.1.12
  • Rails 3.2.13
  • Rails 4.0.0.rc1

Put this into your config/application.rb:

# in config/application.rb
config.eager_load_paths += ["#{Rails.root}/lib"]

That's it!™

Make sure you put it here since it will not work if you put it in config/environments/development.rb, for example.

Make sure your remove all the require statements for your /lib code since require statements will also cause this solution to not work.


This code implicitly requires your code, so if you do environment checks (which are unnecessary) and instead of the above code, you decide to write something like this:

# in config/application.rb
config.eager_load_paths += ["#{Rails.root}/lib"] if Rails.env.development?

you should watch out on the old require statements, since they are still required on all the non-development environments, in this scenario.

So if you still decide to do environment checks, make sure you do inverse checks for require statements. Otherwise you'll get bitten!

require "beer_creator" unless Rails.env.development?

You might think that writing entire paragraph about something that's unnecessary is also unnecessary, but I think that warning people about something that's necessary when doing something unnecessary is also necessary.

If you would like to know more about this topic, check out this little tutorial.

Slype answered 18/5, 2013 at 14:10 Comment(7)
That was a while back buck you can also use load without the eager_load_path. Create helper that take the string as a parameter and use load if env is dev and require if env is prod. If you try that please let me know if it works :)Bald
Doesn't seem to work on Rails 3.0.20 with or without the extra '}' at the end of the string.Demiurge
This didn't work for me on 3.2.13 -- instead, after adding the eager_load_paths, all my other files (controllers + models) stopped getting reloaded between requests also! (Goes back to normal if I remove that line.) Bizarre. I finally got it working with this answer: https://mcmap.net/q/276676/-rails-reloading-quot-lib-quot-files-without-having-to-restart-server-duplicateSharanshard
Are you able to get this to work in Rails 4.1? I have a question (with bounty) about this here, but have not found an answer yet.Armagh
removing the require statements lead to the following error NameError (uninitialized constant ...Retorsion
Is this going to cause them to reload in production mode as well? What if you only want it in development mode?Exchangeable
Correct answer. Confirmed this works on Rails 6.I also had subdirectories in my lib/ directory that I had to explicitly add to config.eager_load_paths, or else they would not be hot-reloaded. Any classes that you have loaded in using a require statements or require_relative statement will not be hot-reloadable.Venice
P
21

Since we are talking Rails, the easiest way is to 'require' your lib/* .rb files using 'require_dependency'. So long as the controller/helper/etc (.rb files under app/) uses require_dependency instead of just require reloading works, without the need to do anything funky.

Before I went down that track, the only solution that worked was the one on hemju.com, but I really did not want to have to hack the ApplicationController for Dev speed.

Pavlodar answered 25/7, 2011 at 4:51 Comment(2)
This seems to have worked for me too. Too bad it can't just work with a plain require though...Necklace
Works great with Rails 3.2.6. This is the best and cleanest answer definitely.Berlyn
S
13

You have to add

config.autoload_paths += %W(#{config.root}/lib)

to your Application class in config/application.rb

https://rails.lighthouseapp.com/projects/8994/tickets/5218-rails-3-rc-does-not-autoload-from-lib

Syllabus answered 16/9, 2010 at 10:7 Comment(3)
It's probably best to put it in development.rbJaclynjaco
This allows my files in lib to be autoloaded (loaded when you access the constant by the same name), but doesn't seem to help cause them to be reloaded on each request/when modified. I'm on Rails 3.2.3.Necklace
yes, with this code files in lib are autoloaded but not reloaded, BUT when you put your files somewhere else they reloads: $:.unshift(config.root); config.autoload_paths += %w(app/models/misc) I think about my lib files as business logic, and I put business logic into my models also, so they are my Models but not db-persisted, and it is right that they are in app/models/misc and not in lib.Tasman
J
3

In RAILS 3, here's the secret sauce to auto-reload lib files. The code below's a bit overkill, for the example, but it's what I did to make it work. You can change the message in YoYo#gogo and see it on the screen each page load. Remove out the initializer and it stays the same.

/config/initializers/lib_reload.rb (new file)

ActiveSupport::Dependencies.explicitly_unloadable_constants << 'YoYo'
ActiveSupport::Dependencies.autoload_once_paths.delete(File.expand_path(File.dirname(__FILE__))+'/lib')

/lib/yo_yo.rb

class YoYo
  def gogo
    "OH HAI THERE"
  end
end

/app/controllers/home_controller

require 'yo_yo'
class HomeController < ApplicationController
  def index
    @message = YoYo.new.gogo
  end
end
Jointure answered 20/7, 2010 at 13:35 Comment(2)
I believe that in Rails 3 it is autoload_once_paths instead of load_once_pathsTermination
best answer, but you can shorten File.expand_path(File.dirname(FILE))+'/lib' with "#{Rails.root}/lib" and also put an if Rails.env.development? checkSanferd
S
3

Here is my version inspired from @pbhogan's answer that reloads all the ruby files in your rails /lib directory when any of those files is changed.

It also silences warnings to avoid messages regarding already initialized constants.

Works as of Rails 3.2.8

if Rails.env.development?

  lib_ruby_files = Dir.glob(File.join("lib/**", "*.rb"))
  lib_reloader ||= ActiveSupport::FileUpdateChecker.new(lib_ruby_files) do
    lib_ruby_files.each do |lib_file|
      silence_warnings { require_dependency(lib_file) }
    end
  end

  ActionDispatch::Callbacks.to_prepare do
    lib_reloader.execute_if_updated
  end

end
Siegel answered 26/9, 2012 at 21:30 Comment(0)
C
2

Add to application_controller.rb or your base controller:

  before_filter :dev_reload if Rails.env.eql? 'development'

  def dev_reload
    # add lib files here
    ["rest_client.rb"].each do |lib_file|
      ActiveSupport::Dependencies.load_file lib_file
    end
  end

Worked for me.

Cowage answered 28/10, 2013 at 11:46 Comment(0)
W
1

Updated answer

I sum up all my findings on my blog, you better look there:

Old answer

I looked around for a solution for this too, and (for completeness' sake and also to point others into this direction) here is what I found.

As of Rails3.1, engines can easily be generated through the command rails plugin new my_plugin --full. This generates the skeleton for an engine.

--full means that the engine will be "merged" right into the including application, so that for example controllers should be directly accessible as if they were defined in the including app. This lets you e.g. have a helper file in my_engine/app/helpers/my_helper.rb that will be merged right into your including app's app/helpers/my_helper.rb helper.

There's another option --mountable which creates a namespace for the engine so that its controllers etc. will never collide with the including application's ones. This results in e.g. a helper being in my_engine/app/helpers/my_engine/my_helper.rb which won't collide with a helper app/helpers/my_helper.rb in your including app.

Now the more interesting part:

Within the generated engine's test folder, there's a dummy folder which holds a complete Rails application! What's it for?

When you develop an engine, its functionalities are meant to work completely on their own, and it should also be tested completely on its own. So it's the "wrong" way to develop an engine "within" another Rails app (though this intuitively often will feel right when extracting existing functionalities from a Rails app into an engine), and so theoretically it is also not needed to reload an engine's code with every request to the including application.

The "right" way seems to be this: develop and test your engine, as if it were a full Rails app using the dummy app! Therein you can do everything you can do in any "normal" Rails app, e.g. create controllers, models, views, etc. which use the functionalities the engine should provide. You also can normally start a server using rails s in your test/dummy directory and access the dummy app on localhost:3000, and when running your tests, the dummy app is automatically used for integration tests. Quite nice! :-)

You have to be careful where to put your stuff:

  • Any functionality that is meant to be used within another Rails app goes into my_engine/app, while any functionality that is only needed to test the engine's functionality goes into test/dummy/app.

Then afterwards you can easily load your engine in your main app's Gemfile like this: gem 'my_engine', :path => 'path/to/my_engine' or publish it to GitHub as a gem.

(One interesting thing (and to come back to this topic's subject) is that when I start the dummy's server, then all changes in the engine seem to be reflected within it! So somehow it seems to be possible to include an engine within a Rails app without caching it...? I don't know how this happens.)

So to sum up: an engine provides functionality that can stand completely on its own, so it should also be developed and tested on its own. Then, when it has reached a stable state, it can be included by any other app that needs its functionality.

Here's some resources I find useful:

I hope you find this answer useful. I'm still very new to engines in general, so if there's any wrong information, please tell me, and I'll correct it.

Ward answered 20/9, 2012 at 7:35 Comment(0)
B
0

note that in Rails 3 "load_once_paths" becomes "autoload_once_paths."

Also, it appears that it should be empty unless you explicitly put something in it.

Bugeye answered 16/9, 2010 at 19:7 Comment(0)
U
0

Also, make sure that you comment out the following line in application.rb (in addition to @dishod's solution), and make sure that your module name is the same as your file name (otherwise, rails won't be able to find it)

#Dir.glob("./lib/*.{rb}").each { |file| require file } # require each file from lib directory
Uniparous answered 28/10, 2011 at 6:23 Comment(0)
P
0

Worked for Rails 3.2.13 for reloading lib inside of gem of an app:

require_dependency 'the_class'

AND

config.autoload_paths += %W(#{config.root}/../fantasy/lib)

Proconsul answered 26/6, 2013 at 13:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.