ActionController - Rails Internals Series - III
05 Apr 2016 ActionController RailsThis is my third post of my Rails Internals series mainly focusing on ActionController library and its modules.
The ‘VC’ layer of the MVC paradigm is responsible for the handling incoming HTTP request and responding with any one of template formats HTML/XML/JSON/PDFs and much more. The view and controller layer of Rails are heavily interdependent on one another.
In Rails, ActionPack is the component/package in charge of VC layer of the application. ActionPack composes of several modules for different mechanisms. From API documentation,
Rails Controller
Let’s dive deep into the controller layer.
Controllers in Rails are backed up by ActionController component. It receives request from the browser, fetch or save data in database and renders HTML output to the browser. All the controllers inherit from ApplicationController which in turn inherits from ActionController::Base class. Having ApplicationController gives us ability to configure things in one place and have the functionality available in all other controllers of the application. For example, adding features to ApplicationController like request forgery protection and authorizing the user before executing any controller actions are inherited by other controllers of the application. ActionController::Base provides us with number of helpful methods.
Categories of ActionController Methods
I would like to categorize the methods added by the ActionController for understanding purpose.
- Input category methods → request, params, session, cookie (if one available)
- Storage category methods → session, cookie, flash
- Filter category methods → before_action, after_action, around_action
- Output category method → response method, render, redirect methods
Note: params, session, cookie, flash methods work like a hash.
Action Controller methods in detail
-
Request accessor method of a controller points to request object which is instance of ActionDispatch::Request, associated with current request cycle. The request object has lots of properties/methods to get information about the request coming in from the client. Refer API documentation for all available Methods. For example,
request.url, request.method, request.request_method, request.path_parameters, request.query_parameters, request.request_parameters, request.body and so forth
-
All parameters from request (either query string from URL or POST data from form) are collected in params hash.
-
With each new request to server, the browser sends session cookie along with other cookies if it has any (session cookie and cookie was set in the previous request). We use session, cookie, flash objects provided by Rails to store small amount of data in browser cookie which is needed for further requests as HTTP is stateless protocol and does not persist the data between requests. session method is used to persist the data between the requests until the browser is closed. cookie method to persist until a specified expiration has been reached. flash method is used to persist the data until just the next request and cleared out in the next request(accessed in the view usually). All persisted data is accessed in our controller through session, cookie, flash hashes.
-
Controller communicates with the database and performs CRUD actions where necessary and stores the data from db in an instance variable for making it available to the view.
- Storage mechanism: Rails uses different storage mechanism to store data upto 4K to persist between requests. Refer API documentations
- ActionDispatch::Session::CookieStore - Stores everything on the client.
- ActionDispatch::Session::CacheStore - Stores the data in the Rails cache.
- ActionDispatch::Session::ActiveRecordStore - Stores the data in a database using Active Record. (require activerecord-session_store gem).
- ActionDispatch::Session::MemCacheStore - Stores the data in a memcached cluster (this is a legacy implementation; consider using CacheStore instead).
-
Filters are method to hook some behaviour on to the actions at very specific time (before_action, around_action, after_action). For example, A common “before” filter is one which authenticates the user for an action to be executed.
-
Response accessor method returns a response object which is instance_of ActionDispatch::Response, represents what is going to be sent back to the client. The response object is not usually used directly, but is built up during the execution of the action (i.e) when calling render and redirect methods without developer intervention. Response object holds the headers and document data to be sent back to user.
-
Rendering mechanism: Action Controller sends content to the user by calling rendering methods. Rendering happens automagically as the ActionController triggers ActionView which enables rendering the ERB templating. The controller uses instance variables to pass the objects to the view.
- Redirecting mechanism: After create, update and destroy action, we need to redirect to other action of the controller than just rendering a template. When redirect_to method is called, Rails uses HTTP status code 302, a temporary redirect, to tell browser to make next request to the URL mentioned in location header field of the HTTP response.
Play Time with ActionController methods
Below is the code I used to learn ActionController. I know its pretty big but still wanted to sample out here to get some idea.
class WelcomeController < ApplicationController
def new
if defined?(request)
puts "**************request method starts**************"
puts request.class # => ActionDispatch::Request
puts request.instance_of? ActionDispatch::Request # => true
puts request.original_url # => http://localhost:3000?id=1
puts request.path_parameters.class # => Hash
puts request.path_parameters # => {:controller=>"welcome", :action=>"new"}
puts request.query_parameters.class # => ActiveSupport::HashWithIndifferentAccess
puts request.query_parameters # => {"id"=>"1"}
puts request.request_parameters.class # => ActiveSupport::HashWithIndifferentAccess
puts request.request_parameters # => {}
puts request.request_method # => GET
puts request.body # => #<StringIO:0x0055ff26808a60>
puts request.headers # => #<ActionDispatch::Http::Headers:0x0055ff26760680>
puts request.method # => GET
puts request.flash.instance_of? ActionDispatch::Flash::FlashHash # => true
if request.flash.empty?
puts "testing flash for emptiness"
else
flash.each do |key,value|
puts key # => prints keys
puts value # => prints values
end
end
puts "**************request method ends**************"
end
if defined?(params)
puts "**************params method starts**************"
puts params.class # => ActionController::Parameters
puts params.instance_of? ActionController::Parameters # => true
puts params.respond_to?('[]') # => true
puts params[:controller] # => welcome
puts params.is_a?(Hash) # => true
params = ActionController::Parameters.new({
person: {
name: 'Francesco',
age: 22,
role: 'admin'
}
})
permitted = params.require(:person).permit(:name, :age, :role)
puts permitted
# => {"name"=>"Francesco", "age"=>22, "role"=>"admin"}
puts params[:person][:name] # => "Francesco"
params = ActionController::Parameters.new(key: 'Testing')
puts params[:key] # => "Testing"
puts params["key"] # => "Testing"
puts "**************params method ends*************"
end
if defined?(session)
puts "**************session method starts*************"
puts session.class # => ActionDispatch::Request::Session
puts session.instance_of? ActionDispatch::Request::Session # => true
puts session.is_a?(Hash) # => false
puts "**************session method end*************"
end
# Cookies, Sessions, Flash hashes, values was set in previous request
puts cookies[:first] # => "First cookie"
puts cookies[:second] # => "Second cookie"
puts session[:first] # => "First session"
puts session[:second] # => "Second session"
puts flash[:first] # => "First flash"
puts flash[:second] # => "Second flash"
if defined?(flash)
puts "***************flash method starts*******************"
puts flash.class # => ActionDispatch::Flash::FlashHash
puts flash.instance_of? ActionDispatch::Flash::FlashHash # => true
puts flash.is_a?(Hash) # => false
puts "**************flash method ends*************"
end
# Setting new values in cookies, session, flash
cookies[:first] = "First cookie again" # => Previously it was "First cookie"
cookies[:second] = "Second cookie again" # => Previously it was "Second cookie"
session[:first] = "First session again" # => Previously it was "First session"
session[:second] = "Second session again" # => Previously it was "Second session"
flash[:first] = "First flash again" # => Previously it was "First flash"
flash[:second] = "Second flash again" # => Previously it was "Second flash"
if defined?(response)
puts "***************response method starts*****************"
puts response.class # => ActionDispatch::Response
puts response.instance_of? ActionDispatch::Response # => true
puts response.is_a?(Hash) # => false
puts response.status # => 200
puts response.headers # => {"X-Frame-Options"=>"SAMEORIGIN", "X-XSS-Protection"=>"1;
# mode=block", "X-Content-Type-Options"=>"nosniff"}
puts response.headers["Content-Type"] = "application/pdf" # =>
puts response.content_type # => application/pdf
puts "**************response method ends*************"
end
end
end