Layouts and Rendering in Rails, Season 2

hyoseongchoi 3,475 views 69 slides Dec 13, 2012
Slide 1
Slide 1 of 69
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32
Slide 33
33
Slide 34
34
Slide 35
35
Slide 36
36
Slide 37
37
Slide 38
38
Slide 39
39
Slide 40
40
Slide 41
41
Slide 42
42
Slide 43
43
Slide 44
44
Slide 45
45
Slide 46
46
Slide 47
47
Slide 48
48
Slide 49
49
Slide 50
50
Slide 51
51
Slide 52
52
Slide 53
53
Slide 54
54
Slide 55
55
Slide 56
56
Slide 57
57
Slide 58
58
Slide 59
59
Slide 60
60
Slide 61
61
Slide 62
62
Slide 63
63
Slide 64
64
Slide 65
65
Slide 66
66
Slide 67
67
Slide 68
68
Slide 69
69

About This Presentation

No description available for this slideshow.


Slide Content

Layouts and Rendering
Ror lab. season 2
- the 12th round -
December 15th, 2012
Hyoseong Choi

Contents
•Rendering methods
•Layouts with multiple content sections
•DRY using partial templates
•Nested layouts( or sub-templates)

Fit Together
Controller
Model View
Request
Response
heavy codes
DB
router
rendering
layout partial

Creating Response
•Three HTTP responses by Controller
➡call “render” : full response
➡call “redirect_to” : redirect status code
➡call “head” : HTTP headers

Default Rendering
•views with names corresponding to valid
routes.
•{action_name}.html.erb
Controllers automatically render

Default Rendering
class BooksController < ApplicationController
end
BooksController
resources :books
config/routes.rb
<h1>Books are coming soon!</h1>
app/views/books/index.html.erb
/books

Default Rendering
resources :books
config/routes.rb
$ 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

Default Rendering
class BooksController < ApplicationController
  def index
    @books = Book.all
  end
end
•{action_name}.html.erb
: app/views/books/index.html.erbCoC

Default Rendering
<h1>Listing Books</h1>
 
<table>
  <tr>
    <th>Title</th>
    <th>Summary</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>
 
<% @books.each do |book| %>
  <tr>
    <td><%= book.title %></td>
    <td><%= book.content %></td>
    <td><%= link_to 'Show', book %></td>
    <td><%= link_to 'Edit', edit_book_path(book) %></td>
    <td><%= link_to 'Remove', book,
:confirm => 'Are you sure?',
:method => :delete %></td>
  </tr>
<% end %>
</table>
 
<br />
 
<%= link_to 'New book', new_book_path %
•app/views/books/index.html.erb

Using ‘Render’
class BooksController < ApplicationController
  def index
    @books = Book.all
render “index”
render_to_string “index”
render :nothing => true
  end
end
Many ways to customize rendering
•Default view
: for a Rails template / a specific template / a file / inline code /
nothing
•text / JSON / XML

Using ‘Render’
def update
  @book = Book.find(params[:id])
  if @book.update_attributes(params[ :book])
    redirect_to( @book)
  else
    render "edit"
  end
end
Rendering an Action’s viewas a String

Using ‘Render’
def update
  @book = Book.find(params[:id])
  if @book.update_attributes(params[ :book])
    redirect_to( @book)
  else
    render :edit
  end
end
Rendering an Action’s viewas a Symbol

Using ‘Render’
def update
  @book = Book.find(params[:id])
  if @book.update_attributes(params[ :book])
    redirect_to( @book)
  else
    render :action => "edit"
  end
end
Rendering an Action’s viewto render “edit” action’s view

Using ‘Render’
def update
  @book = Book.find(params[:id])
  if @book.update_attributes(params[ :book])
    redirect_to( @book)
  else
    render "products/show"
    render :template => "products/show"
  end
end
Rendering an Action’s Template
from Another Controllerfrom another controller

Using ‘Render’
def update
  @book = Book.find(params[:id])
  if @book.update_attributes(params[ :book])
    redirect_to( @book)
  else
render "/u/apps/warehouse_app/current/app/
views/products/show"
    render :file => "/u/apps/warehouse_app/current/
app/views/products/show", :layout => true
  end
end
Rendering an Arbitrary File

Using ‘Render’
render :edit
render :action => :edit
render 'edit'
render 'edit.html.erb'
render :action => 'edit'
render :action => 'edit.html.erb'
render 'books/edit'
render 'books/edit.html.erb'
render :template => 'books/edit'
render :template => 'books/edit.html.erb'
render '/path/to/rails/app/views/books/edit'
render '/path/to/rails/app/views/books/edit.html.erb'
render :file => '/path/to/rails/app/views/books/edit'
render :file => '/path/to/rails/app/views/books/
edit.html.erb'
Wrapping it up

