Ruby Through Rails – Part 1

(This has been reblogged from Sanjiv’s Blog post)

I am starting a series of blog posts where we can learn Ruby through Rails. We will take a deep dive into Rails source code and learn about how Rails works — and learn a lot of Ruby constructs through that!

Which command is more efficient – ‘rails s’ or ‘bundle exec rails s’?

On production environment,  command such as rails, rake, console are typically in gems that are stored in the vendor directory inside the RAILS_ROOT. So it is neccesary to use “bundle exec” prefix. On development environment, the gems are stored in standard ruby gems installation path, so the “rails s” will automatically pick up the correct version of rails if you have a Gemfile defined. However, if you have a different version of the gem (such as rake) installed in your system and a different one specified in your Gemfile, you will need “bundle exec” prefix to use the correct version. Let’s see an example:

$ rails -v
Rails 4.0.0

$ cd my_rails_app
$ rails -v
Rails 3.2.14

The command ‘rails s’ is faster than ‘bundle exec rails s’. With ‘bundle exec’, it makes a call to the bundler gem and then the bundler gem calls ‘script/rails’ in RAILS_ROOT (in Rails3) or ‘bin/rails’ (in Rails4). When we issue command ‘rails s’, it directly call the relevant rails binary bypassing the bunder.

Inside the “script/rails” or “bin/rails” looks like this:


#!/usr/bin/env ruby

APP_PATH = File.expand_path('../../config/application',  __FILE__)
require File.expand_path('../../config/boot',  __FILE__)
require 'rails/commands'

Notice the “require ‘rails/commands'” ? This now checks  for the parameters passed to ‘rails’ command.

   aliases = {"g"  = "generate", "d"="destroy",
              "c"  = "console",  "s"="server",
              "db" = "dbconsole","r"  = "runner"
             }
   command = ARGV.shift
   command = aliases[command] || command

As we can see in above code, there are aliases for generate, server, runner,  etc. Aliases are the short-hand for these commands.

ARGV is ruby global array that contains the parameters passed to the ruby script.  While executing “rails s”, ARGV contain either ‘s’ or ‘server’. The ‘shift’ function the array returns the first element and removes it from array. Hence, in above case these parameter ‘s’ get passed , it will return ‘s’ in ARGV.shift.

We now compare this value using the case statement.

case command
when 'server'
  Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exists?(File.expand_path("config.ru"))
  require 'rails/commands/server'
  Rails::Server.new.tap { |server|
    require APP_PATH
    Dir.chdir(Rails.application.root)
    server.start
  }
end

I have deleted much of code above for clarity and only pasted the needed code. When we pass argument (s, c, etc)  to the rails command, it checks against the various conditions in the case statements. Notice the use of config.ru file for custom configuration.

What is tap?

tap is function in ruby since 1.9.  It’s a helper for call chaining. It passes its object into the given block and after the block finishes, returns the object (in this case, it will return server object). You can read more about tap here.

In case an argument is passed as ‘s’ (or server), it will load the relevant command, in this case “rails/commands/server”. These command files are present in the railties gem

The server command

Here is what happens when we load the server command file.

# railties-3.2.14/lib/rails/commands/server.rb
module Rails
  class Server < ::Rack::Server
    def initialize(*)
      super
      set_environment
    end

    def start
      url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}"
      puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
      puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
      puts "=> Call with -d to detach" unless options[:daemonize]

      trap(:INT) { exit }
      puts "=> Ctrl-C to shutdown server" unless options[:daemonize]

      #Create required tmp directories if not found
      %w(cache pids sessions sockets).each do |dir_to_make|
        FileUtils.mkdir_p(Rails.root.join('tmp', dir_to_make))
      end

      super

      ensure
        # The '-h' option calls exit before @options is set.
        # If we call 'options' with it unset, we get double help banners.
        puts 'Exiting' unless @options && options[:daemonize]
      end
   end
end

We can see in the initialize method that Rails is a Rack application. We can set the environment via the set_environment method or it defaults to development. Now, when the ‘server.start’ is invoked, we see the familiar messages on console about booting the rails server (Webrick, thin etc).

Rails::Server.start method creates the cache, pids, sessions directories and the socket file if they are not found inside application tmp directory.  Remember, the tmp directory must be present inside the RAILS_ROOT otherwise it will throw error while booting rails server.

How do we boot different Rails servers?

Ever wonder how the various servers like thin , webrick etc. are handled with the same code Rails::Server.start? In above code, did you notice the “options” hash used in the start method? Where did that come from?  “options” is defined in the super class of Rails::Server. In the super class Rack::Server.start, it calls parse! method that parses the options passed and any argument passed after ‘rails s’ like thin. mongrel, will now be used as our Rails server!

