Here are a few of the differences between Rails and Sinatra in rendering views:
###File Extensions
Rails views use file_name.html.erb
instead of file_name.erb
. This is because Rails can also render JSON, XML, and other formats using ERB. So... yeah.
###File organization
Rails wants you to organize your views into folders that describe what you're displaying, and to name files according to the routes to which they belong. For example, if you create a User model and controller, your folder structure should include this structure for views:
App
|- views
|-users
The users
folder should contain views for all applicable routes that you have created for users. If you follow the Rails RESTful routes conventions and have routes for all the major actions, you would have the following:
HTTP Verb |
Path |
Action |
Used for |
Your View should be: |
GET |
/users |
index |
display a list of all users |
app/views/users/index.html.erb |
GET |
/users/new |
new |
return an HTML form for creating a new user |
app/views/users/new.html.erb |
POST |
/users |
create |
create a new user |
none |
GET |
/users/:id |
show |
display a specific user |
app/views/users/show.html.erb |
GET |
/users/:id/edit |
edit |
return an HTML form for editing a user |
app/views/users/edit.html.erb |
PATCH/PUT |
/users/:id |
update |
update a specific user |
none |
DELETE |
/users/:id |
destroy |
delete a specific user |
none |
These conventions are important because Rails controllers will look for files named according to the convention and render them automatically. That means if you name your views correctly, it'll save you a lot of time writing render :this
and render :that
. Speaking of rendering,
###Rendering Views
Rails uses render
instead of erb
to render out pages. However, render
does a lot more than parse erb pages.
Routing in Rails is somewhat different than in Sinatra. Very basically, the routes.rb
file in your config
directory will point to a controller method (in a class within app/controllers
), which will perform some action and then return a value. That value can be user-specified or the Rails default.
####The default
Rails will automatically render the page associated with your controller method's action (new, show, edit, etc.) when the method completes. Awesome. That means that where Sinatra might say
get '/user' do
@user = User.new
erb :user
end
Rails can just say:
def new
@user = User.new
# Automatically renders app/views/users/new.html.erb
end
This achieves the same result. Rails will look in the view
folder corresponding to the controller you're working with and find the view that corresponds to the action.
####Rendering some other view
If you don't want your action's default view, you can render another one from the same controller like this:
# You can pass a string:
render "edit"
# OR you can pass a symbol:
render :edit
Rendering a view from a different controller requires that you include the controller's name, aka the name of the folder containing the view you want to render:
No symbols for this method. Keep in mind that you can also redirect, which may be the cleaner way to reroute to a new page when, for example, a user signs up or logs in. Rails has great resources for learning about redirection.
####Rendering text
To render text, just say render text: "Whatever"
. this defaults to no layout, so you'll just see the text in your browser if you make the request from there. This method is useful for requests that don't expect html back, like AJAX or web service calls. You can also render text with your layout template by setting the option layout: true
. More on layouts soon.
####Rendering JSON
No content_type :json
, no to_json
. Rails just spoils you.
####Rendering XML
Same sh*t different markup:
####Rendering JS
You'll never guess.
render js: "console.log('Rails just made my day');"
####Rendering nothing
You can render nothing with
This will give an affirmative server response (200) but won't return anything.
There are tons more resources and options for render. Not going to reinvent the wheel here.
http://guides.rubyonrails.org/layouts_and_rendering.html#using-render.
####A special shout-out: render ain't return
Don't do this:
def new
@user = User.find(params[:id])
if @user.special?
render :special_welcome
end
render :boring_welcome
end
Rails will spit you an error, because calling render doesn't return or break out of the method. You can do this if you want. Note the and return
. Use the word and
instead of &&
.
def new
@user = User.find(params[:id])
if @user.special?
render :special_welcome and return
end
render :boring_welcome
end
##Layouts
You can do all kinds of fancy stuff with layouts. Let's break it on down:
###The default
Your layout files live in app/views/layouts
. When rendering any page for you, Rails will look in that folder first for a layout with the same name as your class (in our example, app/views/layouts/users.html.erb
). If you don't have controller-specific layout, it'll default to app/views/layouts/application.html.erb
. The layouts folder should be relatively flat - if you have so many layouts you need subfolders, reconsider what you're doing with your life. You can also consider some of the tools below to limit propagation of layouts.
###Deviations
####Application Controller
You can explicitly specify a layout in your application controller (mother to all your controllers) like this:
class ApplicationController < ActionController::Base
layout "main"
#...
end
####Your Controller
Or, you can specify a layout for your specific controller:
class UsersController < ApplicationController
layout :users_layout # Note that strings and symbols are both okay.
end
Layouts are inheritable! if you set a layout for ActionController it'll be used for all of its subclasses (read: pretty much all of your defined models) unless you specify different layouts in those models.
Layout selection can be conditional! If you want a certain layout to apply to all but a couple of your actions, do this:
class PostsController < ApplicationController
layout "post", except: [:index, :rss]
...
end
Here, the post
layout would be used for all but the index
and rss
actions.
####Specific actions
##Layout Structure
####Yield
Use yield
in layouts just as in Sinatra to yield to the page at hand.
####Content_for
Something new! You can use content_for
to specify regions of your layout that you'll only need for certain methods.
Here: http://guides.rubyonrails.org/layouts_and_rendering.html#using-the-content-for-method
##Partials
Partials belong in the views/shared folder. Just... put them there.
Render partials like this:
<%= render "shared/my_partial" %>
####Naming partials
Name your partials with an underscore, e.g. _partial_name.html.erb
, but you don't need to call them with the underscore.
####Partials can have layouts
It's true. They look like this:
<%= render partial: "some_partial", layout: "some_layout" %>
Everything else you might want to know is here:
http://guides.rubyonrails.org/layouts_and_rendering.html
#UNDER THE HOOD
##Inheritance Chain
- ActionView Class Module instantiated in Abstract Controller's renderer.rb file
- Action View contains all classes for Templates
- Abstract Renderer contained in Action View / renderer
-
-
# This class defines the interface for a renderer. Each class that
# subclasses +AbstractRenderer+ is used by the base +Renderer+ class to
# render a specific type of object.
#
# The base +Renderer+ class uses its +render+ method to delegate to the
# renderers. These currently consist of
#
# PartialRenderer - Used for rendering partials
# TemplateRenderer - Used for rendering other types of templates
# StreamingTemplateRenderer - Used for streaming
#
# Whenever the +render+ method is called on the base +Renderer+ class, a new
# renderer object of the correct type is created, and the +render+ method on
# that new object is called in turn. This abstracts the setup and rendering
# into a separate classes for partials and templates.
== Rendering objects that respond to `to_partial_path`
#
# Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work
# and pick the proper path by checking `to_partial_path` method.
#
# # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
# # <%= render partial: "accounts/account", locals: { account: @account} %>
# <%= render partial: @account %>
#
# # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
# # that's why we can replace:
# # <%= render partial: "posts/post", collection: @posts %>
# <%= render partial: @posts %>
#
== Rendering the default case
def render_template(template, layout_name = nil, locals = nil) #:nodoc:
view, locals = @view, locals || {}
render_with_layout(layout_name, locals) do |layout|
instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
template.render(view, locals) { |*name| view._layout_for(*name) }
end
end
end