Using ‘Render’
render :inline =>
  "<% products.each do |p| %><p><%= p.name %></p><% end
%>"
with :inline
render :inline =>
  "xml.p {'Horrid coding practice!'}" , :type => :builderdefault type :ERB

Using ‘Render’
render :text => "OK", :layout => true
with :text
short response to AJAX
or web service requests

Using ‘Render’
render :json => @product
with :json
built-in support for JSON

Using ‘Render’
render :xml => @product
with :xml
built-in support for XML

Using ‘Render’
render :js => "alert('Hello Rails');"
with :js
built-in support for XML

Options
for Render
•:content_type
•:layout
•:status
•:location

Options
for Render
:content_type option
render :file => filename, :content_type => 'application/rss'
:html - text/html
:json - application/json
:xml - application/xmlDefault MIME type

Options
for Render
:layout option
render :layout => 'special_layout'to tell Rails to use a specific file
as the layout for the current action
render :layout => false

Options
for Render
:location option
render :xml => photo, :location => photo_url(photo)to set the HTTP Location header

Options
for Render
:status option
render :status => 500
render :status => :forbidden
http://www.codyfauser.com/2008/7/4/rails-http-status-code-to-symbol-mapping

Status Code Status Message Symbol
406 Not Acceptable :not_acceptable
407
Proxy Authentication
Required
:proxy_authentication_req
uired
408 Request Timeout :request_timeout
409 Conflict :conflict
410 Gone :gone
411 Length Required :length_required
412 Precondition Failed :precondition_failed
413 Request Entity Too Large:request_entity_too_large
414 Request-URI Too Long :request_uri_too_long
415 Unsupported Media Type:unsupported_media_type
416
Requested Range Not
Satisfiable
:requested_range_not_sati
sfiable
417 Expectation Failed :expectation_failed
422 Unprocessable Entity :unprocessable_entity
423 Locked :locked
424 Failed Dependency :failed_dependency
426 Upgrade Required :upgrade_required
   
5xx Server Error5xx Server Error5xx Server Error
500 Internal Server Error :internal_server_error
501 Not Implemented :not_implemented
502 Bad Gateway :bad_gateway
503 Service Unavailable :service_unavailable
504 Gateway Timeout :gateway_timeout
505
HTTP Version Not
Supported
:http_version_not_support
ed
507 Insu"cient Storage :insu"cient_storage
510 Not Extended :not_extended
Status Code Status Message Symbol
1xx Informational1xx Informational1xx Informational
100 Continue :continue
101 Switching Protocols :switching_protocols
102 Processing :processing
   
2xx Success2xx Success2xx Success
200 OK :ok
201 Created :created
202 Accepted :accepted
203 Non-Authoritative
Information
:non_authoritative_inform
ation
204 No Content :no_content
205 Reset Content :reset_content
206 Partial Content :partial_content
207 Multi-Status :multi_status
226 IM Used :im_used
   
3xx Redirection3xx Redirection3xx Redirection
300 Multiple Choices :multiple_choices
301 Moved Permanently :moved_permanently
302 Found :found
303 See Other :see_other
304 Not Modified :not_modified
305 Use Proxy :use_proxy
307 Temporary Redirect :temporary_redirect
4xx Client Error4xx Client Error4xx Client Error
400 Bad Request :bad_request
401 Unauthorized :unauthorized
402 Payment Required :payment_required
403 Forbidden :forbidden
404 Not Found :not_found
405 Method Not Allowed :method_not_allowed

Finding Layouts

Rails first looks for a file in app/views/layouts
with the same base name as the controller.Current
layout Controller layout Application layout
inheritance

Finding Layouts
Specifying Layouts for Controllers
class ProductsController < ApplicationController
  layout "inventory"
  #...
end
class ApplicationController < ActionController::Base
  layout "main"
  #...
end
app/views/layouts/inventory.html.erb
app/views/layouts/main.html.erb

Finding Layouts
Choosing Layouts at Runtime
class ProductsController < ApplicationController
  layout :products_layout
 
  def show
    @product = Product.find(params[ :id])
  end
 
  private
    def products_layout
      @current_user.special? ? "special" : "products"
    end
 
end

Finding Layouts
Choosing Layouts at Runtime
class ProductsController < ApplicationController
  layout Proc.new { |controller|
controller.request.xhr? ? 'popup' : 'application' }
end using inline method!

Finding Layouts
Conditional Layouts
class ProductsController < ApplicationController
  layout "product", :only => [:index, :rss]
end
class ProductsController < ApplicationController
  layout "product", :except => [:index, :rss]
end

Finding Layouts
Layout Inheritance (1)cascading downward
class ApplicationController < ActionController::Base
  layout "main"
end
‣application_controller.rb
class PostsController < ApplicationController
end
‣posts_controller.rb

Finding Layouts
Layout Inheritance (2)cascading downward
class SpecialPostsController < PostsController
  layout "special"
