Date post: | 12-Sep-2014 |
Category: |
Education |
View: | 2,598 times |
Download: | 0 times |
Action ControllerOverview
Ror lab. season 2- the 15th round -
February 2th, 2013
Hyoseong Choi
A Controller
• RESTful applications
• like an orchestra conductor
• as a middle man btw models and views
REST
GET /books HTTP/1.1Host: rorlab.orgAccept: application/xml
#index
POST /books HTTP/1.1Host: rorlab.orgAccept: application/xml
#create
GET /books/1 HTTP/1.1Host: rorlab.orgAccept: application/xml
#show
PUT /books/1 HTTP/1.1Host: rorlab.orgAccept: application/xml
#update
GET /books/new HTTP/1.1Host: rorlab.orgAccept: application/xml
#new
DELETE /books/1 HTTP/1.1Host: rorlab.orgAccept: application/xml
#destroy
GET /books/1/edit HTTP/1.1Host: rorlab.orgAccept: application/xml
#edit
URI with HTTP methods
URI?
• Uniform Resource Identifier= URL + URN= Uniform Resource Locator + Uniform Resource Name
http://domain_name/posts/114
Routing
$ rake routes CONTROLLER=books
books GET /books(.:format) books#index
POST /books(.:format) books#create new_book GET /books/new(.:format) books#new
edit_book GET /books/:id/edit(.:format) books#edit
book GET /books/:id(.:format) books#show
PUT /books/:id(.:format) books#update
DELETE /books/:id(.:format) books#destroy
named routes
HTTPverbs
resourceURI
ControllersActions
MVC
MVC
View Model
Controller
Methods & Actions
• A “class” has methods
• A controller < ApplicationController
• public methods => “action” <= routing
$ rake routes CONTROLLER=clients
clients GET /clients(.:format) clients#index POST /clients(.:format) clients#create new_client GET /clients/new(.:format) clients#newedit_client GET /clients/:id/edit(.:format) clients#edit client GET /clients/:id(.:format) clients#show PUT /clients/:id(.:format) clients#update DELETE /clients/:id(.:format) clients#destroy
actions
Parameters• Two kinds of parameters
- Query string
- POST data
• by params hash for both of these
• “Routing” parameters
Hash & Array Params
GET /clients?ids[]=1&ids[]=2&ids[]=3
params[:ids] ?
[ “1”, “2”, “3”]
for Query String
Hash & Array Params
<form accept-charset="UTF-8" action="/clients" method="post"> <input type="text" name="client[name]" value="Acme" /> <input type="text" name="client[phone]" value="12345" /> <input type="text" name="client[address][postcode]" value="12345" /> <input type="text" name="client[address][city]" value="Carrot City" /></form>
for POST data
params[:client] ?
{ “name” => “Acme”, “phone” => “12345”, “address” => { “postcode” => “12345”, “city” => “Carrot City” }}
JSON/XML Params
{ "company": { "name": "acme", "address": "123 Carrot Street" } }
JSON parameter
automatically converted to params hash by Rails
{ :name => “acme”, “address” => “123 Carrot Street” }
params[:company]
default_url_options
➙ global default parameter
class PostsController < ApplicationController # The options parameter is the hash passed in to 'url_for' def default_url_options(options) {:locale => I18n.locale} endend
url_for<%= url_for(:action => 'index') %># => /blog/
<%= url_for(:action => 'find', :controller => 'books') %># => /books/find
<%= url_for(:action => 'login', :controller => 'members', :only_path => false, :protocol => 'https') %># => https://www.example.com/members/login/
<%= url_for(:action => 'play', :anchor => 'player') %># => /messages/play/#player
<%= url_for(:action => 'jump', :anchor => 'tax&ship') %># => /testing/jump/#tax&ship
<%= url_for(Workshop.new) %># relies on Workshop answering a persisted? call (and in this case returning false)# => /workshops
<%= url_for(@workshop) %># calls @workshop.to_param which by default returns the id# => /workshops/5
# to_param can be re-defined in a model to provide different URL names:# => /workshops/1-workshop-name
<%= url_for("http://www.example.com") %># => http://www.example.com
<%= url_for(:back) %># if request.env["HTTP_REFERER"] is set to "http://www.example.com"# => http://www.example.com
<%= url_for(:back) %># if request.env["HTTP_REFERER"] is not set or is blank# => javascript:history.back()
Session
• ActionDispatch::Session::CookieStore
• ActionDispatch::Session::CacheStore
• ActionDispatch::Session:: ActiveRecordStore
• ActionDispatch::Session::MemCacheStore
stateless ➤ stateful in controllers
Session Repositories :
class LoginsController < ApplicationController # "Create" a login, aka "log the user in" def create if user = User.authenticate(params[:username], params[:password]) # Save the user ID in the session so it can be used in # subsequent requests session[:current_user_id] = user.id redirect_to root_url end endend
SessionAccessing the Session
lazily loaded
class LoginsController < ApplicationController # "Delete" a login, aka "log the user out" def destroy # Remove the user id from the session @_current_user = session[:current_user_id] = nil redirect_to root_url endend
SessionRemoving a Session Key
cf. reset_session
Session
• special session : Hash
• cleared with each request
• only available in the next request
• useful for storing error messages etc
Flasha disposable session
redirect_to root_url, notice: "You have successfully logged out."redirect_to root_url, alert: "You're stuck here!"redirect_to root_url, flash: { referral_code: 1234 }
SessionFlash
<html> <!-- <head/> --> <body> <% flash.each do |name, msg| -%> <%= content_tag :div, msg, class: name %> <% end -%> <!-- more content --> </body></html>
SessionFlash
<% if flash[:just_signed_up] %> <p class="welcome">Welcome to our site!</p><% end %>
SessionFlash
class MainController < ApplicationController # Let's say this action corresponds to root_url, but you want # all requests here to be redirected to UsersController#index. # If an action sets the flash and redirects here, the values # would normally be lost when another redirect happens, but you # can use 'keep' to make it persist for another request. def index # Will persist all flash values. flash.keep # You can also use a key to keep only some kind of value. # flash.keep(:notice) redirect_to users_url endend
SessionFlash
flash.keep
class ClientsController < ApplicationController def create @client = Client.new(params[:client]) if @client.save # ... else flash.now[:error] = "Could not save client" render :action => "new" end endend
SessionFlash
flash.now
Cookies• persisted across request and even sessions
class CommentsController < ApplicationController def new # Auto-fill the commenter's name if it has been stored in a cookie @comment = Comment.new(:name => cookies[:commenter_name]) end def create @comment = Comment.new(params[:comment]) if @comment.save flash[:notice] = "Thanks for your comment!" if params[:remember_name] # Remember the commenter's name. cookies[:commenter_name] = @comment.name else # Delete cookie for the commenter's name cookie, if any. cookies.delete(:commenter_name) end redirect_to @comment.article else render :action => "new" end endend
• to delete a cookie value
cookies.delete(:key)
Cookies
xml & json data
class UsersController < ApplicationController def index @users = User.all respond_to do |format| format.html # index.html.erb format.xml { render :xml => @users} format.json { render :json => @users} end endend
Filters
Actionbefore after
around
methods inherited
not to halt actionto halt action
Filtersclass ApplicationController < ActionController::Base before_filter :require_login private def require_login unless logged_in? flash[:error] = "You must be logged in to access this section" redirect_to new_login_url # halts request cycle end end # The logged_in? method simply returns true if the user is logged # in and false otherwise. It does this by "booleanizing" the # current_user method we created previously using a double ! operator. # Note that this is not common in Ruby and is discouraged unless you # really mean to convert something into true or false. def logged_in? !!current_user endend
booleanizing operator
Filters
class LoginsController < ApplicationController skip_before_filter :require_login, :only => [:new, :create]end
• skip_before_filter
Filters
class ChangesController < ActionController::Base around_filter :wrap_in_transaction, :only => :show private def wrap_in_transaction ActiveRecord::Base.transaction do begin yield ensure raise ActiveRecord::Rollback end end endend
• around_filter
Filters
• Three Ways to use Filters
- a private method
- a block
- a class
Filters• Using a Block in more simple cases
class ApplicationController < ActionController::Base before_filter do |controller| redirect_to new_login_url unless controller.send(:logged_in?) endend
Filters• Using a Class in more complex cases
class ApplicationController < ActionController::Base before_filter LoginFilterend class LoginFilter def self.filter(controller) unless controller.send(:logged_in?) controller.flash[:error] = "You must be logged in" controller.redirect_to controller.new_login_url end endend
CSRF
• Site to site hacking
• First step for this:create/update/destroy with non-GET request
• RESTful default to protect CSRF
• Nevertheless, non-GET request still vulnerable
CSRF• To add a non-guessable token with form helpers
<%= form_for @user do |f| %> <%= f.text_field :username %> <%= f.text_field :password %><% end %>
<form accept-charset="UTF-8" action="/users/1" method="post"><input type="hidden" value="67250ab105eb5ad10851c00a5621854a23af5489" name="authenticity_token"/><!-- fields --></form>
CSRF• form_authenticity_token in custom Ajax calls
Request & Response Objects
• Two methods in every controller
- `request` method => request object
- `response` method => response object
Request Objects
Request Objects
• Three hash parameters for request objects
- path_parameters
- query_parameters : query string
- request_parameters : post data
Response Objects
• like in an `after` filter
Response Objects
response.headers["Content-Type"] = "application/pdf"
HTTP Authentications
HTTP Authentications
• Two types
- Basic authentication : using base 64 encoding
- Digest authentication: using MD5 encoding
HTTP Authentications• Basic authentication
class AdminController < ApplicationController http_basic_authenticate_with :name => "humbaba", :password => "5baa61e4"end
HTTP Authentications• Digest authentication
class AdminController < ApplicationController USERS = { "lifo" => "world" } before_filter :authenticate private def authenticate authenticate_or_request_with_http_digest do |username| USERS[username] end endend
Safari v 6.0.2undefined method `unpack' for nil:NilClass
Streaming & File Downloads
require "prawn"class ClientsController < ApplicationController # Generates a PDF document with information on the client and # returns it. The user will get the PDF as a file download. def download_pdf client = Client.find(params[:id]) send_data generate_pdf(client), :filename => "#{client.name}.pdf", :type => "application/pdf", :disposition => "attachement" end private def generate_pdf(client) Prawn::Document.new do text client.name, :align => :center text "Address: #{client.address}" text "Email: #{client.email}" end.render endend
send_data
•send_data•send_file
Streaming & File Downloads
class ClientsController < ApplicationController # Stream a file that has already been generated and stored on disk. def download_pdf client = Client.find(params[:id]) send_file("#{Rails.root}/files/clients/#{client.id}.pdf", :filename => "#{client.name}.pdf", :type => "application/pdf") endend
send_file
Streaming & File Downloads
send_file
in a
RES
Tful
app
licat
ion
require "prawn"class ClientsController < ApplicationController def show @client = Client.find(params[:id]) respond_to do |format| format.html format.pdf { render :pdf => generate_pdf(@client) } end end
private def generate_pdf(client) Prawn::Document.new do text client.name, :align => :center text "Address: #{client.address}" text "Email: #{client.email}" end.render endend
#153(revised)
Streaming & File Downloads
send_file
in a
RES
Tful
app
licat
ion
• in config/initializer/mime_types.rb
Mime::Type.register "application/pdf", :pdf
Parameter Filtering
config.filter_parameters << :password
• in config/application.rb
Rescueclass ApplicationController < ActionController::Base rescue_from User::NotAuthorized, :with => :user_not_authorized private def user_not_authorized flash[:error] = "You don't have access to this section." redirect_to :back endend class ClientsController < ApplicationController # Check that the user has the right authorization to access clients. before_filter :check_authorization # Note how the actions don't have to worry about all the auth stuff. def edit @client = Client.find(params[:id]) end private # If the user is not authorized, just throw the exception. def check_authorization raise User::NotAuthorized unless current_user.admin? endend
Force HTTPS protocol
class DinnerController force_sslend
class DinnerController force_ssl :only => :cheeseburger # or force_ssl :except => :cheeseburgerend
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. config.force_ssl = true
감사합니다.����������� ������������������