Services
Course Overview
Drawing from the real world experience of Engine Yard’s team of Ruby on Rails experts, this course provides a solid foundation in Ruby as well as Rails. Since the course is based on Rails 3.1, you will be able to take advantage of the latest features of the Rails 3 framework, such as the asset pipeline.
NOTE: THIS RAILS COURSE HAS BEEN UPDATED FOR RAILS 3.1
Prerequisites
Experience in .NET, ColdFusion, Java, Perl, PHP, or Python. Fluency in HTML and a strong understanding of Javascript.
Length: 4 days
Syllabus
| Day 1 | Day 2 |
|---|---|
Intro to Ruby
|
Discovering Rails (cont.)
|
| Day 3 | Day 4 |
Views
|
Test Driven Development
|
Excerpt
To give you a sense for our class, we are providing the following excerpt of Unit 5
Unit 5: Views
Unit Objectives
After completing this unit you should be able to:
- Use ERB to create views
- ERB versus HAML
- Use layouts and helpers to DRY up your views
- Understanding the Rails asset pipeline
Unit Topics
- ERB
- HAML
- Views
- Layouts
- Helpers
- Asset pipeline
Views
View Basics
Rails views are HTML templates which are rendered when a controller processes an action. A simple view is rendered all by itself, but it can also be wrapped in a layout and contain partial views. If a controller does not have a method defined for a particular action, it will simply look for the corresponding view template and render it. By default, views are located in a directory named after the corresponding model under app/views/
View filenames are constructed of the action name, followed by the response format, followed by the template language. A standard index view would be named index.html.erb.
Basic ERB Syntax
The default language for view templates in Rails is ERB, a templating system built in to Ruby. ERB uses a set of tags, similar to PHP or JSP, that can be interspersed with static HTML.
<% Ruby code (not displayed) %>
<%= Ruby expression (displayed) %>
<%# comment %>
ERB v. Haml
ERB files consist of standard HTML and Ruby code wrapped in ERB tags. For developers accustomed to and comfortable with HTML, ERB is familiar and easy to work with. There are many alternative templating languages. Haml is one of the most popular, offering a cleaner more succinct syntax that some find easier to parse visually and efficient to write. Haml’s main distinctions are its omission of the brackets used in HTML and ERB and its use of white space to handle nesting (with all of the same controversies that Python engenders for its use of white space).
The downside to Haml is that it is not very friendly for web or graphic designers who are not familiar with the different syntax. It adds a learning curve that might not be budgeted in your project, so some prefer to stick with ERB as it nicely fits into both the Ruby and HTML world.
In this class we will use ERB since it is the default rendering engine for Rails, but we will discuss Haml as well so you can make the choice for yourself.
Haml Basics
Haml uses two spaces to indicate nesting, as a result closing tags (for markup) and end tags (for code) are not required.
ERB
<div id="profile">
<div class="left column">
<div id="date"><%= print_date %></div>
<div id="address"><%= current_user.address %></div>
</div>
<div class="right column">
<div id="email"><%= current_user.email %></div>
<div id="bio"><%= current_user.bio %></div>
</div>
</div>
Haml
#profile
.left.column
#date= print_date
#address= current_user.address
.column
#email= current_user.email
#bio= current_user.bio
HTML entities are prefixed with %, and HTML attributes are expressed as Ruby hashes. So, for example:
HTML:
<table border="0" cellspacing="5">
HAML:
%table{:border => 0, :cellspacing => 5}
Classes and IDs are expressed using the same syntax as CSS. If you omit the HTML entitiy when specifying class and/or ID information, Haml creates a div by default.
<div id="navigation" class="toolbar">
#navigation.toolbar
Just as in ERB, = followed by a Ruby expression will be evaluated and output. Use - before Ruby code that has no output. Use -# for Haml comments.
-# A simple loop
%ul
- @hotels.each do |hotel|
%li= hotel.name
Haml Gotchas
The most common Haml problems have to do with whitespace. The indent for each level of nesting must be exactly two spaces. Tabs are not allowed. Set your text editor to use “soft tabs” of exactly two spaces, and when copying and pasting, be sure your nesting is correct. The other main issue with Haml is that a single statement cannot contain any line breaks. This is intentional on the part of Haml to discourage large chunks of code in the view. There are two exceptions to the line break restriction. You can use the pipe (|) to break multiline strings, and a line break is permitted immediately after commas between attributes of an entity.
From the Haml documentation:
%whoo
%hoo= h( |
"I think this might get " + |
"pretty long so I should " + |
"probably make it " + |
"multiline so it doesn't " + |
"look awful.") |
%p This is short.
and
%script{:type => "text/javascript",
:src => "javascripts/script_#{2 + 7}"}
Walkthrough 5-1: Creating a View
In this walkthrough, you will create an index view for hotels.
Steps
Generate hotels controller
$ rails g controller hotelsCreate
app/views/hotels/index.html.erb<h2>Hotels</h2> <ul id="hotels"> <li>Hilton</li> <li>Hyatt</li> <li>Joe's Inn</li> </ul>One more item is required which we have not yet had a chance to discuss, and that is a route for hotels. Insert the following line inside the main block in
config/routes.rb. We’ll explore routes in more detail in Unit 5.resources :hotelsView page in browser (http://localhost:3000/hotels)
Layouts
Layout Basics
Exemplifying the principle of “Don’t Repeat Yourself” which suffuses the design of Rails, layouts in Rails simplify the use of markup that is reused on multiple pages, such as HTML title and head blocks as well as application headers and footers.
Rails uses the default application layout, in our case app/views/layouts/application.html.erb since we are using Haml, unless it finds a layout specific to the model for the view being rendered. Layouts can also be disabled or overridden explicitly in the controller.
A simple layout might look as follows:
<html>
<head>
<title>Traveljournal</title>
</head>
<body>
<%= yield %>
</body>
</html>
The page template being rendered will be inserted in place of the yield method.
The standard Rails layout template includes a few additional items.
<!DOCTYPE html>
<html>
<head>
<title>Traveljournal</title>
<%= stylesheet_link_tag "application" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
</head>
<body>
<%= yield %>
</body>
</html>
The stylesheet_include_tag and javascript_include_tag statements include the Rails default javascript files and stylesheets. The settings for these defaults can be customized or individual stylesheet and javascript files can be specified. Finally, the csrf_meta_tag statement provides protection against cross-site scripting attacks.
Walkthrough 5-2: Editing a layout
In this walkthrough, you will edit an application-wide layout.
Steps
Open app/views/layouts/application.html.erb
Our application template generated this template when we created the traveljournal application. Let’s edit it to make the title appear nicer.
Travel Journal <%= stylesheetlinktag “application” %> <%= javascriptincludetag “application” %> <%= csrfmetatags %><%= yield %>
Open your browser and view the page.
Helpers
Helper Basics
View helpers are another way to avoid repetition in your application. Any display or formatting logic that occurs in your views can be extracted into helpers. Each model has a corresponding view helper located in /app/helpers/.
Quite simply any logic in your view can be made into a method in the view helper, which then can easily be reused, or simply used to make your views cleaner and more readable.
For example, suppose we want to display a link to flag a hotel as closed if it has closed down, or simply note that it has closed down if the link has already been clicked.
def flag_or_indicate_closed(hotel)
if hotel.closed?
link_to "flag as closed", close_hotel_path(hotel)
else
"This hotel has closed."
end
end
Take note that helpers you create in any file will be included and available to all of your views. If you wish to only include the helpers in application_helper.rb and your controller’s helper file, then call the clear_helpers method inside your ApplicationController.
class ApplicationController < ActionController::Base
clear_helpers
protect_from_forgery
end
Walkthrough 5-3: Creating a Helper
In this walkthrough, you will create a simple view helper that displays the current day of the week.
Steps
Edit
app/helpers/hotels_helper.rbmodule HotelsHelper def day_today Date.today.strftime("%A") end endAdd helper to view.
<h2>Hotels as of <%= day_today %></h2> <ul id="hotels"> <li>Hilton</li> <li>Hyatt</li> <li>Joe's Inn</li> </ul>View the results in your web browser.
The Rails Asset Pipeline
In the past, Rails has relied on serving up assets by using files inside the public_html directory. All of your images, Javascript files, stylesheets, and so on would go inside this directory, to be served statically by the application. If you ever wanted to minify, compress, or obfuscate the code in your Javascript or CSS files, you’d have to rely on external tools or third-party gems. In short, it was a lot of work.
Now Rails comes with an asset pipeline, which provides a framework to concatenate, minify, and compress your Javascript and CSS assets. It also allows you to write your assets in other languages such as CoffeeScript, SCSS or SASS, and even use template languages such as ERB.
Rails uses the Sprockets library to achieve this pipeline. Previously Sprockets was available by using the sprockets-rails gem, but now it is part of the core Rails setup and enabled by default. Developers can take advantage of these features without any additional configuration.
Using the pipeline, your assets will no longer be in public_html, but instead will reside in an assets directory in one of three locations:
app/assets- Assets owned by the application, such as custom images, Javascript, or stylesheets. The bulk of your assets will go here.lib/assets- Any assets for custom libraries or that just don’t fit into the scope of the application go here. This is useful for libraries shared across applications.vendor/assets- Any assets owned by plugins, etc.
Inside your assets directory you should have a directory for images, javascripts, and stylesheets.
Concatenation
The first, although not the most exciting, feature of the pipeline is concatenation of assets. This is very important to high-traffic production applications as it lowers the number of requests for assets and thus reduces server overhead. Now the default behavior of Rails is to concatenate all assets into a single file per type (so one master Javascript file, and one master CSS document).
To concatenate all the files together, Rails uses a manifest file to determine which files to include. Each of these manifests contain directives telling Sprockets which files to require in order build the single, monolithic document. These directives come in the form of comments in the code, using one of three keywords:
- require - includes a single document into the compiled asset output
- require_self - Used primarily in CSS, this includes any code in the current file in the compiled asset output
- require_tree - Similar to require, this loads all the files in the path relative to the current document.
For example, here is the default manifest for Javascripts, located in app/images/javascripts/application.js:
//= require jquery
//= require jquery_ujs
//= require_tree .
The files for jquery and jquery_ujs are in Sprockets’ search path, by being part of the jquery-rails gem included by the Rails application.
For CSS documents, you see the following in app/images/stylesheets/application.css:
/*
*= require_self
*= require_tree .
*/
This loads all CSS documents in app/images/stylesheets after the code in the application.css document itself. Note that the load order listed in the manifest documents is honored, so be careful when listing them.
When the documents are compiled to the single, concatenated form, Rails takes advantage of a concept called fingerprinting to add a hash to the generated filename. So long as the assets do not change, IPSs and browsers can cache the data and serve assets that way. Whenever a change is made to the assets, a new hash is generated, thus invalidating the old cache because an entirely new file will be requested.
So for example, application.css can be referenced with a hash like this: application-dd6b1cd38bfa093df600724704fd7a11.css.
Previously, rails appended a query string to the URL based on the file’s modified time, so the source looked similar to this:
/stylesheets/application.css?201107211050
Each time the code is deployed or served from different machines, the potential for not honoring the cache existed. Plus, a number of web servers do not honor the query string and won’t cache the content. So it wasn’t a fool-proof method of serving up the assets and caching them.
Minification and Compression
The pipeline is also useful for minifying and compressing your asset code. This helps to save bandwidth as your files are transferred to a multitude of users, and it can also help obfuscate your Javascript code to keep the casual observer from benefiting from your hard work.
In CSS documents, this process involves removing comments and unnecessary whitespace.
In Javascript documents this gets to be more elaborate. By default Rails utilizes the uglifier gem, which is a wrapper for the UglifierJS code compressor for NodeJS. By default it will remove whitespace, mangle variable names, “squeeze” code by converting things such as if statements to ternary operations whenever possible, remove dead code that will never be executed, and more. It tightens up Javascript to make it a smaller download but it also serves to obfuscate it from prying eyes.
Preprocessing Languages
The most exciting feature of the asset pipeline has to be the ability to use other languages (or language extensions) that will be compiled down to the native Javascript and CSS code. Now, inside your Rails application you can write CSS using either SCSS or SASS, you can write CoffeeScript for Javascript, or use template languages like ERB in either.
The filenames are similarly structured to how you name views, so you have the filename, followed by the output language (js or css), and then finally use the preprocessor language as your extension. For a CoffeeScript file you would have a file named like this:
app/images/javascripts/traveljournal.js.coffee
Or a CSS document like this:
app/images/stylesheets/traveljournal.css.scss
One neat feature of the pipeline is the ability to stack processing. You can have a document parsed by ERB and then passed to CoffeeScript by naming it like this:
app/images/javascripts/traveljournal.js.coffee.erb
The order is important. Whatever processor you list last will be processed first. So the above example will be processed by ERB first, then CoffeeScript, finally resulting in the Javascript output.
CoffeeScript
Learning the intricacies of CoffeeScript is beyond the scope of this course, but we can talk a little about what it has to offer.
CoffeeScript looks more like Ruby. You do not need semicolons to end expressions, variables do not need to be pre-declared using var, functions are specified with -> like stabby Procs, and so on.
Some examples of how it looks, taken from http://jashkenas.github.com/coffee-script :
# Assignment:
number = 42
opposite = true
# Conditions:
number = -42 if opposite
# Functions:
square = (x) -> x * x
# Arrays:
list = [1, 2, 3, 4, 5]
# Objects:
math =
root: Math.sqrt
square: square
cube: (x) -> x * square x
# Splats:
race = (winner, runners...) ->
print winner, runners
# Existence:
alert "I knew it!" if elvis?
# Array comprehensions:
cubes = (math.cube num for num in list)
Compiles down to this Javascript:
var cubes, list, math, num, number, opposite, race, square;
var __slice = Array.prototype.slice;
number = 42;
opposite = true;
if (opposite) {
number = -42;
}
square = function(x) {
return x * x;
};
list = [1, 2, 3, 4, 5];
math = {
root: Math.sqrt,
square: square,
cube: function(x) {
return x * square(x);
}
};
race = function() {
var runners, winner;
winner = arguments[0], runners = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
return print(winner, runners);
};
if (typeof elvis !== "undefined" && elvis !== null) {
alert("I knew it!");
}
cubes = (function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = list.length; _i < _len; _i++) {
num = list[_i];
_results.push(math.cube(num));
}
return _results;
})();
SASS
SASS (Syntactically Awesome Stylesheets) was originally part of the Haml gem. It provides extensions for CSS3, such as nested rules, variables, mixins, selector inheritance, and more to your CSS documents. There are two different syntaxes you can take advantage of, but developers are leaning more toward using SCSS (Sassy CSS) as it is a superset of CSS3, meaning that valid CSS3 documents are already SCSS documents. These documents are named with a .scss extension.
The other syntax is the original SASS syntax that uses indenting rules similar to Haml as opposed to semicolons and curly braces. It is still supported if your document is named with a .sass extension.
Here is an example SCSS document that utilizes variables, mixins, and nesting:
$heading: #eee;
$text: #000;
@mixin rounded($radius:10px) {
-webkit-border-radius: $radius;
-moz-border-radius: $radius;
border-radius: $radius;
}
header {
@include rounded(5px);
color: $text;
background-color: $heading;
a {
text-decoration: underline;
&:hover {
text-decoration: none;
}
}
}
This generates CSS output that looks like this:
header {
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
color: black;
background-color: #eeeeee; }
header a {
text-decoration: underline; }
header a:hover {
text-decoration: none; }
In this case, the generated output is smaller, but the SCSS is much more developer friendly as it contains the principles of DRY, reusable code, and better object-oriented concepts than base CSS can offer.
Unit Review
- Views
- ERB
- Haml
- Layouts
- Helpers
Lab 5: Creating views, layouts, & helpers
In this lab you will create two views for trips, an application layout, and a view helper for trips.
Objectives
After completing this lab you should be able to:
- Create views using ERB
- Create layouts
- Create partials
- Create view helpers
Steps
Create an index view
- Generate a controller for trips.
- Update routes
- Create an index view.
- View page in browser.
Create a Partial
Partial templates are reusable view templates that can be called by other templates in order to further DRY up your code, similar to how helper methods are used.
To create a partial template, create a normal template but start the filename with an underscore character.
To create a partial template the application layout will look for and render, we will need to create a directory named
app/views/application, which will be in the layout search path for rendering the partial, regardless of which controller we are using:$ mkdir app/views/applicationWrite a partial template to add a Tweet button to the page in
app/views/application/_tweet.html.erb. The code for building a button can be found onhttp://twitter.com/about/resources/tweetbuttonor you can use the following pre-defined snippet:<a href="http://twitter.com/share" class="twitter-share-button"· data-text="I am taking the Zero to Rails 3 Course right now #eyu"· data-count="horizontal"· data-url="http://www.engineyard.com/university"· data-via="eyuniversity">Tweet</a> <script type="text/javascript" src="http://platform.twitter.com/widgets.js"></script>Add the partial to
app/views/layouts/application.html.erbwith the following line, right above the line callingyield:<%= render "tweet" %>Reload the page and watch the Tweet button appear!
Create a helper
- Let’s create a helper method that will display a Gravatar for a user’s e-mail address. More info is available at: http://en.gravatar.com/
- Edit the file
app/helpers/hotels_helper.rb Add this method:
def gravatar(email) require "digest/md5" hash = Digest::MD5.hexdigest(email) "http://www.gravatar.com/avatar/#{hash}?s=32" endBack in the application layout, add this line right above the call to render the tweet partial. Use your email address if you have a Gravatar image setup already:
<%= image_tag gravatar("tgourley@engineyard.com") %>Verify that everything looks as expected in browser