Ruby for Rails: Ruby Techniques for Rails Developers
The Web server—Apache, light TPD , or whatever it may be on a given system—
receives the request from the browser. The server configuration causes the server
to pass the request along to what will turn out to be a Rails application. The server
doesn’t know what Rails is; it just does whatever redirecting or deflecting of the
incoming request it’s set up to do.
The dispatcher’s job is to dispatch the request—that is, to send it to the appropriate controller.
Controllers are the subprograms in a Rails application that per-
form tasks. They reach back into the database and get data, they search and sort,
they test for password matches, and so forth. Typically, a Rails application has sev-
eral controllers, and each controller is capable of multiple actions. For example,
you may have a customer controller that can perform login, logout, edit (edit pro-
file), and other actions.
When the appropriate action, inside the appropriate controller, is executed, it has
automatic access to the following:
The controller action also has access to its own session information. Rails
applications can cache information from one invocation to another. This can be
handy, for instance, for enabling customers to navigate a site without having to log
in every time they go to a different part of the site. The login status is maintained
in the session cache and checked for validity.
You’re now on the downslope of the process. The rest of the controller’s job is to
pass the data to the view. The view fills in its template, resulting in an HTML docu-
ment that is then handed to the Web server and from there back to the original
Web client.
Parse and "understand" the request, and make a decision on how to service that request.
For simple requests, like service static assets, you can just configure the webserver to do that.
In more complicated scenarios server hand these requests off to Rails for further processing using RACK.
First the web server prepares a hash, which is conventionally called the "env hash". The env hash
contains all the information from the HTTP request – for example, REQUEST_METHOD contains
the HTTP verb, PATH_INFO contains the request path and HTTP_* has the corresponding
header values.
On the other hand, the "app" or framework must implement a #call method. The server will expect
it to be there and invoke it with the env hash as the only argument. It is expected to handle the request
based on the information in the env hash and return an array with exactly three
things in it (a.k.a. "a tuple of three").
HTTP status code, hash containing the response headers, array of response body
# app.rb class HelloWorld def call(env) if env['PATH_INFO'] == '/hello' [200, {'Content-Type' => 'text/plain'}, ['Hello World']] else [404, {'Content-Type' => 'text/plain'}, ['Not Found']] end end end
# config.ru require_relative 'app' run HelloWorld.new
$ rackup
# app.rb class Redirect def initialize(app, from:, to:) @app = app @from = from @to = to end def call(env) if env["PATH_INFO"] == @from [301, {"Location" => @to}, []] else @app.call(env) end end end class HelloWorld def call(env) if env["PATH_INFO"] == '/hello' [200, {"Content-Type" => "text/plain"}, ["Hello World!"]] else [404, {"Content-Type" => "text/plain"}, ["Not Found!"]] end end end
# config.ru require_relative 'app' run Redirect.new( HelloWorld.new, from: '/', to: '/hello' )
# config.ru require_relative 'app' use Redirect, from: '/', to: '/hello' run HelloWorld.new
# config.ru require_relative 'config/environment' run Rails.application
Rails us different way to register middleware. Instead in config.ru
we use $ rails middleware
command to see middlewares.
➜ r4music1 git:(feature) ✗ rails middleware use ActionDispatch::HostAuthorization use Rack::Sendfile use ActionDispatch::Static use ActionDispatch::Executor use ActionDispatch::ServerTiming use ActiveSupport::Cache::Strategy::LocalCache::Middleware use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use ActionDispatch::RemoteIp use Sprockets::Rails::QuietAssets use Rails::Rack::Logger use ActionDispatch::ShowExceptions use WebConsole::Middleware use ActionDispatch::DebugExceptions use ActionDispatch::ActionableExceptions use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::Migration::CheckPending use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ContentSecurityPolicy::Middleware use ActionDispatch::PermissionsPolicy::Middleware use Rack::Head use Rack::ConditionalGet use Rack::ETag use Rack::TempfileReaper run R4music1::Application.routes
In case of controlling which middleware should be remove use conf/application.rb
# config/application.rb require_relative 'boot' require 'rails/all' Bundler.require(*Rails.groups) module Blorgh class Application < Rails::Application # Disable cookies config.middleware.delete ActionDispatch::Cookies config.middleware.delete ActionDispatch::Session::CookieStore config.middleware.delete ActionDispatch::Flash # Add your own middleware config.middleware.use CaptchaEverywhere end end
So according to rails middleware
output we know that rack app in this case is R4music1::Application.routes.
This Rack app looks at the request URL, matches it against a bunch of routing rules to find the right controller/action to call. Rails generates this app for you by Rails based on your config/routes.rb.
# config/routes.rb Rails.application.routes.draw do resources :posts end
And finally Action Controller
is rack app itself.
Finally, putting everything together, you can imagine the routes app is a rack app that looks something like this:
class R4music1Routes def call(env) verb = env['REQUEST_METHOD'] path = env['PATH_INFO'] if verb == 'GET' && path == '/posts' PostsController.action(:index).call(env) elsif verb == 'GET' && path == '/posts/new' PostsController.action(:new).call(env) elsif verb == 'POST' && path == '/posts' PostsController.action(:create).call(env) elsif verb == 'GET' && path =~ %r(/posts/.+) PostsController.action(:show).call(env) elsif verb == 'GET' && path =~ %r(/posts/.+/edit) PostsController.action(:edit).call(env) elsif verb == 'PUT' && path =~ %r(/posts) PostsController.action(:update).call(env) elsif verb == 'DELETE' && path = %r(/posts/.+) PostsController.action(:destroy).call(env) else [404, {'Content-Type': 'text-plain', ...}, ['Not Found!']] end end end
It matches the given request path and http verb against the rules defined in your routes config, and delegates to the appropriate Rack app on the controllers.
To replay you need to login. Don't have an account? Sign up for one.