end
‣special_posts_controller.rb

Finding Layouts
Layout Inheritance (3)cascading downward
class OldPostsController < SpecialPostsController
  layout false
 
  def show
    @post = Post.find(params[:id])
  end
 
  def index
    @old_posts = Post.older
    render :layout => "old"
  end
  # ...
end
‣old_posts_controller.rb

Using Redirect_to
•redirect_to
- tell the browser to send a new request
for a different URL
•cf. render
- a view template
redirect_to photos_url
redirect_to :back

Using Redirect_to
redirect_to photos_path, :status => 301
Different Redirect Status Codedefault status : 302

Using Redirect_to
Render vs Redirect_to
def index
  @books = Book.all
end
 
def show
  @book = Book.find_by_id(params[ :id])
  if @book.nil?
    render :action => "index"
  end
end
def index
  @books = Book.all
end
 
def show
  @book = Book.find_by_id(params[ :id])
  if @book.nil?
    redirect_to :action => :index
  end
endBSPVOEUSJQUPUIFCSPXTFS

Using Redirect_to
Render vs Redirect_toPOFTUPQSFOEFSJOH
def index
  @books = Book.all
end
 
def show
  @book = Book.find_by_id(params[ :id])
  if @book.nil?
    @books = Book.all
    render "index", :alert => 'Your book was not found!'
  end
end

Head-Only
Responses
•render :nothing
•a more obvious alternative
‘head’ method

Head-Only
Responses
head :bad_request
HTTP/1.1 400 Bad Request
Connection: close
Date: Sun, 24 Jan 2010 12:15:53 GMT
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
X-Runtime: 0.013483
Set-Cookie: _blog_session=...snip...; path=/; HttpOnly
Cache-Control: no-cache

Head-Only
Responses
HTTP/1.1 201 Created
Connection: close
Date: Sun, 24 Jan 2010 12:16:44 GMT
Transfer-Encoding: chunked
Location: /photos/1
Content-Type: text/html; charset=utf-8
X-Runtime: 0.083496
Set-Cookie: _blog_session=...snip...; path=/; HttpOnly
Cache-Control: no-cache
head :created, :location => photo_path(@photo)

Structuring
Layouts
•Asset tags ( for asset links )
•yield and content_for ( for layouts )
•Partials ( for refactoring )
Three tools for knitting fragmented outputs

Structuring
Layouts
•auto_discovery_link_tag
•javascript_include_tag
•stylesheet_link_tag
•image_tag
•video_tag
•audio_tag
Asset Tag Helpersin <head> section in <body> section

Structuring
Layouts
Asset Tag Helpers
auto_discovery_link_tag
<%= auto_discovery_link_tag( :rss, {:action => "feed"},
  {:title => "RSS Feed"}) %>RSS or ATOM feeds

Structuring
Layouts
Asset Tag Helpers
javascript_include_tag
<%= javascript_include_tag "main" %>
<script src='/assets/main.js' type="text/javascript"></script>•app/assets/javascripts
•lib/assets/javascripts
•vendor/assets/javascripts

javascript_include_tag
<%= javascript_include_tag "main", "columns" %>
<%= javascript_include_tag "main", "/photos/columns" %>
<%= javascript_include_tag "http://example.com/main.js" %>
<%= javascript_include_tag :defaults %>
<%= javascript_include_tag :all %>without asset pipeline without asset pipeline

Disable
Asset Pipeline
•config/application.rb
# Enable the asset pipeline
config.assets.enabled = true

javascript_include_tag
<%= javascript_include_tag :defaults %>
<script src="/javascripts/jquery.js" type="text/javascript"></script>
<script src="/javascripts/jquery_ujs.js" type="text/javascript"></script>without asset pipeline public/javascripts
•app/assets
•lib/assets
•vendor/assets
•public/javascripts
with asset pipelinewithout asset pipeline

config.action_view.javascript_expansions[ :defaults]
= %w(foo.js bar.js)
javascript_include_tag :defaults
config.action_view.javascript_expansions[ :projects]
= %w(projects.js tickets.js)
:defaults expansion in config/application.rb
new defaults expansion in config/application.rb
public/javascripts/application.js with :default