Let us now see what defined in Rack::Server.start method.

   module Rack
     class Server
       def start &blk
         if options[:warn]
           $-w = true
         end

         if includes = options[:include]
           $LOAD_PATH.unshift(*includes)
         end

         if library = options[:require]
           require library
         end

         if options[:debug]
           $DEBUG = true
           require 'pp'
           p options[:server]
           pp wrapped_app
           pp app
         end

         check_pid! if options[:pid]

         # Touch the wrapped app, so that the config.ru is loaded before
         # daemonization (i.e. before chdir, etc).
         wrapped_app

         daemonize_app if options[:daemonize]

         write_pid if options[:pid]

         trap(:INT) do         #to handle the shutdown of server
           if server.respond_to?(:shutdown)
             server.shutdown
           else
             exit
           end
         end

         #server is function which return the Rack::Handle and will be used for determing which server(webrick should
         run based on handle mean server options it will gets)
         server.run wrapped_app, options, &blk
     end

     def server
       @_server ||= Rack::Handler.get(options[:server]) || Rack::Handler.default(options)
     end
   end

This code checks the various options passed during initialization process. It checks if the webserver is already started using the pid file (RAILS_ROOT/tmp/pids/server.pid). If you want to start another server, you have the pass pid file with options -P #{different pid file} and also specify an unused port number. Otherwise, you will get an error.

You can also pass the -d option to rails command which will demonize the rails server. By default rails server start in the foreground  and a CTRL+C will raise trap and shutdown the server.

Are you still wondering where the exact invocation was to boot different Rails servers like webrick, thin, mongrel?
Here is the open secret:


#rack-1.4.5/lib/rack/handler.rb
module Rack
# *Handlers* connect web servers with Rack.
#
# Rack includes Handlers for Thin, WEBrick, FastCGI, CGI, SCGI
# and LiteSpeed.
#
# Handlers usually are activated by calling <tt>MyHandler.run(myapp)</tt>.
# A second optional hash can be passed to include server-specific
# configuration.

  #Rack::Handler#get return the name of server(webrick, mongrel)
  module Handler
    def self.get(server)    # these code get invoke when Rack::Handler.get(options[:server])
                            #|| Rack::Handler.default(options) call in Rack::Server server method
      return unless server
      server = server.to_s

      unless @handlers.include? server
        load_error = try_require('rack/handler', server)
      end

      if klass = @handlers[server]
        klass.split("::").inject(Object) { |o, x| o.const_get(x) }
      else
        const_get(server)
      end

    rescue NameError => name_error
      raise load_error || name_error
    end
  end
end

When server.run(server is method in Rack::Server class explained above) gets called, then the above code ensures that the corresponding webserver get called. (default is webrick). Inside the rack gem, there is handler for various servers (like webrick, thin, mongrel). The handlers inside “rack/handler” directory act as the glue for these different server gems. If you want to run mongrel, the handler needs to be defined in  rack-1.4.5/lib/rack/handler.rb (rack gem). Except for webrick, we need to install the other rails webserver gems. Though there are only a fixed number of handlers defined in rack/handler directory, we can register our own webserver too.

How do you register other webserver ?

All webserver handlers can be registered as shown below

  #rack-1.4.5/lib/rack/handler.rb
module Rack
  module Handler
   def self.register(server, klass)  #these will register the new handler for any webserver.
     @handlers ||= {}
     @handlers[server.to_s] = klass.to_s
   end
  end
  register 'webrick', 'Rack::Handler::WEBrick'
end

In my next post, I will discuss how the bundler works in detail – how dependencies are resolved, what makes bundler so awesome. In this we shall also learn some really good ruby code. For example, did you know how the bundler uses ‘throw’ and ‘catch’ to resolve dependencies? (Yes, throw and catch do exist in ruby alongside raise and rescue!)

Advertisements

About Sanjiv Kumar Jha

Ruby Developer at Josh Software
This entry was posted in Ruby, Ruby on Rails, Tutorials and tagged . Bookmark the permalink.

5 Responses to Ruby Through Rails – Part 1

  1. Awesome Post, Love it.!!

  2. Please make more posts like this one. Great for learning

  3. After the person has been arrested, but before the officer asks certain
    questions, the officer must read a person’s Miranda Rights.

    Second, if you want to find a good Charlotte DWI lawyer, the defendant must do some legwork.

    The lawyer you hire should have your best interest, this means handling the case themselves and not letting a
    paralegal handle most of the work.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s