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.rb
is watched this way) - Watching a directory of files for changes, additions and deletions (
app/**/*.rb
is 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.reloaders
to detect changes to JavaScript files - Adding a
to_prepare
hook 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!