<%= javascript_include_tag :all, :recursive => true %>
javascript_include_tag :all
<%= javascript_include_tag "main", "columns", :cache => true %>
public/javascripts/*.*
combining multiple files into a single downloadpublic/javascripts/all.js
<%= javascript_include_tag "main", "columns",
  :cache => 'cache/main/display' %location of cache

<%= stylesheet_link_tag "main" %>
stylesheet_link_tag
assets/stylesheets/ with Asset Pipeline
<%= stylesheet_link_tag "main", "columns" %>
<%= stylesheet_link_tag "main", "/photos/columns" %>
<%= stylesheet_link_tag "http://example.com/main.css" %>

<%= stylesheet_link_tag :all %>
stylesheet_link_tag :all
public/stylesheets/ without Asset Pipeline
<%= stylesheet_link_tag :all, :recursive => true %>
<%= stylesheet_link_tag "main", "columns", :cache => true %>
<%= stylesheet_link_tag "main", "columns",
  :cache => 'cache/main/display' %>

image/video/audio
•public/images
•public/videos
•public/audios
•app/assets/
•lib/assets/
•vendor/assets/images/
videos/
audios/ images/
videos/
audios/ images/
videos/
audios/
Asset pipeline +Asset pipeline -

Yield
•a section where content from the view
should be inserted.
<html>
  <head>
  </head>
  <body>
  <%= yield %>
  </body>
</html>
<html>
  <head>
  <%= yield :head %>
  </head>
  <body>
  <%= yield %>
  </body>
</html>single yield multiple yields named yield content_for

content_for
<% content_for :head do %>
  <title>A simple page</title>
<% end %>
 
<p>Hello, Rails!</p><%= yield :head %>
<html>
  <head>
  <title>A simple page</title>
  </head>
  <body>
  <p>Hello, Rails!</p>
  </body>
</html>
<html>
  <head>
  <%= yield :head %>
  </head>
  <body>
  <%= yield %>
  </body>
</html>

Partial
•breaking the render process into more
chunks
•moving the code chunk to its own file
•_partial.html.erb vs partial.html.erb
•to simplify views
•partial layout

Partial
<%= render "menu" %>
<%= render "shared/menu" %>
<%= render "shared/ad_banner" %>
 
<h1>Products</h1>
 
<p>Here are a few of our fine products:</ p>
...
 
<%= render "shared/footer" %>
<%= render :partial => "link_area", :layout => "graybar"

Local Variables
<h1>New zone</h1>
<%= error_messages_for :zone %>
<%= render :partial => "form", :locals => { :zone =>
@zone } %>
<%= form_for(zone) do |f| %>
  <p>
    < b>Zone name</b><br />
    <%= f.text_field :name %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>

A Partial variable
•a local variable with the same name as the
partial
•pass an object in to this local variable
<%= render :partial => "customer", :object => @new_customer %>
<%= render @customer %>

Rendering Collections
Rails determines the name of the partial to use by looking at
the model name in the collection. In fact, you can even create a
heterogeneous collection and render it this way
<h1>Products</h1>
<%= render :partial => "product",
:collection => @products %>
index.html.erb
<p>Product Name: <%= product.name %></p>
_product.html.erb

Rendering Collections
Rails determines the name of the partial to use by looking at
the model name in the collection. In fact, you can even create a
heterogeneous collection and render it this way
<h1>Products</h1>
<%= render(@products) || ‘there are no products
available.’ %>
<p>Product Name: <%= product.name %></p>
index.html.erb
_product.html.erbrails
3.0

Heterogenous
Collections
<h1>Contacts</h1>
<%= render [customer1, employee1, customer2, employee2] %>
index.html.erb
<p>Customer: <%= customer.name %></p>
customers/_customer.html.erb
<p>Employee: <%= employee.name %></p>
employees/_employee.html.erb

Local Variables
render :partial => "product",
:collection => @products,
:as => :item %>
<%= render :partial => 'products',
:collection => @products,
           :as => :item,
:locals => {:title => "Products Page"} %>

Spacer Templates
<%= render :partial => @products,
:spacer_template => "product_ruler" %>

Layouts
•Application Layout
•Controller Layout
•Action Layout
•Partial Layout

Layouts
<html>
  <head>
  <%= yield :head %>
  </head>
  <body>
  <%= yield %>
  </body>
</html>
layoutyield placeholder yield(:head) content_for :head yield(:bar) content_for :bar <% content_for :head do %>
  <title>A simple page</
title>
<% end %>
 
<p>Hello, Rails!</p> view template

Nested Layouts
<% content_for :stylesheets do %>
  #top_menu {display: none}
  #right_menu {float: right; background-color:
yellow; color: black}
<% end %>
<% content_for :content do %>
  <div id="right_menu">Right menu items here</
div>
  <%= content_for?(:news_content)
? yield(:news_content)
: yield %>
<% end %>
<%= render :template => 'layouts/application' %>
ApplicationController LayoutNewsController Layout<html>
<head>
  <title><%= @page_title or 'Page Title' %></
title>
  <%= stylesheet_link_tag 'layout' %>
  <style type="text/css">
<%= yield :stylesheets %>
</style>
</head>
<body>
  <div id="top_menu">Top menu items here</ div>
  <div id="menu">Menu items here</div>
  <div id="content">
<%= content_for?(:content)
? yield(:content)
: yield %>
</div>
</body>
</html>

ROR Lab.
E????.