layouts and rendering in rails, season 2
DESCRIPTION
TRANSCRIPT
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
renderin
g
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 < ApplicationControllerend
BooksController
resources :booksconfig/routes.rb
<h1>Books are coming soon!</h1>app/views/books/index.html.erb
/books
Default Rendering
resources :booksconfig/routes.rb
$ rake routes CONTROLLER=books
books GET /books(.:format) books#index POST /books(.:format) books#create new_book GET /books/new(.:format) books#newedit_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 endend
• {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 endend
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" endend
Rendering an Action’s view
as a String
Using ‘Render’
def update @book = Book.find(params[:id]) if @book.update_attributes(params[:book]) redirect_to(@book) else render :edit endend
Rendering an Action’s view
as a Symbol
Using ‘Render’
def update @book = Book.find(params[:id]) if @book.update_attributes(params[:book]) redirect_to(@book) else render :action => "edit" endend
Rendering an Action’s view
to 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" endend
Rendering an Action’s Templatefrom Another Controller
from 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 endend
Rendering an Arbitrary File
Using ‘Render’
render :editrender :action => :editrender '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 => :builder
default 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/xml
Default 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 => 500render :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_required
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_satisfiable
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_supported
507 Insufficient Storage :insufficient_storage
510 Not Extended :not_extended
Status Code Status Message Symbol1xx Informational1xx Informational1xx Informational100 Continue :continue101 Switching Protocols :switching_protocols102 Processing :processing 2xx Success2xx Success2xx Success200 OK :ok201 Created :created202 Accepted :accepted203 Non-Authoritative
Information:non_authoritative_information204 No Content :no_content
205 Reset Content :reset_content206 Partial Content :partial_content207 Multi-Status :multi_status226 IM Used :im_used 3xx Redirection3xx Redirection3xx Redirection300 Multiple Choices :multiple_choices301 Moved Permanently :moved_permanently302 Found :found303 See Other :see_other304 Not Modified :not_modified305 Use Proxy :use_proxy307 Temporary Redirect :temporary_redirect4xx Client Error4xx Client Error4xx Client Error400 Bad Request :bad_request401 Unauthorized :unauthorized402 Payment Required :payment_required403 Forbidden :forbidden404 Not Found :not_found405 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 LayoutsSpecifying 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 LayoutsChoosing 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 LayoutsChoosing Layouts at Runtime
class ProductsController < ApplicationController layout Proc.new { |controller| controller.request.xhr? ? 'popup' : 'application' }end
using inline method!
Finding LayoutsConditional Layouts
class ProductsController < ApplicationController layout "product", :only => [:index, :rss]end
class ProductsController < ApplicationController layout "product", :except => [:index, :rss]end
Finding LayoutsLayout Inheritance (1) cascading downward
class ApplicationController < ActionController::Base layout "main"end
‣ application_controller.rb
class PostsController < ApplicationControllerend
‣ posts_controller.rb
Finding LayoutsLayout Inheritance (2) cascading downward
class SpecialPostsController < PostsController layout "special"end
‣ special_posts_controller.rb
Finding LayoutsLayout 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_urlredirect_to :back
Using Redirect_to
redirect_to photos_path, :status => 301
Different Redirect Status Codedefault st
atus : 302
Using Redirect_toRender vs Redirect_to
def index @books = Book.allend def show @book = Book.find_by_id(params[:id]) if @book.nil? render :action => "index" endend
def index @books = Book.allend def show @book = Book.find_by_id(params[:id]) if @book.nil? redirect_to :action => :index endend
a round trip to the browser
Using Redirect_toRender vs Redirect_to
one stop rendering
def index @books = Book.allend def show @book = Book.find_by_id(params[:id]) if @book.nil? @books = Book.all render "index", :alert => 'Your book was not found!' endend
Head-Only Responses
• render :nothing
• a more obvious alternative
‘head’ method
Head-Only Responses
head :bad_request
HTTP/1.1 400 Bad RequestConnection: closeDate: Sun, 24 Jan 2010 12:15:53 GMTTransfer-Encoding: chunkedContent-Type: text/html; charset=utf-8X-Runtime: 0.013483Set-Cookie: _blog_session=...snip...; path=/; HttpOnlyCache-Control: no-cache
Head-Only Responses
HTTP/1.1 201 CreatedConnection: closeDate: Sun, 24 Jan 2010 12:16:44 GMTTransfer-Encoding: chunkedLocation: /photos/1Content-Type: text/html; charset=utf-8X-Runtime: 0.083496Set-Cookie: _blog_session=...snip...; path=/; HttpOnlyCache-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 Helpers
in <head> section
in <body> section
Structuring LayoutsAsset Tag Helpers
auto_discovery_link_tag
<%= auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "RSS Feed"}) %>
RSS or ATOM feeds
Structuring LayoutsAsset 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/javascriptswith asset pipeline without 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 download
public/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.erb
rails 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>
layout
yield 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.
감사합니다.����������� ������������������