You can tell Ruby on Rails to respond to changes in certain files during development.
Rails knows to watch config/routes.rb for changes and reload them when the files change. You can use the same mechanism to watch other files and take action when they change.
I used this feature for react-rails server rendering and for GraphQL::Pro static queries.
app.reloader
Every Rails app has a @reloader, which is a local subclass of ActiveSupport::Reloader. It’s used whenever you call reload! in the Rails console.
It’s attached to a rack middleware which calls #run! (which, in turn, calls the reload blocks if it detects changes).
config.to_prepare
You can add custom preparation hooks with config.to_prepare:
initializer :my_custom_preparation do |app|
config.to_prepare do
puts "Reloading now ..."
end
end
When Rails detects a change, this block will be called. It’s implemented by registering the block with app.reloader.
app.reloaders
To add new conditions for which Rails should reload, you can add to the app.reloaders array:
# Object responds to `#updated?`
class MyWatcher
def updated?
# ...
end
end
# ...
initializer :my_custom_watch_condition do |app|
# Register custom reloader:
app.reloaders << MyWatcher.new
end
The object’s updated? method will be called by the reloader. If any reloader returns true, the middleware will run all to_prepare blocks (via the call to @reloader.run!).
FileUpdateChecker
Rails includes a goodie for watching files. ActiveSupport::FileUpdateChecker is great for:
- Watching specific files for changes (
config/routes.rbis watched this way) - Watching a directory of files for changes, additions and deletions (
app/**/*.rbis watched this way)
You can create your own FileUpdateChecker and add it to app.reloaders to reload Rails when certain files change:
# Watch specific files:
app.reloaders << ActiveSupport::FileUpdateChecker.new(["my_important_file.txt", "my_other_important_file.txt"])
# Watch directory-extension pairs, eg all `.txt` and `.md` files in `app/important_files` and subdirectories:
app.reloaders << ActiveSupport::FileUpdateChecker([], { "app/important_files" => [".txt", ".md"] })
Some filesystems support an evented file watcher implementation, ActiveSupport::EventedFileUpdateChecker. app.config.file_watcher will return the proper filewatcher class for the current context.
app.reloaders << app.config.file_watcher(["my_important_file.txt", "my_other_important_file.txt"])
All Together Now
react-rails maintains a pool of V8 instances for server rendering React components. These instances are initialized with a bunch of JavaScript code, and whenever a developer changes a JavaScript file, we need to reload them with the new code. This requires two steps:
- Adding a new watcher to
app.reloadersto detect changes to JavaScript files - Adding a
to_preparehook to reload the JS instances
It looks basically like this:
initializer "react_rails.watch_js_files" do |app|
# Watch for changes to javascript files:
app.reloaders << app.config.file_watcher.new([], {
# Watch the asset pipeline:
Rails.root.join("app/assets/javascripts").to_s => ["jsx", "js"],
# Watch webpacker:
Rails.root.join("app/javascript").to_s => ["jsx", "js"]
})
config.to_prepare do
React::ServerRendering.reset_pool
end
end
The full implementation supports some customization. You can see similar (and more complicated) examples with routes reloading, i18n reloading and .rb reloading.
Happy reloading!