(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!)
Awesome Post, Love it.!!
Thanks
Please make more posts like this one. Great for learning
Sure! Thanks for reading. I have started these as part of learning series
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.