Skip to content

Instantly share code, notes, and snippets.

@mjseaman
Last active December 20, 2015 15:08
Show Gist options
  • Save mjseaman/6151697 to your computer and use it in GitHub Desktop.
Save mjseaman/6151697 to your computer and use it in GitHub Desktop.

Views in Rails work similarly to views in Sinatra, although Rails is more opinionated about where your views live in your application directory structure. The main difference is that Rails uses render to render views and partials, abstracting away the particular "format" of the view, e.g., ERB or something else.

In Sinatra we'd write something like

get '/users/:id' do
  @user = User.find(params[:id])

  erb :show_users
end

In Rails we'd write something like

class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])

    # We can actually omit this call to render
    # Rails will render app/views/users/show.html.erb by default
    render :show
  end
end

Check out the Rails Guide on Layouts and Rendering: http://guides.rubyonrails.org/layouts_and_rendering.html

Objectives

Create a "views tutorial" for your fellow boots, something they'll want by their side for the remaining three weeks. Explain how rendering views, partials, and layouts work in terms they'll understand. It's ok to be unclear about all the details; this isn't set in stone and you can revise it over the coming weeks. Just be clear about being unclear.

You and your pair own this tutorial. It's yours to manage.

Write your tutorial using GitHub Flavored Markdown and upload it to http://gist.github.com.

Don't be afraid to ask for input from staff or fellow boots, either. Your fellow boots will be your "customers" and asking for their input is just good customer development.

Strategies

Here are a few strategies for writing a good tutorial (not meant to be exhaustive):

  1. Compare how Rails and Sinatra approaching rendering views and partials by giving contrasting code samples
  2. Create diagrams illustrating how Rails' renders views and partials
  3. Record a screencast illustrating your points
  4. Curate outside resources

(Hypothetical) FAQ

There are many questions which are worth asking and answering. Be aware of questions you have as they come up — they'll be the questions everyone else will have, too. Here are some possible ones:

  1. How does Rails decide what view to render? Does it depend on what controller I'm inside?
  2. How do I pass variables to my views? To my partials? Is there more than one way?
  3. How does Rails differentiate between views and partials?
  4. What are some common ways I can use the render method?
  5. How does one render non-HTML views, e.g., JSON or JavaScript?
  6. How does Rails handle layouts? Where do they live?
  7. What should I do if I want a view or partial available to multiple controllers, e.g., a common UI component? Where should that partial live in my application?

Think back to the Sinatra applications you've built. Try to recreate view-related scenarios you're familiar with. How are they different? How are they similar?

There could be many more questions and some of the above questions might not be relevant, so don't take them as an "assignment." They are just plausible questions that you or someone else might ask and which might deserve an answer. It's perfectly fine to say "I have no idea how to answer that question", "Please help, I don't understand", or even "As editor of this tutorial I don't think it's important to answer this question."

You have full editorial discretion. You should measure your success based on how helpful your tutorial is to your fellow boots.

Views and Partials

Rails does it different

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:

render "photos/index"

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

render json: @user

No content_type :json, no to_json. Rails just spoils you.

####Rendering XML

Same sh*t different markup:

render xml: @user

####Rendering JS

You'll never guess.

render js: "console.log('Rails just made my day');"

####Rendering nothing You can render nothing with

render nothing: true

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment