How rails reloads your source code in development mode?

Written by Yuva on October 3, 2013; tagged under ,

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.

Lets go through them one by one.

ActiveSupport::FileUpdateChecker

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:

Bonus

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

Hope you have enjoyed this article, and follow us on twitter @codemancershq for all the awesome blogs, or you can use rss feeds.

Isolate Namespace in Rails Engines - A hack in itself

Written by Yuva on September 22, 2013; tagged under , , ,

isolate_namespace is that one feature which Rails boasts about to use while creating gems, but doesn’t work when one wants to extend models, controllers and views which are provided by that gem. We have used it while developing one of our gems and found that its really hard to extend models, controllers and views. For instance, if you copy views from gem to main application in order to customize them, and try to use routes defined in the main application, you will be slapped with an error saying those routes are not defined! It takes some time to understand how it works under the hood, and all it boils down to is: isolate_namespace is nothing but a bunch of hacks. This blog post will do a code walk-through and will try to explain how it works:

The isolate_namespace method

This method can be found in railties-engine file. Its reads as:

def isolate_namespace(mod)
  engine_name(generate_railtie_name(mod))

  self.routes.default_scope =
    { module: ActiveSupport::Inflector.underscore(mod.name) }
  self.isolated = true

  unless mod.respond_to?(:railtie_namespace)
    name, railtie = engine_name, self

    mod.singleton_class.instance_eval do
      define_method(:railtie_namespace) { railtie }

      unless mod.respond_to?(:table_name_prefix)
        define_method(:table_name_prefix) { "#{name}_" }
      end

      # removed code for :use_relative_model_naming?
      # removed code for :railtie_helpers_paths

      unless mod.respond_to?(:railtie_routes_url_helpers)
        define_method(:railtie_routes_url_helpers) {
          railtie.routes.url_helpers
        }
      end
    end
  end
end

mod is module, which is has to be isolated. In case of blorgh gem, its Blorgh itself. Hence forth, we will use blorgh gem as an example given in rails guides

  1. routes.default_scope: It defines default scope for routes. This scope will be used while generating routes for Rails engine. It says module to be used is blorgh, and all the controllers will be searched under gem. This can be easily understood. Just put a binding.pry inside routes.rb of gem, and you can see this:

    [4] pry(#<Mapper>)> self.instance_variable_get(:@scope)
    => {:path_names=>{:new=>"new", :edit=>"edit"},
    :module=>"blorgh",
    :constraints=>{},
    :defaults=>{},
    :options=>{}}

    It says default module that should be used is blorgh. All the controllers will be prepended by blorgh/

  2. If module doesn’t respond to railtie_namespace {generally modules dont!}, it goes ahead and adds bunch of methods to module {i.e Blorgh for example}, and not to engine. Thats why its a hack! There is nothing done on engine! Everything is added to Blorgh module. So, what it adds exactly?

  3. table_name_prefix: This can be easily guessed. It will be used by active record. Now searching through activerecord source, we find this:

    def full_table_name_prefix #:nodoc:
      (parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).
        table_name_prefix
    end

    It looks tricky, but this is how it works. It searches through all the parents of the AR class, and checks whether any of them responds to table_name_prefix and returns it. {Default value is empty string}. Well, parents are not the parents of the class, but the hierarchy of the modules. activesupport defines this method:

    # Returns all the parents of this module according to its name,
    # ordered from nested outwards. The receiver is not contained
    # within the result.
    #
    #   module M
    #     module N
    #     end
    #   end
    #   X = M::N
    #
    #   M.parents    # => [Object]
    #   M::N.parents # => [M, Object]
    #   X.parents    # => [M, Object]
    def parents
      parents = []
      if parent_name
        parts = parent_name.split('::')
        until parts.empty?
          parents << ActiveSupport::Inflector.constantize(parts * '::')
          parts.pop
        end
      end
      parents << Object unless parents.include? Object
      parents
    end

    Now, here comes the funny part: Create a folder called blorgh in your main application under app/models folder, and create a model called Test

    module Blorgh
      class Test < ActiveRecord::Base
      end
    end

    Now, fire up console, and execute the following:

    [1] pry(main)> Blorgh::Test.table_name
    => "blorgh_tests"

    See, it automatically prepends tests table name with blorgh_. One will be wondering how did that happen? Well it happened because of the module called Blorgh. So anything one puts under Blorgh module will get special treatment. Do it with any other namespace, {i.e module}, it will just return tests. The only way you can get rid of this behavior is to specify the table name explicity on model using self.table_name = "tests". If someone in some gem magically says to isolate a namespace which you are using in your application, hell breaks loose! You will be wondering why your application code is behaving strangely.

    You can find other hacks by searching through the Rails code. We will cover another hack here:

  4. railtie_routes_url_helpers: This method is used to define route helpers which can be accessible to generate paths. Digging through the code, you can find it in actionpack.

    module AbstractController
      module Railties
        module RoutesHelpers
          def self.with(routes)
            Module.new do
              define_method(:inherited) do |klass|
                super(klass)
                namespace = klass.parents.detect { |m|
                  m.respond_to?(:railtie_routes_url_helpers)
                }
    
                if namespace
                  klass.send(:include,
                      namespace.railtie_routes_url_helpers)
                else
                  klass.send(:include, routes.url_helpers)
                end
              end
            end
          end
        end
      end
    end

    This inherited method will be called in the context of your controllers. Again if your controller is under Blorgh module, it magically includes only the routes defined by gem, otherwise it includes the application route helpers. Thats why even though you copy views from gem to your main app, they still cannot access helpers defined by main application. Generally we all know how to fix this: Call the url helpers by prepending with either main_app or gem mount point, i.e blorgh here. This way all the route helpers will be available in all the views.

    = link_to "Surveys", blorgh.question_groups_path
    = link_to "Login", main_app.new_user_session_path
  5. Other issues include extending models and controllers. Rails guides gives two options here. One to use class_eval, and other to use concerns introduced in Rails 4. Both are kind of hacky. Hope there is a better solution.