We all know Rails has this feature, which reloads source code in development mode everytime a request hits server. Starting from version 3.2.0, it introduced faster dev mode where it reloads application code when the code changes, and not on every request.

This blog will talk about important parts of Rails source code which helps in achieving faster dev mode.

ActiveSupport::FileUpdateChecker : Checks whether any of the application code files are changed.

: Checks whether any of the application code files are changed. Activesupport::Dependencies : Actual module responsible for reloading classes.

: Actual module responsible for reloading classes. ActionDispatch::Reloader : The middleware which helps in reloading classes.

: The middleware which helps in reloading classes. reload_classes_only_on_change : As name states, this configuration option tell Rails to enable/disable code for re-loading classes only if the code changes.

Lets go through them one by one.

This class helps in checking whether application code is updated or not. It exposes 2 methods: updated? and execute . The former tells whether files are updated or not, while latter executes a block given by updating timestamp of latest changed file. The code can be found here. How file checker checks whether a file is updated or not depends on the modified time of the file. There is a small function max_time which returns timestamp for recently modified path (most recent one)

class ActiveSupport :: FileUpdateChecker # NOTE: Removed some code to reflect the logic def updated? current_updated_at = updated_at ( current_watched ) if @last_update_at < current_updated_at @updated_at = current_updated_at true else false end end # Executes the given block and updates the timestamp. def execute @last_update_at = updated_at ( @last_watched ) @block . call end def updated_at ( paths ) @updated_at || max_mtime ( paths ) || Time . at ( 0 ) end # This method returns the maximum mtime of the files in +paths+ def max_mtime ( paths ) paths . map { | path | File . mtime ( path )} . max end end

ActiveSupport::Dependencies

This module consists of the core mechanism to load classes, by following Rails naming conventions. It uses const_missing to catch missing classes, and then searches in autoload_paths to load those missing classes. The code can be found here.

module Dependencies def const_missing ( const_name ) from_mod = anonymous? ? :: Object : self Dependencies . load_missing_constant ( from_mod , const_name ) end def load_missing_constant ( from_mod , const_name ) qualified_name = qualified_name_for ( from_mod , const_name ) path_suffix = qualified_name . underscore file_path = search_for_file ( path_suffix ) if file_path expanded = File . expand_path ( file_path ) require_or_load ( expanded ) end raise NameError , "uninitialized constant #{ qualified_name } " end end

ActionDispatch::Reloader

This is a middleware which provides hooks that can be run while code reloading. It has 2 callback hooks, :prepare and :cleanup . Rails code will make use of these hooks to install code which determine whether to reload code or not. :prepare callbacks will run before request is processed, and :cleanup callbacks will run after request is processed. You can see call(env) of reloader here

class ActionDispatch :: Reloader def call ( env ) @validated = @condition . call prepare! response = @app . call ( env ) response [ 2 ] = :: Rack :: BodyProxy . new ( response [ 2 ] ) { cleanup! } response rescue Exception cleanup! raise end end

reload_classes_only_on_change

This configuration option is defined in railties. By default, it is set to true, so Rails reloads classes only if code changes. Set it to false, and Rails will reload on each request. Digging into the place where this boolean is defined, we find that there is an initializer set_clear_dependencies_hook . This initializer is defined here.

initializer :set_clear_dependencies_hook , group : :all do callback = lambda do ActiveSupport :: DescendantsTracker . clear ActiveSupport :: Dependencies . clear end if config . reload_classes_only_on_change reloader = config . file_watcher . new ( * watchable_args , & callback ) self . reloaders << reloader ActionDispatch :: Reloader . to_prepare ( prepend : true ) do reloader . execute end else ActionDispatch :: Reloader . to_cleanup ( & callback ) end end

The above code installs a file watcher if config var is true. watchable_args consists of autoload_paths along with other files like schema.rb . So, file_watcher is configured to watch these paths. If config var is false, it just installs callback as :cleanup hook, which means all the code will be unloaded after each request is processed.

How do these components fall in place?

By joining all the dots, the sequence is:

Request hits the server. The middleware ActionDispatch::Reloader kicks in, and executes callbacks inserted

kicks in, and executes callbacks inserted One of the callback inserted is to check whether any application code files have been changed and execute the lambda {shown above which clears dependencies}

The callback (lambda) will clear all the dependencies, i.e it unloads all the class constants {by using builtin remove_const }.

}. The middleware passes the request down the middleware stack for proper handling. Most probably routes will process the request.

Once the request handling starts, since all the constants are unloaded, the main module ActiveSupport::Dependencies kicks in, uses const_missing and loads all classes once again, thus loading the modified code.

Bonus

If you want to know how routes reloading happens, check this file

If you have any questions or feedback, feel free to drop us a mail at team@codemancers.com