Chapter 2. Rails in a Nutshell
Ruby on Rails is a framework designed for quickly and cleanly writing web applications. Extracted from 37signals' project management tool Basecamp, creator David Heinemeier Hansson wrote Rails with solutions to common real-world problems already in hand. These solutions and decisions are what makes Rails a pleasure to use – the boring, menial chores are taken care of for you, leaving you to concentrate on your own problem instead of the infrastructure around it.
To be specific about the boring parts: we’re talking about deciding on a directory structure, caching layers, data persistence, templating systems, testing frameworks, mailing, routing, and all the other components that you need in almost every web application. Rails comes with the full stack of components you need to get your application online, and they’ve all been hand-written to work together. Gone are the days of hoping the templating language you found works with the complex object relational mapping (ORM) layer. Basic security concerns around SQL injection, XSS and CSRF attacks are all taken care of by default. Support for AJAX calls comes baked-in. Need to expose an API? Rails makes it doable in a few lines. It all just works.
You can also forget about writing reams of tedious XML configuration for components like databases. Rails is all about convention over configuration – keep configuration to a minimum by
doing the obvious. If there's a class called Animal that
models animals, and there’s a database table called animals,
why should you be writing configuration files that specify that they link up? Rails does that
for you.
Rails gets out of your way. It doesn’t surprise you. It doesn’t make you repeat yourself. It makes web development fun.
Architecture
Rails applications are developed using the Model View Controller (MVC) pattern. The easiest way to show how Rails uses MVC is by looking at a simplified example of the Rails request lifecycle as seen in Figure 2.1, “MVC in action: the Rails request lifecycle”.
Here’s an example user scenario: let’s imagine that your grandmother wants to purchase a book on tribal tattoos on an online store. Let’s break down the steps of visiting the front page of the store in the context of the MVC pattern:
Your grandmother fires up her web browser and visits the online store. The web browser hits the web application with a request for the front page. This request arrives at a Controller, whose job it is to determine what needs to be done in order to fulfill the request.
The Controller needs to fetch some data from the database about tribal tattoo books, so it asks a book Model, whose job it is to communicate with the database, to get representations of tribal tattoo books.
The book Model fetches data by making SQL calls to the database to fetch records and returns instances of book representations to the Controller. If need be, the book Model is also responsible for data validation.
The Controller needs to package up the response to your grandmother’s web browser in the HTML format that it asked for, so it passes the book Model instances and anything else needed to a View to render into HTML.
The View combines the book Model instances with a template to generate an HTML page and returns it to the Controller.
The Controller gives the rendered HTML page back to the Client as a response.
This approach to organizing application components makes for readable and maintainable projects as it’s predictable where each piece of code should go and can be found: Models deal with interactions with the database and business logic (typically the bulk of your application), View templates are where HTML and the like goes, while Controllers are the thin layer coordinating the two. Without this separation of concerns, you end up with SQL in your HTML, caching code all over, and a general mess on your hands, making writing new features an exercise in masochism.
Another pattern that permeates Rails’ design is Representational State Transfer (REST), an architectural idea of how a well-designed web application should behave. It defines a web application as a collection of resources accessed through uniform, predictable URIs and HTTP verbs like GET, PUT, POST, and DELETE, and returning a representation of a resource that transfers the client (a user’s web browser) into a certain state. This approach frees you from having to make architectural decisions about the layout and composition of your application and its API. We’ll see later about how this idea is what lets Active Resource work so well.
Note
A great tutorial on REST is http://www.xfront.com/REST-Web-Services.html
Model
A model contains business logic – whether or not to calculate something, send a happy birthday notification, or launch a space ship. To make those business decisions and take those actions, it often depends on accessing a database, so a model is also responsible for creating, retrieving, updating, and deleting the data it represents. It’s also tasked with ensuring data integrity by preventing invalid entries from being entered into the database.
Rails makes describing all that logic easy with a component called Active Record that lets you work in Ruby instead of the typical mess of database configuration/connection and SQL voodoo required for creation, retrieval, updating, and deletion (CRUD). Here are some examples of what your code could look like.
# create a record book_author = Person.create(:name => "Edward", :nationality => "Canadian") # retrieve records canadian_authors = Person.where(:nationality => "Canadian") # update a record book_author.name = "Edward Ocampo-Gooding" book_author.save # delete a record book_author.destroy
Because of how Rails is set up, you can do this sort of thing anywhere in your application – in any model, controller, or view – your models are always accessible like this.
Instead of making you manually specify which classes map to which
database tables, Active Record just looks at the names used in the schema – it makes sense
that a table called animals should map to a class
Animal, so Active Record does that for you. It also
handles pluralizations like people mapping to a class
Person.
Setting up associations detailing what belongs to what and has many or one of something else is also dead-simple:
class Person < ActiveRecord::Base belongs_to :family has_one :free_time_activity has_many :interests end
After setting up these associations, writing code like this becomes natural:
edward = Person.where(:name => 'Edward Ocampo-Gooding') edward.interests.create(:name => 'Open Data')
Active Record also takes the intuitive step of providing methods on model objects that
match the table’s columns. If the people table has a
column called gray_hairs, then you automatically get
accessors for that column by the same name:
john = Person.where(:nationality => 'American').first puts john.gray_hairs # The Person#gray_hairs method appears automatically john.gray_hairs = 9 # The Person#gray_hairs= method also appears automatically john.save
Active Record is also flexible enough to bend when you need it – if automatic table-class mapping doesn’t work for a situation, you can specify the details manually. If a complex search written in Ruby isn’t optimized enough, dropping to SQL is painless.
There’s more beyond that – Active Record comes with common validations to ensure data integrity and let you skip having to write the same code to check if a field is numeric, of a certain size or format, just plain required, or other common situations. In other words, you can do this:
class Animal < ActiveRecord::Base validates :known_population, :numericality => true validates :biological_name, :uniqueness => true, :presence => true end
and the model won’t save unless those validations pass, with the added bonus of automatically generating error messages. More validations are available out of the box, and writing your own is easy.
There’s lots more to Active Record that makes your work as a developer quick and to the point: it has rich support for callback hooks throughout the CRUD object lifecycle, and observers to simultaneously listen for callback events on multiple models. Transactions are built-in and straightforward. Database schema migrations to help keep everyone on the team on the same schema are concise and easy to read.
Active Record supports a variety of database backends, including SQLite, MySQL, Postgres, Oracle, Microsoft SQL Server, Sybase, and DB2.
Check out Chapter 4, Active Record for more on how Active Record can make your life easier.
View
The View layer is what the user of your application sees and touches. It usually entails rendering model data and web forms, but could also be rendering XML for an RSS reader or JSON for some custom API.
Rails delivers this presentation level of the stack with a component called Action View. It comes bundled with a templating system called embedded Ruby (ERB) that works great for HTML output – if you’ve used PHP, ASP, or JSP, you’re already familiar with the syntax:
<h1>All People</h1>
<ul>
<% Person.all.each do |person| %>
<li><%= person.name %> is having a birthday
in <%= distance_of_time_in_words_to_now(person.birthday) %></li>
<% end %>
</ul>Any Ruby in <% … %> tags is evaluated but not
displayed in the output. Ruby inside <%= … %> is
evaluated and is also rendered in the output. The above would render HTML
looking something like this:
<h1>All People</h1> <ul> <li>Edward is having a birthday in 11 days</li> <li>James is having a birthday in 11 days</li> <li>John is having a birthday in 156 days</li> <li>Cody is having a birthday in 90 days</li> </ul>
The distance_of_time_in_words_to_now method comes
with Action View – it’s one of the many convenience methods you’ll find making interfaces
easy and quick to write. Other similar methods exist for time, dates, links, forms, text,
and produce clean HTML5. You’ll also find that the output has been made
easy to hook into via unobtrusive JavaScript techniques often used by Rails developers to
make their applications feel snappier and look snazzier.
Action View also comes bundled with Builder, a tool for easily producing hierarchial output like XML. We’ll see it in action later on in this chapter, producing an Atom/RSS output.
See ??? for more on Action View.
Controller
Action Controller orchestrates the Rails request lifecycle: it responds to HTTP requests, gets data and decisions from models, then renders a view. To make running the show easier, Action Controller provides automatic logging, benchmarking, caching, session and cookie management, as well as other interesting features like filters, which run code before or after actions, often acting as ways to easily introduce things like authentication.
Action Controller also intelligently knits together endpoints, or actions with the corresponding view of the same name. Here’s an excerpt from a typical controller to show you what that means:
class PeopleController < ApplicationController
before_filter :ensure_person_is_authenticated
def index
@people = Person.all
respond_to do |format|
format.html # index.html.erb
format.json { render :json => @people }
end
end
# ...
endWhen handling a request for a URI like http://some-app.com/people/index.html, the PeopleController#index method (also called an action because it handles a request) will look for a view template by the same
name and request format: index.html.erb
What’s even more cool is that Action Controller can also respond
to different request formats with the same action: a URI like http://some-app.com/people/index.json will be responded to by the above example
code with a JSON representation of the same thing the HTML view rendered. That means writing an API for your
application doesn’t have to involve a whole bunch of other special controllers and logic.
Just reuse your existing code by adding “.json” to the end of your
URIs, render the same object you’d otherwise pass to your HTML view in JSON, and you’re done.
For more on Action Controller, see Chapter 5, Action Controller.
Note
Action View and Action Controller are together known as Action Pack.
Mailer
Action Mailer makes sending templated email a breeze. Because Rails treats sending email very similarly to rendering a regular HTML/ERB view, you’ll find yourself already knowing how to use it by the time you need it.
Action Mailer also lets you to easily process incoming email, turning typically complex jobs like turning email sent by users into comments, inventory updates, or new profile pictures into a straightforward and simple procedure.
Read more about Action Mailer in Chapter 11, Action Mailer.
Web Services
Rails includes a component called Active Resource which lets you integrate your application with other web services like those offered by Shopify, Twitter, Pivotal Tracker and Lighthouse via a RESTful API. Instead of having to figure out various HTTP calls and how to build and parse JSON and XMLrequests and responses, Active Resource takes care of all that for you, letting you work with other web services like they were just another model in your application.
Find out how to work with Active Resource in Chapter 12, Active Resource.
Plugins
It’s easy to modify Rails to get it to do what you want if it doesn’t already. For that reason, there are a ton of well-maintained plugins and Ruby gems available written by the community making everyday tasks like file uploads and image resizing as easy as a one-line install.
If installing one of the many out there already doesn’t work for you, we’ll show you how to write one in Chapter 15, Plugins.
Getting Started
To give you a feel of how quickly and elegantly you can develop web applications with Rails, let’s build a video jukebox.
This application is going to
Accept videos from around the web that our friends think are hilarious, touching, or otherwise awesome
Shield against vandalism with data sanitization
Validate video submissions so none have missing fields
Offer syndication through an Atom feed to let RSS readers subscribe to the list of submitted videos
Accept comments
Feel snappy with some AJAX
To keep it simple, we’re going to assume the video is already up on the web somewhere, so its HTML embed code will be used instead of actually storing the video.
If you don’t have Rails installed, see the Appendix for instructions.
Generate a Rails application
Begin by creating a skeleton Rails application:
$ rails new video_jukebox
Take a look at the application’s directory tree and the typical files and configuration needed in every Rails application:
app/All application-specific code lives in this directory.
assets/This is where your assets go: images, stylesheets, and javascripts are the usual suspects.
controllers/This is where your controllers go. We’ll put
videos_controller.rbandcomments_controller.rbhere later.helpers/Holds view helpers: collections of methods used to simplify your views. Consider encapsulating any complex logic used in a view into a helper method. Helpers we’ll create later are
videos_helper.rbandcomments_helper.rbmailers/Classes used for email generation go here.
models/Where your business logic goes.
video.rbandcomment.rbwill appear here.views/View templates that render controller actions like
videos/index.html.erbgo here.layouts/Layouts render view templates inside of them like a loving embrace. This tutorial will use just the one
application.html.erb
config/Configuration for your application, database, routing, and other components.
db/The database schema of your application,
schema.rb, is here.migrate/Migrations (incremental database schema changes) are placed here. We’ll write a few during this tutorial.
doc/Documentation for your application generated by the rake doc:app command is placed here.
lib/Code that doesn’t belong in a model, controller, or helper belongs in a library module or class. This directory is in the load path so anything placed here can be included or required in the rest of the application.
tasks/Automate monotonous or complex tasks that should be automated by creating rake task recipes here. If you’re used to a ball of shellscripts used to periodically collect statistics from your database, bill customers, back up data, etc., then stop and use a custom rake task instead.
log/Logs for your application are written here. Each environment (typically development, testing, and production) will have its own logfile.
public/Static files like
404.htmland500.htmlgo here.script/Executable scripts that don’t make sense as rake tasks go here. Rails creates several by default like
serverwhich we’ll use in a moment to start the application server.test/Tests and associated files for asserting the behaviour of your models, controllers, and the application in general belong here.
tmp/Temporary files for tracking caches, pids, sessions, and sockets are kept here.
vendor/External libraries that the application depends on. Like
lib/, this directory and its children are in the load path.assets/Assets (stylesheets, etc.) belonging to plugins go in here.
plugins/Plugins installed to extend the functionality of your app go here.
config.ruUsed to start the Rails app – you typically won’t be having to mess with this file.
GemfileUsed to list the gem dependencies of your app.
Gemfile.lockis used in conjunction to keep track of which gems are currently installed. You really want to make sure both of these are checked into version control and kept updated.RakefileUsed to load tasks you define in
lib/tasks/*.rake– these tasks are really handy for running Cron jobs with to run regular operations on your app, like nightly mail or culling of the database. Note that you typically won’t have to modify this file.READMEBe kind to others and your future self – delete what’s there and fill it in meaningfully with sections that tell you what this app is, who’s responsible, how to get started, and how/where to contribute and ask questions.
Start up your app
Take a look at what your application looks like by starting up its server:
$ cd video_jukebox $ rails server => Booting WEBrick => Rails 3.1.0 application starting in development on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server [2011-09-18 02:57:11] INFO WEBrick 1.3.1 [2011-09-18 02:57:11] INFO ruby 1.8.7 (2011-06-30) [i686-darwin11.1.0] [2011-09-18 02:57:11] INFO WEBrick::HTTPServer#start: pid=61839 port=3000
With the server running in a terminal, visit http://localhost:3000 in a web browser. If everything is working correctly, you should
be looking at a web page with some suggestions on how to start writing your application as
seen in Figure 2.2, “If you can see this, Rails is correctly installed.”.
Generate a scaffolded resource
Follow the steps suggested in the browser and start by changing directories into the project and creating a video resource with a title and HTML embed code attributes:
$ rails generate scaffold Video title:string embed_code:text
Running just this one command generates all we need for a working
video resource. Let’s take a look at some of the files
just created:
db/migrate/20100129040614_create_videos.rbA script to alter the database schema to accommodate the new
Videomodel. Scripts like these are called migrations. Don’t worry if your filename is slightly different – that’s just a UTC timestamp in the name to ensure the migrations are named differently.app/models/video.rbThe new
Videomodel.test/unit/video_test.rbA suite of unit tests for the
Videomodel.test/fixtures/videos.ymlA configuration file to keep test data used to populate the videos table for use in testing.
app/controllers/videos_controller.rbThe controller for your
videoresource.app/views/videos/index.html.erbThe view used for listing all videos. The
VideoControlleruses it when responding tohttp://localhost:3000/videos/andhttp://localhost:3000/videos/indexapp/views/videos/edit.html.erbThe view used for editing a single video. The
VideoControlleruses it when responding to a request likehttp://localhost:3000/videos/1/editapp/views/videos/show.html.erbThe view used for displaying a single video. The
VideoControlleruses it when responding to a request likehttp://localhost:3000/videos/1app/views/videos/new.html.erbThe view used for displaying a form used to create a new video. The
VideoControlleruses it when responding to a request likehttp://localhost:3000/videos/newapp/views/videos/_form.html.erbA view partial used in other views. It encapsulates some template content that other views like
app/views/videos/new.html.erbandapp/views/videos/edit.html.erbboth use.test/functional/videos_controller_test.rbA test suite for the
VideoControllerthat asserts expected behavior. For example, one of the default tests checks that when a HTTP GET is made for the /index path on theVideoController, videos are fetched from the database and the response is successful.app/helpers/videos_helper.rbA place to put methods that encapsulate code used in your video resource views. Any code in the
VideosHelpermodule found withinvideos_helper.rbis available in all video views.test/unit/helpers/videos_helper_test.rbA test suite for the code in the
VideosHelpermodule.app/assets/javascripts/videos.js.coffeeCoffeeScript specific to the
videosresource. CoffeeScript is “an attempt to expose the good parts of JavaScript in a simple way” (http://jashkenas.github.com/coffee-script/) – it’s a language that compiles down to JavaScript and is nice to write in, just like Ruby. Rails automatically pre-processes it for you, sending compiled JavaScript on the way out.app/assets/stylesheets/videos.css.scssSCSS specific to the
videosresource. SCSS adds nested rules, variables, mixins, selector inheritance, and other niceties to CSS, making it easier to work with. Rails automatically pre-processes it for you, sending compiled CSS on the way out.app/assets/stylesheets/scaffolds.css.scssA stylesheet of default styles. This and
videos.css.scssis processed and rendered inapp/views/layouts/application.html.erbby way of thestylesheet_link_tagmethod call.
Running database migrations
The scaffold generator also created a database migration to update
the database schema with the new table and fields the video resource will use. Take a look at
it in db/migrate/20100131231323_create_videos.rb
:
class CreateVideos < ActiveRecord::Migration
def change
create_table :videos do |t|
t.string :title
t.text :embed_code
t.timestamps
end
end
endInstead of editing the routes as recommended in step 2 of the
“Welcome Aboard!” screen at http://localhost:3000, skip to step 3 and run the
migration the scaffold generator created by typing rake db:migrate
The results should look like this:
$ rake db:migrate == CreateVideos: migrating =================================================== -- create_table(:videos) -> 0.0022s == CreateVideos: migrated (0.0023s) ==========================================
Running the migration creates a SQLite3 database for you in
db/development.sqlite3 and creates a table called
"videos" with the schema we specified earlier.
Let’s take a deeper look at the CreateVideos migration and at what just happened.
Active Record migrations define a change method that
specifies some changes to the schema, or some code to run to put the local database schema
into a certain version. By versioning your database schema via these migrations, you gain the
same benefits that you get from using a source control system:
A history of how the database got into the shape it’s in now
Your colleagues can run the migrations you check into version control and wind up with the same schema and database state as you. (No more passing around giant balls of SQL and hoping that it works.)
Rollbacks are automated, assuming simple, non-destructive operations
As long as you’re not dropping down to SQL and use functionality specific to a database engine, the operations are database agnostic, so swapping out SQLite3 for MySQL later on is not a big deal
The typical thing you’ll be doing is migrating up a version, which is what just
happened when you ran rake db:migrate. In this case, it created a new table
videos with a column title of type string (usually 255 chars, but the specifics depend on the database
being used) and a column embed_code of type text. Timestamp
columns created_at and updated_at are also created in the videos table
– these column names are special and when detected by Rails, are set accordingly when a model
that maps to that table is created or updated.
You can also specify self.up and
self.down methods in a migration instead of change to instruct
Rails what to do when migrating up or down specifically. Migrations are also great for
flexible bulk data inserts, deletes and updates. There’s also a built-in mechanism in Rails
for cleanly seeding your database with initial data. Learn more about this sort of stuff in
Chapter 4, Active Record.
Note
By default, Rails uses a built-in SQLite3 database adapter, so installing a database engine to use Rails and Active Record is not strictly required. Rails also has support for databases like Postgres, MySQL, Oracle, DB2, but requires the installation of Ruby bindings. See the Active Record chapter for more on configuring database adapters and bindings.
Check out your working application
At this point, we can start playing around with the application. Believe it or not, we’re done with the hard parts of writing this application.
Make sure your application server is up by running
$ rails server
then visit http://localhost:3000/videos to see a page like Figure 2.3, “Rendered output of the VideosController#index action”.
Click on the New video link and start playing around with the app to see it working already.
Make sure to watch the URIs in your browser’s
location bar and the log messages coming from the terminal you started the application server
in, or by tailing tail -f log/development.log – you
should be seeing messages like POST "/videos" followed by
Processing by VideosController#create as HTML
Note
If your application doesn’t work, the fastest way to debug it is
by looking at the application log in log/development.log – Rails reports its inner workings in detail here and in
the terminal the application server is run in with colorized output. See the Appendix for
more on the logging services built into Rails.
Setting up a default route
With that said, let’s finish up this app. A good next step would be
to make it so that when you visit http://localhost:3000/, the application routes
to the VideosController#index action and you see the same view when
you visit http://localhost:3000/videos
Set this up by adding the following under the resources :videos line in config/routes.rb:
root :to => "videos#index"
Next, delete public/index.html:
$ rm public/index.html
After you’ve done that, reload http://localhost:3000 and you
should be looking at your videos/index page.
If you’re still looking at the same “Welcome aboard” page, make sure
you’ve deleted public/index.html – everybody makes this same mistake.
Curious about how that public/index.html file works and why if you
don’t delete it, it’ll always be served up instead of anything you’ve set a default route for?
Any path that resolves to a file under public/ will be
served up before Rails checks its routes. This is also how pages like http://localhost:3000/404 and http://localhost:3000/500 render without
having to depend on a working Rails setup – just take a look at public/404.html and public/500.html.
Note
Rails is able to skip the usual request handling lifecycle when
requests come in for static files in public/ by using
something called middleware, which inspects requests as they come in. Take a look at Chapter 5, Action Controller for more on the request lifecycle and
middleware.
XSS protection
Let’s add a video via its HTML embed code and have it render correctly.
Visit http://localhost:3000/ and create a new video
record. Set its title to “First thing I could think of” and grab the HTML
embed code from the first video you see from http://vimeo.com or your favourite
online video site. (There should be a button somewhere on the video site that says “embed”.)
The embed code should look like this:
<iframe width="425" height="349" src="http://www.youtube.com/embed/QH2-TGUlwu4" frameborder="0" allowfullscreen></iframe>
After creating the video, you can see that it was created, but the video embed code isn’t rendering correctly. This is because Rails escapes all dynamically rendered text in views to prevent cross-site scripting (XSS) attacks where some external JavaScript does something nasty to your users.
We want to display this embed code, but don’t want to open up a
XSS exploit vector. We can do that by just making sure only a few kinds
of HTML tags can be used when we render a video’s embed code, like <iframe> – anything else like a script tag that could contain
malicious JavaScript should be HTML-escaped so that it won’t be evaluated
by the browser as executable code.
This functionality is built-into Rails in the form of Action View’s
sanitize helper – it lets you specify certain HTML elements and attributes to pass through and disables escaping on the
rendered output.
Open up and edit the view used to render single videos, also known
as the video show page: app/views/videos/show.html.erb
Change
<p> <b>Embed code:</b> <%= @video.embed_code %> </p>
to
<p> <b>Embed code:</b> <%= sanitize @video.embed_code, :tags => ['iframe'] %> </p>
Refresh the video show page you’re looking at (e.g.,
http://localhost:3000/videos/1), to see your app safely rendering the
video embed code like in Figure 2.4, “Baked-in XSS protection thanks to data-sanitization by default is nifty”.
Nice work! It looks like this application is almost done. Before we move on to validation, comments, RSS, and AJAX, let’s poke around our application to make sure everything is working correctly.
Try visiting videos/index at http://localhost:3000/
Hmmm… it looks like the video isn’t rendering correctly. Open the
view used to render this URI (app/views/videos/index.html.erb) and look at this line:
<td><%= video.embed_code %></td>
While it would be easy to just change it to the sanitized version we used earlier, having to use the same view code in two places doesn’t feel right. If we realized some element in the sanitization call was missing, we might forget to change it in both places and cause a bug to slip in. We need to encapsulate the video embed code sanitization.
View helpers
This sort of functionality fits nicely in a helper: a method used in
views to encapsulate complex logic and clarify what’s happening in the view. Open up app/helpers/videos_helper.rb and change it to look like this:
module VideosHelper
def sanitize_embed_code(video)
sanitize video.embed_code, :tags => ['iframe']
end
endNow we can use it in both app/views/videos/show.html.erb and in app/views/videos/index.html.erb
Change app/views/videos/show.html.erb to look like this:
<p> <b>Embed code:</b> <%= sanitize @video.embed_code, :tags => ['iframe'] %> </p>
and app/views/videos/index.html.erb to look like
this:
<p> <b>Embed code:</b> <%= sanitize_embed_code @video %> </p>
Note
By default, the VideosHelper
module is included in all views rendered by the VideosController – there’s no extra code or configuration needed. Rails makes
this inclusion based on the module name, so if you create a WhatevsHelper, it will be included in views rendered by a WhatevsController. This is a great example of how Rails
conventions save you from extra configuration. For more on helpers and Action View, see
???.
Refresh your browser and make sure everything is working. If you’ve
got a bug, look at the end of the application log in log/development.log for clues, or tail -f it
as you run it.
Model validations
While playing with submitting videos, you might have noticed that it’s possible to create a video without a title or embed code. That’s not good. We need some validation to prevent creating video entries in the database with missing titles and/or embed code.
The good news is that Active Record models come with all sorts of
validation code to handle common situations like this one. In fact, we can handle this
requirement by adding just this one line to the top of the Video model in app/models/video.rb:
class Video < ActiveRecord::Base validates :title, :embed_code, :presence => true end
Try submitting a video without a title or embed code and you can see the validation we just added in action, showing an error message and a red outline around the missing elements that need to be fixed. Not bad for just a single line of validation!
We should also validate the embed code. For now, let’s just check that the code contains the beginning of an embed tag.
Change the Video model in
app/models/video.rb so that it looks like this:
class Video < ActiveRecord::Base
validates :title, :embed_code, :presence => true
validate :must_have_valid_embed_code
def must_have_valid_embed_code
unless embed_code.include?("<iframe")
errors.add(:embed_code, "Must include an iframe tag")
end
end
endThe validate method takes the name of an instance
method and runs it during validation, making it really easy to write custom validation. In
this case, the method must_have_valid_embed_code adds an
error to the instance of the model on the embed_code
attribute with a message about it needing to include an embed tag.
Try creating a new video with obviously invalid embed code with this custom validation in place and see what happens.
Rails also comes with other common validators like length_of, numericality,
format which ensures some text follows a certain format
or pattern like an email or phone number field, confirmation for when you want a password to be entered twice, and acceptance for when you need a terms of service to be accepted.
Find out more about validations in Chapter 4, Active Record.
RSS feeds & request formats
We’re almost done writing our application. The next step is to open up the videos resource to RSS readers.
An RSS reader is just like a regular web browser,
except instead of asking for HTML, it asks for a special blend of XML called Atom. So in order to support RSS readers asking for
videos, we just need to modify the VideoController to
respond to requests for the RSS format and content-type.
Fortunately for us, this is easy to do in Rails. In fact, your application already supports asking for JSON representations of resources like videos/index.
Give it a shot by visiting http://localhost:3000/videos.json and looking at the
source. It should look like this:
[{
"embed_code": "<iframe width=\"425\" height=\"349\"
src=\"http://www.youtube.com/embed/QH2-TGUlwu4\"
frameborder=\"0\" allowfullscreen></iframe>",
"created_at": "2011-10-05T19:16:59Z",
"title": "The first thing I could think of",
"updated_at": "2011-10-10T01:04:55Z",
"id": 1
}]There’s no magic here; the respond_to block in the
index method tells the action to respond to various formats in
different ways. When presented with a request for the videos/index resource in HTML format, it uses the format.html method,
which by default renders the index.html.erb view
template.
When a request for videos/index in JSON format
appears, it uses the format.json method, which in this case
has a block passed to it. Inside this block is a call to render the @videos instance variable in JSON, which happens internally by
calling @videos.to_json
Note
The Rails scaffold generator adds an JSON format of views by default in order to give you an RESTful API for your application out of the box. Find out more about how you can use Active Resource to hook up to APIs like this in Chapter 12, Active Resource.
With that in mind, in order to add an RSS feed to
the videos/index resource, all we have to do is add another method like format.html or format.json. An
easy format for handling RSS feeds is Atom – it’s the simplest
specification for RSS feeds to implement and works for what we need to do.
Change the VideosController#index
method in app/controllers/videos_controller.rb to look
like this:
def index
@videos = Video.all
respond_to do |format|
format.html # index.html.erb
format.json { render :json => @videos }
format.atom
end
endThe format.atom method tells the controller to render
the matching view template for the action and format requested.
While we could create a view template index.atom.erb and use ERB to generate the output we want,
it’s easier to write the view template using Builder, a templating system like ERB, but better suited to forming XML-like documents like Atom.
To specify that the view template uses the Builder system, just name the file with a final
postfix of .builder instead of .erb
Create the view template app/views/videos/index.atom.builder with the following:
atom_feed do |feed|
feed.title("Awesome Videos")
feed.updated(@videos.first.created_at)
@videos.each do |video|
feed.entry(video) do |entry|
entry.title(video.title)
entry.content(sanitize_embed_code(video), :type => 'html')
entry.author do |author|
author.name("The Awesome Video Group")
end
end
end
endVisit http://localhost:3000/videos.atom with an RSS reader
to see the new video index feed. Figure 2.5, “An RSS reader happily reading videos/index” is an example of what it
could look like.
View layouts
Having an RSS feed is great, but it’d be better if it was auto-discoverable by web browsers, giving the user that little “here be RSS” glowing button in the URL like in Figure 2.6, “Safari showing an RSS feed waiting for the taking”.
Making RSS feeds be auto-discoverable is done by adding
<link href="/videos.atom" rel="alternate" title="ATOM" type="application/atom+xml" />
inside the <head> section of each HTML page served up by this application. Rails makes remembering and using these
sort of links even easier by providing a view helper that lets you write
<%= auto_discovery_link_tag :atom, videos_path(:atom) %>
but where does it go in order to be in the <head>
of each HTML page?
All view templates we’ve looked at have only dealt with content
specific to the controller action they were rendering, but where’s the bigger template where
the rest get rendered inside of? That bigger template is called a layout template, and in this
case, it’s in app/views/layouts/videos.html.erb and looks
like this:
<!DOCTYPE html> <html> <head> <title>VideoJukebox</title> <%= stylesheet_link_tag "application" %> <%= javascript_include_tag "application" %> <%= csrf_meta_tags %> </head> <body> <%= yield %> </body> </html>
The Action View component covered in ??? goes into greater detail
about each method used here, but the general idea is that <= yield
%> is where rendered HTML view template code appears, and
that each controller uses the layout it’s named after (in this case, videos.html.erb) by default.
We want an auto-discoverable RSS link to appear
in the <head>, so it’s as simple as inserting the
auto-discovery link at the bottom of the <head>
section so that app/views/layouts/videos.html.erb looks
like:
<!DOCTYPE html> <html> <head> ... <%= auto_discovery_link_tag :atom, videos_path(:atom) %> </head> ...
Refresh http://localhost:3000/videos and if your browser supports it, you can
see a syndication icon appear like in Figure 2.6, “Safari showing an RSS feed waiting for the taking”.
Routes
Curious about how all this is working under the hood?
To find out which paths are handled by which controllers’ methods, run rake routes
$ rake routes
videos GET /videos(.:format) {:controller=>"videos", :action=>"index"}
POST /videos(.:format) {:controller=>"videos", :action=>"create"}
new_video GET /videos/new(.:format) {:controller=>"videos", :action=>"new"}
edit_video GET /videos/:id/edit(.:format) {:controller=>"videos", :action=>"edit"}
video GET /videos/:id(.:format) {:controller=>"videos", :action=>"show"}
PUT /videos/:id(.:format) {:controller=>"videos", :action=>"update"}
DELETE /videos/:id(.:format) {:controller=>"videos", :action=>"destroy"}Note how HTTP verbs are a big deal here – the
same /videos path routes to a different action depending if
it’s a GET or a POST.
This is part of the idea behind RESTful routing. The other important thing
to see here is that the paths are predictable and obvious. For example, a HTTP
DELETE sent to /videos/1
is a request to delete the video record with id 1.
Note
Public controller methods routed to handle requests are called actions. The connection between a path and a controller’s action is called a route.
The :id parts in the paths are
placeholders for a record id. Try creating a video and you’ll see that you can visit http://localhost:3000/videos/1 to see it again.
Your application’s routes are configured in config/routes.rb – open it up and take a look at how much code we
needed to set up these routes. Actually… just look at this one line the scaffold generator
added, because that’s all it takes:
resources :videos
This line is a method call that sets up RESTful
routing for the videos resource, where a resource is a term
borrowed from REST literature to describe a source of information with a
universal identifier and standard interface like the videos URIs in the
routing table. REST terminology also dictates that the standard interface
be able to exchange representations of the resource. In other words, when you ask for /videos/1, you should get back video 1 in some
representation.
Model associations
The last major piece of functionality we have to implement is the ability to comment on a video. If we treat comments as just another RESTful resource, we can use a generator to fill in a working skeleton of code to fill in and be done quickly.
Instead of using the scaffold generator we used for the videos resource, this time we’re going to use the resource generator – it’s the same as the scaffold generator but doesn’t create view code. Since comments are to be displayed in the video views, we don’t need comment views, so a resource generator is exactly what we need:
rails generate resource Comment body:text
You should see it create a migration, a model, a unit test suite, a fixture, a controller,
a functional test suite for the controller, a helper, a test suite for the helper, and an
entry in the config/routes.rb file (i.e. a route).
Each video needs to know which comments it has so they can be easily displayed in its view. Likewise, it might be handy for each comment to know which video it belongs to, so let’s set up those associations.
Associations between resources are defined at a model level, so
start by editing app/models/comment.rb to look like
this:
class Comment < ActiveRecord::Base belongs_to :video end
and change app/models/video.rb to
class Video < ActiveRecord::Base
has_many :comments
validates :title, :embed_code, :presence => true
validate :must_have_valid_embed_code
def must_have_valid_embed_code
unless embed_code.include?("<iframe")
errors.add(:embed_code, "must include an iframe tag")
end
end
endBefore we run the migration the generator wrote for us, we need to edit it so that each comment has a reference to the video it belongs to. In other words, we need to add a foreign key field to hold a video id.
Open up the newly created migration db/migrate/20100313161619_create_comments.rb and edit it so that it looks like
this:
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.text :body
t.references :video
t.timestamps
end
end
endThat t.references :video line adds the foreign key
field video_id to the comments database table.
Run the migration to add the comments table with the columns we specified:
rake db:migrate
Now we need to reflect that association in the routes.
Nested resources
If you look at config/routes.rb
you can see the resources :comments line the generator
inserted at the top, which routes URIs like http://localhost:3000/comments/5. Check this by running:
$ rake routes
Because each comment belongs to a video, each time a comment is created with that URI, the associated video id would have to be passed in as a parameter. A more
elegant way of dealing with hierarchial resource URIs is to use nested
resource routes, which produce URIs like http://localhost:3000/videos/1/comments/5 where the parent resource ids (in this
case, the video id) are in the path itself.
To nest the comments resource routes within the videos resource
change the routes so that the resources :videos line looks
like this:
resources :videos do resources :comments end
Check the routes after you make this change by running:
$ rake routes
This should display routes like:
video_comments GET /videos/:video_id/comments(.:format)
{:action=>"index", :controller=>"comments"}The next step is to write the CommentsController#create action. Open up app/controllers/comments_controller.rb and add a public method create so that the class looks like this:
class CommentsController < ApplicationController
def create
@video = Video.find params[:video_id]
@comment = @video.comments.create params[:comment]
redirect_to @video
end
endThe last step to make comments work is the view code. Let’s modify the video/show view to
allow comment submission and list existing comments for that video. Open up apps/views/videos/show.html.erb and add this around the bottom of
the file:
<h2>Please leave a comment:</h2>
<%= form_for([@video, Comment.new], :remote => true) do |f| %>
<div class="field">
<%= f.text_area :body, :cols => 40, :rows => 2 %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<h2>What others felt about this:</h2>
<div id="comments">
<% @video.comments.reverse.each do |comment| %>
<div class="comment">
<p><%= comment.body %></p>
</div>
<% end %>
</div>Visit http://localhost:3000/, click on a video, then try leaving a comment.
Upon refresh, it should appear below the comment form.
Partials
While it’s great that VideoHelpers#sanitize_embed_code wraps up data sanitization, what do you do when
you need to encapsulate a chunk of a page, like the form used when editing a video in http://localhost:3000/videos/1/edit or when making a new one in http://localhost:3000/videos/new? The answer is to use a partial template, better
known as a partial.
Our app renders the same partial in two places, in app/views/videos/new.html.erb:
<h1>New video</h1>
<%= render 'form' %>
<%= link_to 'Back', videos_path %>and in app/views/videos/edit.html.erb
<h1>Editing video</h1>
<%= render 'form' %>
<%= link_to 'Show', @video %> |
<%= link_to 'Back', videos_path %>That render 'form' line is what
renders the partial. All partials have their filename start with an underscore, so this
render call is using app/views/videos/_form.html.erb which looks like this:
<%= form_for(@video) do |f| %>
<% if @video.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@video.errors.count, "error") %> prohibited this video from being saved:</h2>
<ul>
<% @video.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :title %><br />
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :embed_code %><br />
<%= f.text_area :embed_code %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>There’s a lot going on here, but the important parts to notice are:
Instance variables like
@videoassigned inVideosController#newcan be used in partials that the videos/new view renders.This partial generates a form used to gather a video title and embed code and fills in existing values from
@video.
Partials are not only a great fit for displaying the same content in
more than one view, but they’re also great for rendering the same bit many times on the same
page. See how that works in the Action View chapter, as well as how to use form helpers like
form_for, label,
text_field, text_area,
and submit.
Note
VideosController#new is a
common Ruby shorthand notation for “the VideosController instance method new”
Let’s make a change to the partial to show how making a change in one place will appear in both the videos/new and videos/edit views.
Add a helpful little suggestion for the title field by making this
change to app/views/videos/_form.html.erb:
<%= form_for(@video) do |f| %> # ... <%= f.label :title %> <small>(Be clever!)</small><br /> # ...
Now take a look at http://localhost:3000/videos/new and http://localhost:3000/videos/1/edit and you should see the single change made in the
partial appear in both views.
AJAX
Let’s make adding comments feel more responsive by adding a sprinkle of AJAX so that newly submitted comments appear on the page right away, without a page refresh.
Note
AJAX, or Asynchronous JavaScript and XML has evolved in usage since the technique was named. These days, JSON is usually used in new applications instead of XML as a data format. Even more confusing is that sometimes, like this time, no data is sent back; sometimes it’s just a little JavaScript and rendered HTML. Be cool – in the end, all it means is that the page doesn’t refresh and JavaScript is sent back to the browser to be interpreted.
Rails makes doing this sort of thing super easy. Start by changing the form tag for
comments in app/views/videos/show.html.erb to look like this:
<h2>Please leave a comment:</h2> <%= form_for([@video, Comment.new], :remote => true) do |f| %> <div class="field"> ...
The key thing to notice is the addition of the :remote => true option to the
form_for call. What that does is it adds a data-remote="true" attribute to the rendered form tag, which is noticed
by code in jquery_ujs.js, which unobtrusively turns that form’s submit
actions into asynchronous (or async) requests. It’s not important to understand everything
that’s going on here; just know that we’re going to have an async call to our app from
JavaScript.
Let’s respond to that async JavaScript call from the CommentsController by changing the #create action to
respond to requests from JavaScript:
def create
@video = Video.find params[:video_id]
@comment = @video.comments.create params[:comment]
respond_to do |format|
format.html { redirect_to @video }
format.js # create.js.erb
end
end
Just like the .html.erb templates used for the videos, we’re going to
create a template to be rendered and passed back as the response to the CommentsController#create action. Create that file now: app/views/comments/create.js.erb
$("#new_comment")[0].reset()
$("#comments").prepend("<%= escape_javascript(@comment.body) %>");These few lines of jQuery are all we need. The first line resets the form used to create
new comments – by default, Rails gives it an HTML id of #new_comment. The second line prepends the comment body to the top of the #comments div. Note that it also escapes the content so it’s safe to embed in
JavaScript that’s being sent back as the response to the #create
request.
With that added, comments submitted now appear almost instantaneously due to the browser making an async call and rendering the JavaScript received as a response. That’s pretty much all there is to “making things AJAX” in Rails.
For more complex things, just play around with jQuery – there’s tons of great resources on
the web and in books. As to what else has the :remote => true option: it also
works on form_tag, link_to, and button_to – take a look
at ??? for more.
Note
“How did I just get jQuery?” As of Rails 3, jQuery comes standard with all new Rails
applications. The inclusion happens via the javascript_include_tag
"application" line in app/views/layouts/application.html.erb
which is in turn provided by app/assets/javascripts/application.js
which by default has those two lines that bring in jQuery and jQuery UJS for unobtrusively letting you do AJAX stuff.
The Console
The Rails console is a super handy tool. It loads up your entire application and gives you an irb shell so you can manually play with your application and its data like you were standing inside of it. Here’s an example:
$ rails console Loading development environment v = Video.first => #<Video id: 1, title: "First thing... > v.title = "My sweet derpina" => "My sweet derpina" v.save => true
During the early stages of app development, the console makes for a really handy admin. It also happens to be super useful for trying out ideas as you can type out one-liners without having to interact with your application from the web, running temporary hacks in the source. Apart from giving you an interactive window into your application, the Rails console also exposes a few interesting variables:
app is an instance of ActionDispatch::Integration::Session which simulates a controller in your
application. Here’s an example of playing around with it:
$ rails console
Loading development environment
>> app.url_for(Video.first)
=> "http://www.example.com/videos/1"
>> app.get '/videos/1'
=> 200
>> app.assigns(:video)
=> #<Video id: 1, ... >
>> app.path
=> "/videos/1"
>> app.reset!
=> nil
>> app.get '/hi/hello/whatever'
=> 404
>> app.post '/videos/1/comments', { :body => 'Trees are great' }
=> 200The Rails console also gives you access to a local variable called helper
that lets you run helper methods. Here are some examples:
$ rails console Loading development environment >> helper.pluralize 2, "video" => "2 videos" >> helper.submit_tag => "<input name=\"commit\" type=\"submit\" value=\"Save changes\" />"
The console also provides a super handy --sandbox option. Turning this on
will cause any database operations to be rolled back upon exiting the console. It’s great for
playing around with destructive or mutative operations in ActiveRecord without making
permanent changes to the database. Here’s how to turn it on:
$ rails console --sandbox
Note
For more on playing around with the console, check out http://errtheblog.com/posts/24-irb-mix-tape where some of these examples were
referenced from.
Need to drop down to the database and tired of remembering the differences of how to do it between SQLite, MySQL and Postgres? Look no further than dbconsole:
$ rails dbconsole SQLite version 3.7.5 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> SELECT * FROM comments; 1|Nice video!|1|2011-10-05 19:17:11.192907|2011-10-05 19:17:11.192907
Summary
Nice work! You’ve written a typical Rails application from scratch, complete with data sanitization, model validations, an RSS feed, and comments. Not bad.
Think of how long it would take if you had to spend time configuring the guts of an ORM layer, hooking it up to a view templating system, hacking together a routing system, and finding all the rest of the components you might need. Thanks to Rails being a full-stack framework, you didn't have to do any of that.
The rest of this book goes into deeper detail into each part of the Rails stack of components, like Active Record, Action View, Action Controller, and all the other bits and pieces we’ve talked about so far. We’ll be covering the most useful methods and concepts from each and steer you clear of common issues and problems.











View 4 comments




"are taken care for " should be "are taken care of for"
additionally the "leaving just you to concentrate" should probably be revised to "leaving just your own problems for you to concentrate on", or something like that.
These solutions and decisions are what makes Rails a pleasure to use – the boring, menial chores are taken care ~of~ for you, leaving just ~the tasks specific to your application for you to concentrate on~.
@Fred – changed. Thanks!
Add a comment