Introduction to Ruby on Rails

Transcription

Introduction to Ruby on Rails
Introduction to Ruby on Rails
Lesso n 1: Int ro duct io n
Intro ductio n to Rails
Yo ur First Pro gram
Restart the Web Server
Viewing Yo ur Rails Applicatio n
What Just Happened?
Editing Yo ur First Page
The Ruby Language
Adding Ruby Co ntent to a Web Page
Co ngratulatio ns!
Quiz 1 Pro ject 1
Lesso n 2: Yo ur First Dat abase Applicat io n
Creating Applicatio ns that Sto re Data
What Will the Co de Need to Do ?
Generated CRUD Co de is Called "Scaffo lding"
Step 1: Generate the Scaffo lded Co de
What Co de Gets Created?
Step 2: Create Sto rage fo r the Task Data
No w Try Out the Scaffo lded Pages
Pro ject 1 Quiz 1
Lesso n 3: Mo dif ying a Dat abase Applicat io n
Mo difying the Scaffo lding
Edit sho w.html.erb
Preserving Line Breaks
Helper Functio ns
What Just Happened?
Functio ns That Take Multiple Arguments
Alternative Helper: time_ago _in_wo rds
What Just Happened?
Functio ns with Optio ns
What Just Happened?
Pro ject 1
Lesso n 4: Adding Fie ld Validat io n
So metimes Yo u Need Rules
Making Fields Mandato ry
Let's Review
Making Mo re Than One Field Mandato ry
Unique Task Names
Checking Numbers
What Just Happened?
Quiz 1 Pro ject 1
Lesso n 5: Adding Cust o m Ruby Co de
Making Yo ur App Smarter with Ruby
Writing Custo m Validatio n
Validatio ns Fail if They Create a Message
A Little Mo re Ruby
Validate Every Time?
Hide Time Remaining?
<%= ... %> Co ntains a VALUE
<% ... %> Co ntains LOGIC
What Just Happened?
Quiz 1
Lesso n 6 : Cust o m Dat abase Que rie s
Asking New Questio ns
Behind the Scenes: Ho w Rails Displays a Task
The ActiveReco rd Library Talks to the Database
Ho w to Read All the Tasks
Reading Single Tasks fro m the Array
Searching fo r Data
Finding All Matches
Which Tasks Are Due o n No vember 6 th, 20 14?
Ho w Many Tasks Are an Ho ur Lo ng?
So rting Results
What Just Happened?
Quiz 1
Lesso n 7: Adding Cust o m Dat a Page s
Putting Data o n the Page
Intro ducing the Co ntro ller
Ho w to List Only Inco mplete Tasks
Ho w Do Yo u See the Co mplete Tasks?
Ro utes Define a Path thro ugh the App
Creating a Ro ute
Creating the Template File
What Just Happened?
Pro ject 1 Quiz 1
Lesso n 8 : Layo ut s
The Lo o k and the Links
Adding the Link to the Other Pages
What is a Rails Layo ut?
Change the Page Layo uts with applicatio n.html.erb
Creating Separates fo r Different Page Gro ups
Ho w Do Yo u Make a Link No t a Link?
What Just Happened?
Quiz 1 Pro ject 1
Lesso n 9 : Part ials
Sharing Page Fragments with Partials
Creating Yo ur First Partial
Remo ving Mo re Duplicatio n
Sharing Co de is Go o d
Adding a Seco nd Table to a Page
Ho w Do We Display the Overdue Tasks?
Why Do Lo cal Variables Help?
What Just Happened?
Pro ject 1 Quiz 1
Lesso n 10 : Lo gging In
Adding Security to the Inco mplete Tasks Page
Ho w Basic Authenticatio n Wo rks
Securing the Other Task Pages
The befo re-filter Step by Step
What Just Happened?
Quiz 1 Pro ject 1
Lesso n 11: Managing Use rs
Managing Multiple Users
Authenticating Against the Database
Displaying the Current User in the Applicatio n
What Just Happened?
Pro ject 1 Quiz 1
Lesso n 12: Asso ciat ing Dat a wit h Use rs
Giving Users Their Own Tasks
Database Relatio nships
Keys and Fo reign Keys
Add the user_id with a Migratio n
Jo ining Tables To gether
Setting the user_id o f a Task
Finding a Task's User
What Just Happened?
Quiz 1 Pro ject 1
Lesso n 13: Cre at ing T asks f o r a Use r
Reco rding Who Created a Task
Determining Which Co de Creates a Task
Finding All o f a User's Tasks
has_many is the Reverse o f belo ngs_to
The Co llectio n o f Tasks Has Its Own Finders
What Just Happened?
Pro ject 1 Quiz 1
Lesso n 14: Cre at ing a Se arch Fe at ure
Fo rms: Adding Search
Adding a Custo m Search Ro ute
Adding an actio n-metho d
Adding a Template fo r the Search Results
Passing Data to the Applicatio n
Adding a Fo rm
Abso lute vs. Relative Paths
Using a Fo rm Helper Functio n
What Just Happened?
Quiz 1 Pro ject 1
Lesso n 15: Adding Fie lds
Adding Fields to Yo ur Applicatio n
Add the Field to the Database
Add the Field to the View
Adding the Prio rity to the Sho w Task Page
Validating the Prio rity Attribute
What Just Happened?
Quiz 1 Quiz 2 Pro ject 1
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Introduction
Welco me to the O'Reilly Scho o l o f Techno lo gy's Intro ductio n to Rails co urse.
Course Objectives
When yo u co mplete this co urse, yo u will be able to :
use co de generatio n to get an applicatio n up and running.
mo del co mplex business data with database relatio nships.
add lo gin security to yo ur applicatio n.
manage yo ur web interface efficiently with partial templates and layo uts.
add search to yo ur applicatio n.
Lesso ns in this co urse begin with a list o f the specific go als fo r each lesso n, like this:
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
navigate the O'Reilly Scho o l o f Techno lo gy UserActive appro ach to learning.
navigate in the OST learning enviro nment called Co deRunner.
write and run a basic pro gram using Rails.
Befo re we begin, let's talk a bit abo ut the pro gramming enviro nment yo u'll be using fo r the co urse. We've installed all o f the
so ftware yo u'll need and co nfigured it fo r yo u so yo u can get up and running quickly. Everything yo u'll need is available thro ugh
a web bro wser. Yo u do n't need to do wnlo ad o r install anything, it's all here, ready fo r yo u to use!
Learning with O'Reilly School of T echnology Courses
As with every O'Reilly Scho o l o f Techno lo gy co urse, we'll take a user-active appro ach to learning. This means that yo u
(the user) will be active! Yo u'll learn by do ing, building live pro grams, testing them and experimenting with them—
hands-o n!
To learn a new skill o r techno lo gy, yo u have to experiment. The mo re yo u experiment, the mo re yo u learn. Our system
is designed to maximize experimentatio n and help yo u learn to learn a new skill.
We'll pro gram as much as po ssible to be sure that the principles sink in and stay with yo u.
Each time we discuss a new co ncept, yo u'll put it into co de and see what YOU can do with it. On o ccasio n we'll even
give yo u co de that do esn't wo rk, so yo u can see co mmo n mistakes and ho w to reco ver fro m them. Making mistakes
is actually ano ther go o d way to learn.
Abo ve all, we want to help yo u to learn to learn. We give yo u the to o ls to take co ntro l o f yo ur o wn learning experience.
When yo u co mplete an OST co urse, yo u kno w the subject matter, and yo u kno w ho w to expand yo ur kno wledge, so
yo u can handle changes like so ftware and o perating system updates.
Here are so me tips fo r using O'Reilly Scho o l o f Techno lo gy co urses effectively:
T ype t he co de . Resist the temptatio n to cut and paste the example co de we give yo u. Typing the co de
actually gives yo u a feel fo r the pro gramming task. Then play aro und with the examples to find o ut what else
yo u can make them do , and to check yo ur understanding. It's highly unlikely yo u'll break anything by
experimentatio n. If yo u do break so mething, that's an indicatio n to us that we need to impro ve o ur system!
T ake yo ur t im e . Learning takes time. Rushing can have negative effects o n yo ur pro gress. Slo w do wn and
let yo ur brain abso rb the new info rmatio n tho ro ughly. Taking yo ur time helps to maintain a relaxed, po sitive
appro ach. It also gives yo u the chance to try new things and learn mo re than yo u o therwise wo uld if yo u
blew thro ugh all o f the co ursewo rk to o quickly.
Expe rim e nt . Wander fro m the path o ften and explo re the po ssibilities. We can't anticipate all o f yo ur
questio ns and ideas, so it's up to yo u to experiment and create o n yo ur o wn. Yo ur instructo r will help if yo u
go co mpletely o ff the rails.
Acce pt guidance , but do n't de pe nd o n it . Try to so lve pro blems o n yo ur o wn. Go ing fro m
Acce pt guidance , but do n't de pe nd o n it . Try to so lve pro blems o n yo ur o wn. Go ing fro m
misunderstanding to understanding is the best way to acquire a new skill. Part o f what yo u're learning is
pro blem so lving. Of co urse, yo u can always co ntact yo ur instructo r fo r hints when yo u need them.
Use all available re so urce s! In real-life pro blem-so lving, yo u aren't bo und by false limitatio ns; in OST
co urses, yo u are free to use any reso urces at yo ur dispo sal to so lve pro blems yo u enco unter: the Internet,
reference bo o ks, and o nline help are all fair game.
Have f un! Relax, keep practicing, and do n't be afraid to make mistakes! Yo ur instructo r will keep yo u at it
until yo u've mastered the skill. We want yo u to get that satisfied, "I'm so co o l! I did it!" feeling. And yo u'll have
so me pro jects to sho w o ff when yo u're do ne.
Lesson Format
We'll try o ut lo ts o f examples in each lesso n. We'll have yo u write co de, lo o k at co de, and edit existing co de. The co de
will be presented in bo xes that will indicate what needs to be do ne to the co de inside.
Whenever yo u see white bo xes like the o ne belo w, yo u'll type the co ntents into the edito r windo w to try the example
yo urself. The CODE TO TYPE bar o n to p o f the white bo x co ntains directio ns fo r yo u to fo llo w:
CODE TO TYPE:
White boxes like this contain code for you to try out (type into a file to run).
If you have already written some of the code, new code for you to add looks like this.
If we want you to remove existing code, the code to remove will look like this.
We may also include instructive comments that you don't need to type.
We may run pro grams and do so me o ther activities in a terminal sessio n in the o perating system o r o ther co mmandline enviro nment. These will be sho wn like this:
INTERACTIVE SESSION:
The plain black text that we present in these INTERACTIVE boxes is
provided by the system (not for you to type). The commands we want you to type look lik
e this.
Co de and info rmatio n presented in a gray OBSERVE bo x is fo r yo u to inspect and absorb. This info rmatio n is o ften
co lo r-co ded, and fo llo wed by text explaining the co de in detail:
OBSERVE:
Gray "Observe" boxes like this contain information (usually code specifics) for you to
observe.
The paragraph(s) that fo llo w may pro vide additio n details o n inf o rm at io n that was highlighted in the Observe bo x.
We'll also set especially pertinent info rmatio n apart in "No te" bo xes:
Note
T ip
No tes pro vide info rmatio n that is useful, but no t abso lutely necessary fo r perfo rming the tasks at hand.
Tips pro vide info rmatio n that might help make the to o ls easier fo r yo u to use, such as sho rtcut keys.
WARNING
Warnings pro vide info rmatio n that can help prevent pro gram crashes and data lo ss.
T he CodeRunner Screen
This co urse is presented in Co deRunner, OST's self-co ntained enviro nment. We'll discuss the details later, but here's
a quick o verview o f the vario us areas o f the screen:
These video s explain ho w to use Co deRunner:
File Management Demo
Co de Edito r Demo
Co ursewo rk Demo
Introduction to Rails
What is Ruby o n Rails? Ruby o n Rails, o ften sho rtened to Rails, is an o pen so urce, full-stack web applicatio n
framewo rk fo r the Ruby pro gramming language. Ruby o n Rails runs o n the general-purpo se pro gramming language
Ruby, which predates it by mo re than a decade.
Yo u may have used Rails befo re witho ut ever realizing it. Rails po wers websites like Hulu, Yello w Pages, and Twitter.
It's also used frequently by high-tech startups, because Rails develo pment is fast. Mo st websites perfo rm many o f the
same kinds o f tasks, fo r instance, sto re info rmatio n that a user has entered into a web page. Rails uses code
generation to do lo ts o f the mo st co mmo n types o f pro gramming fo r yo u.
Rails develo pment usually lo o ks like this:
Yo u generate so me co de, mo dify it, and then repeat; this means Rails applicatio ns can be created quickly. In fact, by
the end o f this lesso n, yo u will have created yo ur very first Rails applicatio n!
Your First Program
Rails is used to create co mplete websites; a website such as Twitter is a single web applicatio n. That's a little different
fro m mo st o ther types o f web pro gramming. If yo u've used Perl o r PHP, yo u've pro bably created websites o ne page at
a time. Each page o n the site is like a separate pro gram. Rails applicatio ns are different. A Rails applicatio n needs lots
o f files and pages befo re it runs, but the go o d news is that Rails can generate mo st o f the files itself.
Let's get started.
Open a Unix shell by clicking the Ne w T e rm inal ico n at the to p o f the Co de Edito r windo w:
Yo u're pro mpted fo r yo ur lo gin and passwo rd. The lo gin name will appear auto matically. If fo r so me weird reaso n it
do esn't, yo u can find it o n the Student Start Page listed as "Yo ur Sandbo x lo gin." Remember this lo gin name, because
yo u'll need it again sho rtly.
INTERACTIVE SESSION:
cold1 login: login name
Password:
cold1:~$
Once yo u lo g in, yo u see the co ld1:~$ pro mpt. This means the co mputer is ready to accept yo ur instructio ns. Create a
directo ry that will co ntain yo ur Rails applicatio ns. Type these co mmands exactly as you see them here:
INTERACTIVE SESSION:
cold1:~$ mkdir railsapps (if the directory already exists, just continue to the next co
mmand)
cold1:~$ cd railsapps
cold1:~/railsapps$
WARNING
Note
The co mmands yo u need to type are m kdir railsapps and cd railsapps as sho wn in blue. Do n't
type the co ld1:...$ parts—they're just prompts fro m the co mputer that tell yo u it's ready to go .
Did yo u no tice that the pro mpt changed to "co ld1:~/railsapps$" after yo u entered the cd co mmand? The
pro mpt always sho ws yo u which directo ry yo u are in currently. Whenever yo u run co mmands in the Unix
shell, make sure yo u are in the co rrect directo ry.
The first co mmand creates a directo ry named railsapps; the seco nd co mmand mo ves yo u into that directo ry. It's
extremely impo rtant that yo u name this directo ry railsapps, because it's the name o ur servers expect. If yo u name it
anything else, yo ur Rails pro grams wo n't wo rk.
No w that yo u're in the railsapps directo ry, yo u're ready to create yo ur first Rails applicatio n. We'll name the applicatio n
o st app. Type the co mmand exactly as yo u see it. The server is co nfigured to expect an applicatio n named o st app:
INTERACTIVE SESSION:
cold1:~/railsapps$ rails new ostapp
create
create README
create Rakefile
create config.ru
create .gitignore
create Gemfile
create app
create app/controllers/application_controller.rb
create app/helpers/application_helper.rb
create app/mailers
create app/models
create app/views/layouts/application.html.erb
create config
create config/routes.rb
...
create vendor/plugins
create vendor/plugins/.gitkeep
cold1:~/railsapps$
Rails generates a large number o f files and directo ries—far mo re than we sho w here. Do n't wo rry abo ut the number o f
files Rails generates. Mo st o f them are just default co de that yo u wo n't need to change.
All o f tho se files are created in the directo ry named o st app that yo u can see in the File Bro wser panel o n the left. The
directo ry name matches the name yo u gave the applicatio n.
Note
If yo u do n't see the /o st app directo ry in the File Bro wser panel, right-click the Ho m e directo ry and select
Re lo ad fro m the menu that appears. This updates the bro wser panel to sho w the latest files and
directo ries.
Restart the Web Server
Once yo u've created yo ur applicatio n, yo u need to co nnect it to a web server. We've already co nfigured yo ur web
server so it kno ws where to find it, all yo u need to do is restart the web server:
INTERACTIVE SESSION:
cold1:~/railsapps$ ~/httpd/bin/apachectl restart
cold1:~/railsapps$
When yo ur web server is restarted, yo ur applicatio n will begin running and it's ready to use.
Viewing Your Rails Application
Open a new web bro wser windo w o r tab and go to ht t p://login-id.o re illyst ude nt .co m /o st app/. Replace the login-id
part o f the address with the name yo u used to lo g in to the terminal.
When yo u o pen the bro wser, it will lo o k so mething like this:
What Just Happened?
When yo u type rails ne w o st app, yo u're telling Rails that yo u want to create a new applicatio n named o st app. Rails
then creates an o st app fo lder and fills it with a who le heap o f files and fo lders that make up the new applicatio n.
When yo u po int yo ur bro wser at ht t p://login-id.o re illyst ude nt .co m /o st app/, yo ur bro wser co ntacts the Rails
server. The Rails server has already been co nfigured to run the applicatio n yo u created in the o st app directo ry. So
why did the server return this particular welco me page?
This is the default ho me page fo r yo ur applicatio n. Every Rails applicatio n has to have a ho me page, tho ugh yo u'd
never want an applicatio n to go live with the default versio n o f that page, so yo u'll need to edit it.
Editing Your First Page
The ho me page o f yo ur applicatio n is static—it always lo o ks the same. All o f the static co ntent o f yo ur applicatio n—
such as the images, stylesheets, and the ho me page—is in the public/ subdirecto ry o f /o st app. Yo ur ho me page is
sto red in the inde x.ht m l file.
To edit inde x.ht m l, o pen the /railsapps/o st app/public directo ry in the File Bro wser panel and then do uble-click the
inde x.ht m l file. The inde x.ht m l file o pens in the Co de Edito r panel at the bo tto m o f the screen.
The inde x.ht m l file is just an o rdinary HTML web page. When a user makes a request fo r a URL like ht t p://loginid.o re illyst ude nt .co m /o st app/, the Rails server sends back the co ntents o f the
railsapps/o st app/public/inde x.ht m l file.
OBSERVE:
<!DOCTYPE html>
<html>
<head>
<title>Ruby on Rails: Welcome aboard</title>
<style type="text/css" media="screen">
body {
margin: 0;
margin-bottom: 25px;
padding: 0;
background-color: #f0f0f0;
...
Scro ll do wn to the <bo dy> sectio n (aro und line 18 6 ), and then add so me co ntent, like this:
CODE TO TYPE:
...
<body>
<h1>Awesome new addition</h1>
<p>This is an awesome new addition to my
Rails application.</p>
<p>Look on my works, ye Mighty, and despair!</p>
<div id="page">
<div id="sidebar">
<ul id="sidebar-items">
<li>
<h3>Browse the documentation</h3>
<ul class="links">
<li><a href="http://api.rubyonrails.org/">Rails API</a></li>
<li><a href="http://stdlib.rubyonrails.org/">Ruby standard library</a></li
>
<li><a href="http://corelib.rubyonrails.org/">Ruby core</a></li>
<li><a href="http://guides.rubyonrails.org/">Rails Guides</a></li>
</ul>
</li>
</ul>
</div>
When yo u finish editing the file, save it by clicking the Save butto n o n the to o lbar:
T ip
Altho ugh the edito r to o lbar has a Pre vie w butto n that will o pen the current page in a web bro wser, it won't
wo rk fo r mo st o f the pages yo u create in Rails. Instead, keep a separate bro wser windo w o pen to view yo ur
applicatio n.
Switch back to the ht t p://login-id.o re illyst ude nt .co m /o st app/ windo w and click o n the bro wser's Re f re sh butto n.
Yo ur page is updated:
All o f the files in the /public directo ry are static, but mo st web applicatio ns display dynamic co ntent. When yo u go to
ht t p://t wit t e r.co m , yo u expect to see a list o f the latest tweets. When yo u do a search o n Go o gle, yo u see a page
co ntaining a list o f search results. All o f the dynamic co ntent—whether it's a list o f tweets, o r a set o f search results—
has been generated so mewhere by a piece o f code. In the case o f Ruby o n Rails, the co de that generates dynamic
co ntent is written in the Ruby language.
We've already used HTML, but what do es Ruby co de lo o k like?
T he Ruby Language
Befo re we add Ruby co de to o ur applicatio n, let's try o ut so me Ruby co de in the Unix shell. On the O'Reilly Scho o l
servers, we've installed a pro gram called Int e ract ive Ruby, o r irb. Go back to the Terminal tab o r o pen a new o ne,
and type irb at the co ld: pro mpt as sho wn:
INTERACTIVE SESSION:
cold1:~/railsapps$ irb
irb(main):001:0>
Interactive Ruby is a to o l that runs small pieces o f Ruby co de. irb will run a piece o f Ruby co de as so o n as yo u enter it.
Fo r example, yo u can add two numbers to gether like this:
INTERACTIVE SESSION:
irb(main):001:0> 1 + 1
=> 2
irb(main):002:0>
1 + 1 is a Ruby expression. An expressio n is a piece o f co de that has a value. In this case, Ruby returns the value 2—
that's what => 2 means.
Ruby can wo rk with text as well as numbers. Fo r example:
INTERACTIVE SESSION:
irb(main):002:0> "Hello " + "World!"
=> "Hello World!"
irb(main):003:0>
If Ruby sees a sequence o f characters surro unded in quo tes, it treats the characters as a single piece o f text called a
string. If yo u add two strings to gether using the + symbo l, Ruby creates a new string by co nnecting them to gether. Ruby
even lets yo u multiply a string by a number:
INTERACTIVE SESSION:
irb(main):003:0> "Hello " * 10
=> "Hello Hello Hello Hello Hello Hello Hello Hello Hello Hello "
irb(main):004:0>
Multiplying a string by 10 creates a new string co ntaining 10 co pies o f the o riginal. Ruby can pro cess numbers and
strings, but that's no t all. Fo r example, Ruby can tell yo u the current date and time:
INTERACTIVE SESSION:
irb(main):004:0> Time.now
=> Tue Apr 11 07:30:35 -0500 2012
irb(main):005:0>
The T im e .no w expressio n is a little mo re interesting than the o thers, because it will pro duce a different result each
time yo u run it:
INTERACTIVE SESSION:
irb(main):005:0> Time.now
=> Tue Apr 11 07:32:21 -0500 2012
irb(main):006:0> Time.now
=> Tue Apr 11 07:32:23 -0500 2012
irb(main):007:0> Time.now
=> Tue Apr 11 07:32:29 -0500 2012
irb(main):008:0>
T ip
To repeat a co mmand yo u've typed befo re in the Unix shell, use the up arro w key. Yo u can scro ll thro ugh
previo us co mmands using the up and do wn arro w keys.
To leave the irb sessio n, use the quit co mmand:
INTERACTIVE SESSION:
=> Tue Apr 11 07:32:29 -0500 2012
irb(main):008:0> quit
cold1:~$
Adding Ruby Content to a Web Page
No w, what if yo u want to include the value o f a piece o f Ruby co de inside a web page? Fo r example, let's say yo u want
to build a web page that wo rks like a timer:
Yo u co uld build a web page that displays the current value o f the T im e .no w expressio n.
To insert a dynamic value like T im e .no w into a web page, we'll co mbine HTML co de and Ruby co de in the same
page.
We'll begin by generating so me co de. Remember, in Rails, yo u generate the co de first and then mo dify it. Yo u'll
generate a dynamic web page by entering a co mmand into the Unix shell. Yo u run the Rails co mmand fro m the
applicatio n's directo ry—~/railsapps/o st app, so yo u need to change into the /o st app/ directo ry and then run the
co mmand to generate the web page:
INTERACTIVE SESSION:
cold1:~/railsapps$ cd ~/railsapps/ostapp
cold1:~/railsapps/ostapp$ rails generate controller my_timer show_current_time
create app/controllers/my_timer_controller.rb
route get "my_timer/show_current_time"
invoke erb
create
app/views/my_timer
create
app/views/my_timer/show_current_time.html.erb
invoke test_unit
create
test/functional/my_timer_controller_test.rb
invoke helper
create
app/helpers/my_timer_helper.rb
invoke
test_unit
create
test/unit/helpers/my_timer_helper_test.rb
cold1:~/railsapps/ostapp$
We use the rails ge ne rat e co mmand to add co de to an existing applicatio n. Let's break the co mmand do wn and see
what each part o f it means:
OBSERVE:
rails generate controller my_timer show_current_time
rails ge ne rat e tells Rails that we want it to create so me extra co de fo r the applicatio n.
co nt ro lle r indicates which kind o f co de we want to add to the applicatio n—in this case, we're creating
co nt ro lle r co de (we'll explo re co ntro ller co de in mo re detail later in the co urse, but fo r no w just be aware
that it will include a dynamic web page that we can edit).
m y_t im e r is the name o f the subdirecto ry where the co de will be sto red. Rails always sto res dynamic web
pages in the app/vie ws subdirecto ry. The m y_t im e r value indicates that we want Rails to sto re o ur new
web page in the app/vie ws/m y_t im e r subdirecto ry.
sho w_curre nt _t im e is the name o f o ur new web page file. Rails will add an .ht m l.e rb extensio n
auto matically, so the dynamic web page file will be named
app/vie ws/m y_t im e r/sho w_curre nt _t im e .ht m l.e rb.
The rails ge ne rat e co mmand creates a new web page file named
~/railsapps/o st app/app/vie ws/m y_t im e r/sho w_curre nt _t im e .ht m l.e rb. In fact, the ~/railsapps/o st app/app
directo ry co ntains mo st o f the co de yo u'll use. Yo u can view the page that the sho w_curre nt _t im e .ht m l.e rb file
sends o ut by o pening a bro wser at ht t p://loginid.o re illyst ude nt .co m /o st app/m y_t im e r/sho w_curre nt _t im e .ht m l:
Note
The file in the Rails applicatio n has the extensio n .ht m l.e rb, but when yo u view it o n the web, yo u use an
.ht m l extensio n in the URL. The web page file is
~/railsapps/o st app/app/vie ws/m y_t im e r/sho w_curre nt _t im e .ht m l.e rb, but the matching URL is
ht t p://login-id.o re illyst ude nt .co m /o st app/m y_t im e r/sho w_curre nt _t im e .ht m l. Rails will use the
sho w_curre nt _t im e .ht m l.e rb file to create a respo nse every time a bro wser asks fo r
sho w_curre nt _t im e .ht m l.
This new web page is a little different fro m the static ho me page yo u edited befo re; the new web page can be mo dified
to include so me Ruby co de.
Edit the new web page file by do uble-clicking
~/railsapps/o st app/app/vie ws/m y_t im e r/sho w_curre nt _t im e .ht m l.e rb in the File Bro wser panel:
OBSERVE: ~/railsapps/o stapp/app/views/my_timer/sho w_current_time.html.erb
<h1>MyTimer#show_current_time</h1>
<p>Find me in app/views/my_timer/show_current_time.html.erb</p>
This file is called a template. A template is similar to an o rdinary static HTML page, except it's a little sho rter and can be
mo dified to include Ruby code.
Note
Templates are much sho rter than static pages because Rails will add things like the <he ad>...</he ad>
sectio n fo r yo u auto matically. Later in the co urse yo u'll see ho w yo u can co ntro l the additio nal HTML that
Rails will add.
To add Ruby co de to a template, insert it between "<% =" and "% >" markers. These markers tell Rails that the text
between them is a Ruby expression. Rails will evaluate the co de between the markers and send the value o f the
expressio n to the web bro wser. Mo dify the sho w_curre nt _t im e .ht m l.e rb file to include the T im e .no w Ruby
expressio n to see ho w this wo rks:
CODE TO TYPE:
<h1>MyTimer#show_current_time</h1>
<p>Find me in app/views/my_timer/show_current_time.html.erb</p>
The current time is:
<%= Time.now() %>
Save the file and lo ad o r refresh the bro wser windo w at ht t p://loginid.o re illyst ude nt .co m /o st app/m y_t im e r/sho w_curre nt _t im e .ht m l.
Instead o f displaying the T im e .no w text, Rails replaces it with the current date and time. If yo u refresh the web page,
the date and time changes and sho ws the current time.
Congratulations!
Yo u just created yo ur first dynamic Rails applicatio n! Let's review:
Yo u created a brand-new applicatio n using the rails ne w o st app co mmand.
Yo u edited the fro nt page o f the applicatio n by changing the co ntents o f o st app/public/inde x.ht m l.
Yo u created so me dynamic controller co de with rails ge ne rat e co nt ro lle r m y_t im e r
sho w_curre nt _t im e .
The co ntro ller co de generated a web page at http://loginid.o reillystudent.co m/o stapp/my_timer/sho w_current_time.html
Yo u mo dified the page to dynamically include the date and time every time a user lo o ks at it.
When yo u finish each lesso n, go back to yo ur syllabus page and co mplete the quizzes and pro jects to make sure yo u're ready
to mo ve o n to the next lesso n. See yo u there!
Copyright © 1998-2014 O'Reilly Media, Inc.
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Your First Database Application
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
create a Database Applicatio n using Rails.
use Rails scaffo lding to generate a set o f web pages that will allo w a user to create, read, update, and delete tasks.
Creating Applications that Store Data
Mo st applicatio ns need to sto re and retrieve info rmatio n. Facebo o k sto res yo ur po sts and lets o ther peo ple read
them. Go o gle sto res data abo ut web pages that yo u can search thro ugh. Whatever yo u design yo ur applicatio n to do ,
it will pro bably need to sto re and retrieve info rmatio n.
Let's lo o k at an example. Suppo se yo u want to help peo ple manage tasks o nline. A task might have these elements:
Ele m e nt
Name
Exam ple
Buy eggs
Descriptio n Go to the market and buy six eggs. Try to get o rganic, if po ssible.
Duratio n
60
Due date
12/12/20 14
Co mplete? X
Altho ugh the details o f each task will be different, the kinds o f data sto red fo r each task will be the same. Fo r example, a
task will always be given a name. Here is a list o f elements that a user might want to reco rd fo r a task:
Nam e is a sho rt piece o f text that identifies the task.
De script io n is a lo nger piece o f text that pro vides mo re details abo ut exactly what the task invo lves.
Durat io n is the number o f minutes the task is likely to take.
Due dat e is the date when the task sho uld be co mpleted.
Co m ple t e is a true/false value that indicates whether o r no t the task is co mplete.
These elements are called the attributes o f the task. Each attribute has a data type that specifies which kind o f data the
attribute will sto re. Rails suppo rts just a few data types:
Dat a t ype
De script io n
string
a sho rt piece o f text
text
a lo nger piece o f text o f up to 6 4,0 0 0 characters
date
a date
bo o lean
a true/false value
integer
a who le number with no fractio ns
flo at
a number that includes fractio ns
decimal
a base-10 fractio nal number
binary
a series o f 1s and 0 s—mo st o f the time yo u will use a number instead
datetime
a date that also includes the time o f day
time
just the time o f day, witho ut a date
timestamp an unfo rmatted number that refers to a particular date and time
references a reference to so me o ther piece o f data
By telling Rails the data type o f each attribute, yo u can prevent a user fro m entering inappro priate info rmatio n, such as
sto ring a piece o f text in the duratio n attribute, o r setting the due date to a number.
Rails will see each task as a co llectio n o f attributes, like this:
At t ribut e dat a t ype
Name
string
Descriptio n text
Duratio n
integer
Due date
date
Co mplete? bo o lean
Once yo u've go t a list o f attributes with data types, yo u're ready to write so me Rails co de to manage the data.
What Will the Code Need to Do?
If users are go ing to manage tasks, yo ur applicatio n needs to be able to :
Cre at e t asks: Users will need to create tasks; tho se tasks need to be sto red safely.
Re ad t asks: Users will need to be able to read the tasks they've created.
Updat e t asks: After users create tasks, they may want to edit them; they may want to co rrect spelling,
change the date, o r mark the task as co mplete.
De le t e t asks: Users might want to delete tasks.
These fo ur o peratio ns are kno wn as the CRUD o peratio ns. CRUD is a charming acro nym devised fro m: Create, Read,
Update, and Delete. CRUD o peratio ns are universal. Any change yo u'll ever make to any set o f data will invo lve
creating, reading, updating, o r deleting.
Mo st applicatio ns yo u write in Rails will need to do these fo ur o peratio ns. Rather than writing CRUD co de yo urself,
yo u can get Rails to generate the co de fo r yo u.
Generated CRUD Code is Called "Scaffolding"
In Rails, we create co de by generating it, then mo difying it. Yo u've seen that Rails can generate a single dynamic page
fo r yo u, but yo u can also generate an entire set o f web pages that will allo w yo u to create, read, update, and delete a
piece o f info rmatio n. The generated co de that do es this has a special name—it's called scaffolding.
Scaffo lded co de fo rms the basis o f mo st Rails applicatio ns. In the same way that yo u'd co nstruct scaffo lding if yo u
were building a ho use, yo u create scaffo lding when yo u're building a Rails applicatio n. This is ho w we will scaffo ld the
co de fo r o ur task data:
Ge ne rat e t he scaf f o ld: This will create a set o f web pages to create and edit the task data.
Pre pare t he st o rage : Once the web pages exist to create tasks, we'll need so mewhere to sto re the data.
Cust o m ize t he co de : Rails will do the hard wo rk o f creating mo st o f the co de fo r yo u, but yo u'll o ften
make changes to the co de Rails creates.
Step 1: Generate the Scaffolded Code
We'll use the rails ge ne rat e co mmand to generate the scaffo lding co de. Remember—rails ge ne rat e is the
co mmand yo u use whenever yo u want to create extra co de fo r an applicatio n that already exists. Make sure yo u are in
the ~/railsapps/o st app directo ry, and then run this co mmand (make sure yo u type all o f it!):
INTERACTIVE SESSION:
cold1:~/railsapps/ostapp$ rails generate scaffold Task name:string description:text dur
ation:integer due_date:date complete:boolean
invoke active_record
create
db/migrate/20120401085758_create_tasks.rb
create
app/models/task.rb
invoke
test_unit
create
test/unit/task_test.rb
create
test/fixtures/tasks.yml
route resources :tasks
invoke scaffold_controller
create
app/controllers/tasks_controller.rb
invoke
erb
create
app/views/tasks
create
app/views/tasks/index.html.erb
create
app/views/tasks/edit.html.erb
create
app/views/tasks/show.html.erb
create
app/views/tasks
ew.html.erb
create
app/views/tasks/_form.html.erb
invoke
test_unit
create
test/functional/tasks_controller_test.rb
invoke
helper
create
app/helpers/tasks_helper.rb
invoke
test_unit
create
test/unit/helpers/tasks_helper_test.rb
invoke stylesheets
create
public/stylesheets/scaffold.css
cold1:~/railsapps/ostapp$
Let's lo o k at the co mmand in detail:
OBSERVE:
rails generate scaffold Task name:string description:text duration:integer due_date:dat
e complete:boolean
rails ge ne rat e : Always use this co mmand when yo u want to create extra co de fo r an applicatio n.
scaf f o ld: This is the kind o f co de yo u want to create. Scaffo ld co de will create all o f the pages yo u need to
create, read, update, and delete data.
T ask: This is the name we'll use fo r the data. The name must begin with an uppercase letter, it must be a
singular wo rd—so here we're using T ask rather than T asks, and the name must no t co ntain any spaces.
The rest o f the co mmand tells Rails exactly what the task data will lo o k like. These are the data attributes we lo o ked at
earlier:
At t ribut e Dat a T ype
name
string
descriptio n text
duratio n
integer
due_date
date
co mplete
bo o lean
We tell Rails abo ut each o f these attributes using the fo rmat attribute:data-type.
What Code Gets Created?
Earlier yo u used the rails ge ne rat e co mmand to create a dynamic web page:
OBSERVE:
cold1:~/railsapps/ostapp$ rails generate controller my_timer show_current_time
The controller co de included a single dynamic web page at
~/railsapps/o st app/app/vie ws/m y_t im e r/sho w_curre nt _t im e .ht m l.e rb, but when yo u generate
scaffo lding co de, Rails creates several web pages. Each o f the web pages perfo rms a specific task—fo r
example, o ne page allo ws the user to enter a new task and ano ther displays a list o f all tasks. Rails sto res
these web pages in a single directo ry in ~/railsapps/o st app/app/vie ws. Because yo u to ld Rails to generate
scaffo lding co de fo r T ask data, Rails named the directo ry ~/railsapps/o st app/app/vie ws/t asks (that's why
we used the singular T ask in the ge ne rat e scaf f o ld co mmand):
Each o f these fo ur pages co rrespo nds to an action that yo u want to perfo rm o n the task data. Each actio n has
a URL:
Scaf f o lde d act io n
List/index all tasks
URL
http://login-id.o reillystudent.co m/o stapp/tasks/
Display a fo rm to create a task http://login-id.o reillystudent.co m/o stapp/tasks/new.html
Display a fo rm to edit a task
http://login-id.o reillystudent.co m/o stapp/tasks/edit.html
...
...
When yo u ran the ge ne rat e scaf f o ld T ask co mmand, Rails created the /t asks directo ry under the
~/railsapps/o st app/app/vie ws fo lder and then put the .ht m l.e rb template files into it. Yo u specified the
attributes each task wo uld co ntain, so Rails was able to create template files that co ntained places fo r the
name, descriptio n, and such fo r the task data.
Yet even tho ugh Rails has generated all o f the actio ns to manage the T ask data, the scaffo lding is no t ready
to be used yet. Yo u still need to create a place fo r Rails to sto re the task data.
Step 2: Create Storage for the T ask Data
Ho w do yo u prepare the sto rage? The go o d news is that the scaffo lding co de already includes everything yo u need to
prepare a place to sto re the task data. All yo u need to do is run it. Rails has created a script file in the
~/railsapps/o st app/db/m igrat e directo ry with a name that includes these elements: timestamp_cre at e _t asks.rb.
Take a lo o k at the script; yo u'll see it co ntains so me Ruby co de:
OBSERVE: ~/railsapps/o stapp/db/migrate/20 120 420 115533_create_tasks.rb
class CreateTasks < ActiveRecord::Migration
def self.up
create_table :tasks do |t|
t.string :name
t.text :description
t.integer :duration
t.date :due_date
t.boolean :complete
t.timestamps
end
end
def self.down
drop_table :tasks
end
end
This Ruby co de was generated at the same time as the scaffo lded template files, but even tho ugh Rails created this
file, the co de inside it hasn't been run yet.
Note
Rails do es no t run this script auto matically, as so o n as it creates it. That's because database changes
can so metimes take a lo ng time to run, and if yo u're sharing a database with several o ther develo pers,
yo u might want to decide exactly when database changes are made. Fo rtunately, yo u have exclusive use
o f the database yo u're wo rking o n here, so yo u can make changes to it whenever yo u like.
So far yo u've o nly used small Ruby expressions, but this script is much lo nger. Yo u do n't need to understand the
details o f this Ruby co de just yet—yo u just need to kno w that it will create a t able to sto re the task data. Rails sto res
data in a database using tables. If yo u've never wo rked with a database befo re, think o f a table as so mething similar to
a spreadsheet. The Task table will lo o k like this:
nam e
de script io n
durat io n due _dat e co m ple t e
Buy eggs Go to the sto re and buy eggs
6 0 12/11/20 14 false
Visit gym Run 8 m
30 11/12/20 14 false
A table is a sto rage area in a database. Each task is sto red in a separate row in the table. The ro w co ntains several
columns—o ne fo r each o f the task attributes. The cre at e _t asks.rb script will create this table fo r yo u. Yo u can run the
script using the rake co mmand:
INTERACTIVE SESSION:
cold1:~/railsapps/ostapp$ rake db:migrate
(in ~/railsapps/ostapp)
== CreateTasks: migrating ====================================================
-- create_table(:tasks)
-> 0.0046s
== CreateTasks: migrated (0.0048s) ===========================================
cold1:~/railsapps/ostapp$
Note
The rake db:m igrat e co mmand runs the migration script in /db/m igrat e .
rake is a to o l that helps yo u manage yo ur Rails applicatio n. It's like a perso nal assistant. It do esn't generate any co de
itself, but it helps yo u manage the applicatio n's enviro nment. In this case, rake is to ld to go and lo o k fo r any new
scripts in the db/m igrat e directo ry and run them. The db/m igrat e directo ry is used to sto re Ruby scripts that change
(o r migrate) the database sto rage. Fo r that reaso n, the scripts in the db/m igrat e directo ry are called migrations and the
rake db:m igrat e co mmand means, "Run any new migratio n scripts."
Once yo u've created the table, refresh the bro wser po inted at ht t p://login-id.o re illyst ude nt .co m /o st app/t asks/
and yo u'll see this:
Now T ry Out the Scaffolded Pages
If yo u can see an empty list o f tasks, yo ur scaffo lding co de is up and running. Take so me time to use the
pages to create and edit so me tasks. Start by clicking the Ne w T ask link:
Then, enter the details o f a new task:
Rails creates the fo rm to match the attributes o f the data. The name attribute o f a task is a st ring, so Rails
generates a label o n the page co ntaining the text "Name" and then places it abo ve a simple text entry field:
The descriptio n attribute is a lo nger text value, so Rails generates a large textarea fo r the user to enter the
descriptio n:
Rails creates ano ther text field fo r the user to enter a duratio n value:
The due_date attribute is a date, so Rails has to wo rk a little harder. First, it co nverts the due_date attribute
name into a mo re readable "Due date" label, and then it creates three separate dro p-do wn select bo xes fo r
the user to enter the date value:
Rails generates a simple checkbox fo r the bo o lean co m ple t e attribute:
Because yo u already to ld Rails the data types o f the task attributes, Rails uses that info rmatio n to derive the
kinds o f fields it needs in the applicatio n.
When yo u finish entering the data in the fo rm, click Cre at e T ask:
The details o f the task are sent to the Rails applicatio n, where they are sto red in the database. Then, the
applicatio n displays a page co ntaining the details o f the new task:
That means yo u used the applicatio n to create and read task data. To return to the main tasks page, click the
Back link. Yo u will see yo ur new task listed. If yo u like, yo u can click Ne w T ask and add ano ther task. Each
time yo u add a task, it gets added to the list o f tasks o n the fro nt page:
If yo u want to change any o f the tasks, click the Edit link and yo u'll see the task in a fo rm:
Note
This fo rm might lo o k familiar. Rails uses the same fo rm to create and edit tasks. The co de that
displays the fo rm is in app/vie ws/t asks/_f o rm .ht m l.e rb and the ne w.ht m l.e rb and
cre at e .ht m l.e rb templates use it. Yo u'll see ho w o ne template can include the co ntents o f
ano ther template later in the co urse.
If yo u change the details o f the task and then click Updat e T ask, yo u'll be sho wn the details o f the amended
task.
So no w yo u can create, read, and update tasks, but what abo ut deleting tasks? Next to each task o n the main
list, there's a link labeled De st ro y. Click o n this link and the applicatio n will ask yo u if yo u're sure yo u want to
delete the task:
Click OK to remo ve the task fro m the list:
The scaffo lded pages allo w yo u to create, read, update, and delete task data. This is ho w all o f tho se
scaffo lded pages link to gether:
Note
The Delete functio n do esn't have a page o f its o wn. When yo u click the De le t e link o n the
list/index page, it remo ves the task and then returns yo u to the list.
Yo u can enter and maintain any number o f tasks. All o f the pages and all o f the co de yo u need have been
generated fo r yo u. Because the data is sto red inside a database, it do esn't matter if yo ur applicatio n crashes
o r if the server machine is restarted; yo ur tasks remain o n the system until so meo ne deletes them. Yo ur data
is persistent.
Do es this mean that scaffo lding creates the entire applicatio n fo r yo u? No t quite. Scaffo lding creates general
co de that allo ws anyo ne to change data in a general way. It do esn't have any security. It always displays all o f
the data and, o f co urse, it has a standardized lo o k and feel.
Scaffo lding gives yo u a head start o n Rails develo pment. It creates the bulk o f the co de that yo u'll need and it
will get yo ur applicatio n up and running really quickly. Ho wever, scaffo lded co de is really just a starting po int
in Rails develo pment. To go further, yo u need to understand mo re abo ut ho w scaffo lding wo rks, so that yo u
can custo mize scaffo lded co de and make the applicatio n do exactly what yo u want.
In the next lesso n we'll begin to mo dify scaffo lded co de. See yo u there!
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Modifying a Database Application
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
use o ptio ns in Rails to mo dify yo ur applicatio ns.
Modifying the Scaffolding
We've used Rails scaffo lding to generate a set o f web pages that will allo w a user to create, read, update, and delete
task data using o ur applicatio n. Scaffo lded co de is a great way to get started o n an applicatio n. It pro vides a lo t o f the
basic co de to do the standard o peratio ns a user will need, but scaffo lded co de is only a start. Yo u'll almo st never send
an applicatio n live that just uses the default scaffo lded co de. Yo u might want to change the links within the generated
pages, o r fo rmat the data differently.
Fo r example, if a user enters a really lo ng descriptio n, they might want to break it up into separate paragraphs to make
it mo re readable:
The scaffo lded pages hide the line breaks when yo u view the task:
Yo u need to mo dify the scaffo lded co de to preserve the line breaks. So , if yo u want to mo dify ho w the task descriptio n
is displayed, what co de will yo u need to change? Think back to when yo u created a dynamic web page that displayed
the current date and time:
This page was generated dynamically using a template file called sho w_curre nt _t im e .ht m l.e rb. Every time a user
requests the web page, the template is used to generate a respo nse. Scaffo lding wo rks in exactly the same way, but
instead o f using just o ne template file, it uses several. We saw earlier ho w each o f tho se pages linked to gether.
Each o f the pages has a matching template file in the /app/vie ws/t asks directo ry. To change the appearance o f a
page, yo u need to edit its template file.
Edit show.html.erb
To mo dify the page that displays an individual task, yo u'll need to edit the sho w.ht m l.e rb file. Open that file in the
edito r no w by do uble-clicking o n the file in the File Bro wser panel:
The file appears in the Co de Edito r at the bo tto m o f this screen:
OBSERVE: app/views/tasks/sho w.html.erb
<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
<%= @task.name %>
</p>
<p>
<b>Description:</b>
<%= @task.description %>
</p>
<p>
<b>Duration:</b>
<%= @task.duration %>
</p>
<p>
<b>Due date:</b>
<%= @task.due_date %>
</p>
<p>
<b>Complete:</b>
<%= @task.complete %>
</p>
<%= link_to 'Edit', edit_task_path(@task) %> |
<%= link_to 'Back', tasks_path %>
The sho w.ht m l.e rb file includes Ruby expressio ns fo r each o f the attributes o f the task. Each expressio n uses the
period operator to read the value o f each o f the attributes.
We want to mo dify the way the descriptio n appears to make sure we can see line breaks, so we'll mo dify the
@ t ask.de script io n expressio n.
Preserving Line Breaks
So yo u've fo und the template file yo u need to change, and yo u've identified the line o f co de that's inserting the
descriptio n. No w yo u need to find o ut why the line breaks are disappearing.
It has to do with the way HTML wo rks. View the task in a bro wser and then lo o k at the HTML so urce (to do
that, yo u can usually right-click the web page and cho o se so mething like, "View So urce"). The line breaks in
the o riginal descriptio n text are included in the so urce o f the web page:
Ho wever, when a bro wser reads that HTML so urce, all o f the line breaks are skipped and the entire
descriptio n displays as a single line o f text.
To preserve the line breaks, yo u use a helper function.
Helper Functions
Rails helper functio ns fo rmat the data in web pages. A function is a piece o f co de that reads o ne o r mo re pieces o f data
and creates so me new value. Fo r example, yo u might have a functio n that reads two numbers and calculates their
sum. The value that the functio n creates is called a return value. Rails helper functio ns take the data they are given and
co nvert it into fo rmatted text that displays well o n a web page.
Fo r example, to preserve the line breaks in a piece o f text, yo u can edit the sho w.ht m l.e rb file and use the
sim ple _f o rm at helper functio n:
CODE TO TYPE:
<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
<%= @task.name %>
</p>
<p>
<b>Description:</b>
<%= @task.description %>
<%= simple_format(@task.description) %>
</p>
<p>
<b>Duration:</b>
<%= @task.duration %>
</p>
<p>
<b>Due date:</b>
<%= @task.due_date %>
</p>
<p>
<b>Complete:</b>
<%= @task.complete %>
</p>
<%= link_to 'Edit', edit_task_path(@task) %> |
<%= link_to 'Back', tasks_path %>
Save the file.
When Rails creates a web page, it do esn't simply insert the value o f the task's descriptio n. Instead, it passes the
descriptio n to the sim ple _f o rm at helper functio n, which creates a fo rmatted versio n o f the descriptio n to insert into
the web page.
To see what the co de change do es, o pen (o r switch back to ) a bro wser windo w at ht t p://loginid.o re illyst ude nt .co m /o st app/t asks/ and then click the Sho w link next to a task that co ntains line breaks in the
descriptio ns:
The line breaks are no w preserved and appear when yo u view the task. To see what happened in the HTML, view the
so urce o f the web page:
The sim ple _f o rm at helper was given the @ t ask.de script io n co ntaining line breaks, and it created a new piece o f
text that wrapped each o f the lines in the descriptio n with <p>...</p> tags. This preserves the line breaks when the
descriptio n is viewed in a bro wser.
The sim ple _f o rm at helper demo nstrates the basic behavio r o f all Ruby functio ns. When yo u call (execute) the
functio n, yo u pass an argument to it in the fo rm o f the task descriptio n. The helper functio n pro cesses this argument
and creates a return value, which in this case is a newly fo rmatted versio n o f the descriptio n. Yo u'll see thro ugho ut this
co urse that mo st Ruby co de is written as functio ns.
While we're talking abo ut functio ns, let's talk abo ut fo rmatting to o . In the co de abo ve, the argument to the
sim ple _f o rm at functio n is wrapped in parentheses. That's a go o d way to separate a functio n name fro m its
argument. Still, so me Ruby pro grammers o pt to call functio ns in poetry style—which means they leave o ut the
parentheses:
OBSERVE:
...
<%= simple_format @task.description %>
...
So me co ders prefer this fo rmat because it makes simple functio n calls easier to read. Other pro grammers prefer
parentheses because it makes the co de mo re explicit.
Which do yo u prefer? Can yo u think o f any pro blems with po etry-style co ding? Try o ut po etry-style co ding as we
co ntinue thro ugh the lab and yo u can decide fo r yo urself whether yo u like it.
In either case, yo u sho uld be consistent in yo ur co ding and stick with o ne o r the o ther.
What Just Happened?
We identified which template file to change.
We used the sim ple _f o rm at helper functio n to preserve line breaks.
We lo o ked at po etry-style co de fo rmatting.
Functions T hat T ake Multiple Arguments
What if yo u want to display ho w much time is left until a task is due? The sho w.ht m l.e rb template file currently
displays the @ t ask.due _dat e ; the amo unt o f time left fo r a task is just the difference between the due date and the
current date. Yo u kno w ho w to get the value o f the current time—use the Ruby expressio n T im e .no w. Ho w abo ut the
current date? Yo u can do that in Ruby with Dat e .t o day. To display the difference between the due date fo r the task and
to day, edit the sho w.ht m l.e rb file and subtract o ne date fro m the o ther:
CODE TO TYPE:
<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
<%= @task.name %>
</p>
<p>
<b>Description:</b>
<%= simple_format(@task.description) %>
</p>
<p>
<b>Duration:</b>
<%= @task.duration %>
</p>
<p>
<b>Due date:</b>
<%= @task.due_date %>
</p>
<p>
<b>Time remaining:</b>
<%= @task.due_date - Date.today %> days
</p>
<p>
<b>Complete:</b>
<%= @task.complete %>
</p>
<%= link_to 'Edit', edit_task_path(@task) %> |
<%= link_to 'Back', tasks_path %>
Save the sho w.ht m l.e rb file and o pen o r refresh the web bro wser at ht t p://loginid.o re illyst ude nt .co m /o st app/t asks. The Ruby expressio n @ t ask.due _dat e - Dat e .t o day calculates the
number o f days between the due date and to day.
If there is a large number o f days remaining, wo uldn't it be better to display so me mo re descriptive amo unt o f time, fo r
example, measuring the time in mo nths o r years?
It wo uld take so me pretty co mplex pro gramming to switch between days, mo nths, and years. Fo rtunately, Ruby has a
date helper function that is designed specifically to translate time differences into English phrases:
The dist ance _o f _t im e _in_wo rds helper, co nverts the difference between dates/times into a text descriptio n. That
means the dist ance _o f _t im e _in_wo rds must be given two arguments—in this case, the current date and the due
date. To pass multiple arguments to a functio n in Ruby, yo u need to separate them with co mmas:
The number o f arguments that yo u pro vide and the o rder they co me in, depends o n the functio n yo u're calling. Add the
date helper functio n to the sho w.ht m l.e rb file like this:
CODE TO TYPE:
<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
<%= @task.name %>
</p>
<p>
<b>Description:</b>
<%= simple_format(@task.description) %>
</p>
<p>
<b>Duration:</b>
<%= @task.duration %>
</p>
<p>
<b>Due date:</b>
<%= @task.due_date %>
</p>
<p>
<b>Time remaining:</b>
<%= @task.due_date - Date.today %> days
<%= distance_of_time_in_words(Date.today, @task.due_date) %>
</p>
<p>
<b>Complete:</b>
<%= @task.complete %>
</p>
<%= link_to 'Edit', edit_task_path(@task) %> |
<%= link_to 'Back', tasks_path %>
The dist ance _o f _t im e _in_wo rds helper functio n describes the abso lute difference between two po ints in time, so
in this particular case it do esn't matter if yo u type put Dat e .t o day o r @ t ask.due _dat e first. In general tho ugh, the
o rder o f the arguments can be impo rtant. The functio n uses the o rder to decide the meaning o f each argument.
Save yo ur changes and then refresh the page that's displaying the details o f the task:
Ruby makes a distinctio n between date values and what it calls date-time values. A date-time value is a co mbinatio n o f
a date and a time. When yo u insert the T im e .no w value into a web page, that's a date-time value:
The expressio n Dat e .t o day is a simple date value—it do esn't include a time o f day. Ruby wo n't let yo u subtract a date
fro m a date-time, o r vice-versa. That's why we calculated the time remaining o n a task manually, using the
Dat e .t o day value:
If we'd tried to subtract T im e .no w fro m the due _dat e , Ruby wo uld have displayed an erro r:
But the dist ance _o f _t im e _in_wo rds helper can calculate the difference between any two date-time values, so yo u
can measure the distance to @ t ask.due _dat e fro m either T im e .no w o r Dat e .t o day:
Alternative Helper: time_ago_in_words
There's a functio n similar to dist ance _o f _t im e _in_wo rds named t im e _ago _in_wo rds—it also
describes a perio d o f time in wo rds, but takes o nly a single date o r time value and it measures the distance
between the given value and no w:
What Just Happened?
We calculated the number o f days between dates using subtractio n.
We used a helper functio n to describe time difference in wo rds.
We passed multiple arguments to the helper functio n separated by co mmas.
We discussed the t im e _ago _in_wo rds helper to calculate time fro m no w.
Functions with Options
The task duratio n is measured in minutes, but if yo u want to display it in ho urs, we can do that—in the sho w.ht m l.e rb
file, yo u just divide the value by 6 0 .0 :
CODE TO TYPE:
<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
<%= @task.name %>
</p>
<p>
<b>Description:</b>
<%= simple_format(@task.description) %>
</p>
<p>
<b>Duration:</b>
<%= @task.duration %>
<%= @task.duration / 60.0 %> hours
</p>
<p>
<b>Due date:</b>
<%= @task.due_date %>
</p>
<p>
<b>Time remaining:</b>
<%= distance_of_time_in_words(Date.today, @task.due_date) %>
</p>
<p>
<b>Complete:</b>
<%= @task.complete %>
</p>
<%= link_to 'Edit', edit_task_path(@task) %> |
<%= link_to 'Back', tasks_path %>
Save the sho w.ht m l.e rb file and then refresh the bro wser windo w that's displaying a task:
Note
We divide the duratio n by 6 0 .0 rather than 6 0 to make sure Ruby will include fractio ns in the result. If yo u
divide two who le numbers in Ruby, the result will always be ro unded to a who le number.
Still, there's a pro blem with simply dividing the duratio n by 6 0 .0 —the result co ntains way to o many decimal places.
Yo u'll need to fo rmat the number in so me way. Rails has a helper functio n to deal with this: the
num be r_wit h_pre cisio n functio n. Yo u might want to specify:
the number o f decimal places.
the o verall number o f significant figures.
the separator characters between the who le number and fractio nal parts.
All o f these different o ptio ns will need to be specified as functio n arguments, but ho w do yo u pass so me arguments to
a functio n and no t o thers?
The designer o f Ruby go t aro und this pro blem with function options. Optio ns are similar to no rmal arguments except
that:
o ptio ns are passed to the functio n after all o ther arguments.
o ptio ns are passed with nam e s.
Here's ho w yo u wo uld fo rmat a number to include 5 decimal places and use the co mma (",") character to separate the
who le numbers fro m the fractio ns:
Each o f the o ptio ns is listed with a name and a value separated by the characters =>. This is called the hash ro cke t
by Ruby pro grammers. Hash because a list o f named values in this fo rmat is called a hash map, and rocket because
=> lo o ks a little like a sideways ro cket.
Optio n names begin with a co lo n (:) character. A co lo n fo llo wed by a series o f characters is called a sym bo l in Ruby.
A symbo l is kind o f like a string. Symbo ls are used to sto re names in Ruby, so o ptio ns are listed with symbo ls.
Here are so me mo re examples o f the num be r_wit h_pre cisio n helper:
Funct io n Call
number_with_precisio n(1.16 6 6 6 6 6 7, :precisio n => 5, :separato r => ',')
number_with_precisio n(1.16 6 6 6 6 6 7, :precisio n => 3)
number_with_precisio n(1.16 6 6 6 6 6 7, :significant=>true, :precisio n => 3)
number_with_precisio n(456 78 9 0 .0 , :delimiter=>' ', :precisio n=>6 )
Re t urns
1,16 6 6 7
1.16 7
1.17
4 56 7 8 9 0 .0 0 0 0 0 0
So if yo u want to display the duratio n in ho urs to 2 decimal places, mo dify the sho w.ht m l.e rb file as sho wn:
CODE TO TYPE:
<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
<%= @task.name %>
</p>
<p>
<b>Description:</b>
<%= simple_format(@task.description) %>
</p>
<p>
<b>Duration:</b>
<%= @task.duration %>
<%= number_with_precision(@task.duration / 60.0, :precision => 2) %> hours
</p>
<p>
<b>Due date:</b>
<%= @task.due_date %>
</p>
<p>
<b>Time remaining:</b>
<%= distance_of_time_in_words(Date.today, @task.due_date) %>
</p>
<p>
<b>Complete:</b>
<%= @task.complete %>
</p>
<%= link_to 'Edit', edit_task_path(@task) %> |
<%= link_to 'Back', tasks_path %>
The call to num be r_wit h_pre cisio n takes the value o f the duratio n / 6 0 .0 , as well as a single :pre cisio n o ptio n.
Save sho w.ht m l.e rb and then refresh the task page, yo u'll see the duratio n fo rmatted to two decimal places:
What Just Happened?
In this lesso n, we learned:
So me functio ns accept options.
An o ptio n is an argument given by name.
Optio ns appear after all the o ther arguments
Optio n names are given as :sym bo ls — which are similar to " st rings"
Optio n names are separated fro m values using "=>"—the hash rocket.
Excellent wo rk so far. Practice what yo u've learned so far as yo u do yo ur ho mewo rk. See yo u in the next
lesso n!
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Adding Field Validation
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
apply rules in yo ur applicatio n.
make yo ur Rails applicatio n check fo r (and prevent) empty data fo r a particular field.
make yo ur app check that entries are o f a particular type (fo r example, numeric).
Sometimes You Need Rules
The existing versio n o f the task applicatio n lets users create, update, and delete tasks, but it do esn't apply any rules to
the task data. Fo r example, suppo se a user clicks the Ne w T ask link o n the fro nt page and sees a new fo rm like this (if
yo u're trying this, enter a value fo r the Duratio n as sho wn; o therwise yo u'll get an erro r message):
No thing prevents them fro m clicking Cre at e T ask and saving the data, even tho ugh mo st o f the fields are blank:
When yo u create an applicatio n that allo ws users to create and update data, yo u usually want to apply rules. Yo u may
want to make sure the user pro vides a descriptio n, yo u pro bably do n't want two different tasks to have the same name,
o r yo u might want the duratio n value to be within the range 1-240 , co nsistently.
In o rder to acco mplish task like these, yo u want the system to validate the data the user enters. So , ho w do yo u specify
validatio n rules, and where wo uld yo u write them in a Rails applicatio n?
Making Fields Mandatory
To begin the pro cess o f validatio n, let's make so me fields mandato ry, so that users can't save a reco rd if any o f tho se
fields are empty. All o f the co de we've mo dified so far has changed the way the applicatio n lo o ks to the user. Co de that
co ntro ls ho w the applicatio n lo o ks is called vie w co de . That's why all o f the page templates that generate the Task
pages are sto red in the app/vie ws fo lder:
Rails adheres to several key design principles; o ne o f them is Convention over Configuration. Instead o f co nfiguring
each applicatio n to wo rk in a different way, Rails apps all use the same co nventio ns. So view co de is always sto red in
the /app/vie ws fo lder, static co ntent is in the /public fo lder, and so o n.
PRINCIPLE:
Convention
over
Conf iguration
Rails applicatio ns have stro ng co nventio ns abo ut where co de sho uld be sto red, so Rails
pro grammers can find the co de they need fast. Rails has co nfiguratio n info rmatio n built into
it, like what the name o f a particular type o f file is go ing to be (e.g.
"Timestamp_create_tasks.rb"), so pro grammers do n't have to tell it that stuff. Fo r example,
yo u do n't have to create a file o r o therwise tell Rails that the view files are in the /app/views
fo lder, because Rails already kno ws!
But what abo ut validatio n co de? Where do es that live?
Validatio n co de checks the data befo re it's sto red in the database. The co de that co ntro ls all o f the data in the
applicatio n is called model co de. The model is the business mo del o f yo ur applicatio n—it indicates what data yo ur
applicatio n has to manage and ho w it will behave. So if yo u were writing Twitter, the model wo uld be the co llectio n o f
tweets. If yo u were writing an o nline game, the mo del wo uld be the co o rdinates o f the buildings and the lo catio ns o f
the players.
Rails sto res all o f the mo del co de in yo ur applicatio n in the /app/m o de ls/ fo lder:
When yo u generated the scaffo lding fo r the task pages, Rails created a file called /app/m o de ls/t ask.rb. Open that
no w by do uble-clicking it in the File Bro wser:
OBSERVE: app/mo dels/task.rb
class Task < ActiveRecord::Base
end
This piece o f Ruby co de is called whenever yo ur applicatio n creates o r reads o r writes a piece o f task data. As yo u can
see, there's no t much to it just yet. What little co de there is just means "Task data is stored in the database". There's no
o ther co de in the t ask.rb file yet because Rails hasn't been to ld to do anything else with task data. The t ask.rb file
do esn't even say anything abo ut the attributes sto red against each task. Rails can wo rk o ut what they are by lo o king
into the database.
If yo u want to validate the task data, yo u need to do that here. Add this line to the co de:
CODE TO TYPE: app/mo dels/task.rb
class Task < ActiveRecord::Base
validates(:description, :presence => true)
end
Make sure yo u type the co de exactly as yo u see it here. This co de is a functio n (like the functio ns we used in the last
lab), but this functio n is run every time the Rails applicatio n accesses task data. The first parameter is the name o f an
attribute o f the task, in this case, the symbo l :de script io n, because we want to validate the descriptio n field. A symbo l
is much like a string; it always begins with a co lo n (:) character. Symbo ls are o ften used fo r the names o f things, like
attributes in Ruby. The seco nd parameter is an o ptio n called :pre se nce . If Rails sees the :pre se nce o ptio n, it checks
fo r the presence o f a value fo r the attribute. Setting the :pre se nce o ptio n to t rue means that the :de script io n
attribute must be present befo re the task can be saved to the database.
Save the task.rb file, o pen a bro wser at ht t p://<lo gin-id>.o re illyst ude nt .co m /o st app/t asks, and click Ne w
T ask. Yo u'll see the New Task fo rm just like befo re:
On the New Task page, leave the Descriptio n field blank (enter a value fo r Duratio n) and click Cre at e T ask. Yo u'll see
this:
Cancel the additio n by clicking Back. On the Listing tasks page, remo ve the no n-named task yo u added earlier by
clicking De st ro y to the right o f it.
Let's Review
The app/m o de ls/t ask.rb sto res the custo m mo del co de fo r Task data. Previo usly, there wasn't much in that
file because there were no rules asso ciated with task data, but we added this line o f co de:
OBSERVE: Yo ur validato r co de
class Task < ActiveRecord::Base
validates(:description, :presence => true)
end
The validat e s co de here means "Check that the task description has a value." We called the validat e s
functio n with two arguments to check that the de script io n value is pre se nt befo re a task is saved.
Note
The validat e s functio n is o ften called in po etry fo rmat, witho ut parentheses.
OBSERVE: The same co de in po etry fo rmat.
class Task < ActiveRecord::Base
validates :description, :presence => true
end
validat e s is a data validator. The validato r checks the data befo re it's saved to the database. If the validato r
says there's a pro blem, Rails sends the page back to the user:
When the user submits the New Task fo rm, Rails calls the co de in the t ask.rb script befo re the data is saved
to the database. If the validatio n co de passes, the data is sto red in the database, but if any o f the validato rs in
t ask.rb fail, Rails sends the fo rm back to the user with the erro rs highlighted.
One o f the benefits o f validating data in the mo del co de is that it will also run when the user updates the data.
So if yo u edit o ne o f yo ur existing tasks and try to blank o ut the Descriptio n, this happens:
Click Back to exit this page witho ut saving any changes.
Rails separates the co de that generates web pages fro m the co de that handles the data. This is o ne o f the key
principles in the design o f Rails applicatio ns. It allo ws yo u to find the piece o f co de yo u need to amend
witho ut much hassle, and yo u get to reuse yo ur co de. If the validatio n co de had been sto red inside a page
template, yo u wo uld have had to create separate validatio n in bo th the New Task page and the Editing Task
page. Instead, because the validatio n is do ne in the mo del co de, and the same mo del co de is shared by any
piece o f co de that writes data to the database, yo u can maintain the co de in just one place.
PRINCIPLE:
DRY
Do n't Repeat Yo urself. Rails was designed so that co de almo st never needs to be
repeated. By separating the validatio n co de fro m the page co de, the New task and
Editing task pages can bo th use the same validatio n witho ut having separate co pies o f
the co de.
Making More T han One Field Mandatory
Yo u can add as many validato rs to the mo del co de as yo u like; to make the nam e and durat io n mandato ry, edit
t ask.rb like this:
CODE TO TYPE: app/mo dels/task.rb
class Task < ActiveRecord::Base
validates :description, :presence => true
validates :name, :presence => true
validates :duration, :presence => true
end
Save the task.rb file, o pen o r refresh the bro wser, and try adding a new task witho ut a name, descriptio n, and duratio n.
Rails allo ws yo u to co mbine several validatio ns to gether into a single line o f co de, like this:
CODE TO TYPE: app/mo dels/task.rb
class Task < ActiveRecord::Base
validates :description, :presence => true
validates :name, :presence => true
validates :duration, :presence => true
validates :name, :description, :duration, :presence => true
end
Note
T ip
All validatio ns are applied when the data is saved, so each o f the messages will appear in the web page.
Ordering o nly affects the o rder o f the erro r messages that will appear. We changed the o rder o f the
attribute names here so the validatio n messages will appear in the same o rder that the fields appear o n
the page.
Any time yo u want to apply the same validatio n to several attributes, just list all o f the attribute names in the
validat e s call.
Unique T ask Names
Yo u've seen ho w to make fields mandato ry, but there are many o ther checks yo u might want to make. Fo r example,
yo u might want each task to have a unique name.
Create a task with the exact same name as o ne o f yo ur existing tasks; fo r example, "Buy eggs" in the example sho wn
here:
When yo u return to Listing tasks, yo u see two tasks with the same name.
It can be co mplex to check fo r duplicate values. Every time yo u save a new task to the database, yo u'd need to read
thro ugh the who le database to see if there's already a task with that name. Yo u'd need to do the same thing each time
so meo ne edits a task. Fo rtunately, Rails helps yo u to check fo r uniqueness—just use the unique ne ss rule:
CODE TO TYPE: app/mo dels/task.rb
class Task < ActiveRecord::Base
validates :name, :description, :duration, :presence => true
validates :name, :uniqueness => true
end
When Rails sees this in the mo del co de it will check fo r uniqueness fo r yo u. If it finds the name has already been used,
it will send the fo rm back with an erro r.
Save the task.rb file, o pen o r refresh the bro wser, edit the seco nd "Buy eggs" task, and click Updat e t ask:
Click Back to cancel this edit, and then click De st ro y to the right o f the seco nd Buy e ggs task to remo ve it.
Checking Numbers
Rails is remarkably fo rgiving. Edit the Mo rning run task and use the number "0 " as the letter "O" in the Duratio n field
as sho wn:
What sho uld Rails do ? The default behavio r is to igno re the no n-numeric characters. That means that if yo u enter 7 O,
Rails treats it as 7 .
Rails is designed to be fo rgiving so that the applicatio ns are easier to use o n the internet. That's great, bu what if yo u
want to be mo re strict? So metimes a numeric value is really impo rtant, and if the user enters the letter O instead o f the
number 0 , yo u do n't want to just ignore it; yo u want to actively reject it. That's when yo u'll use
validat e s_num e ricalit y_o f . It checks to see if a field co ntains a number; if it do esn't it wo n't let the user save the
reco rd. Mo dify /app/m o de ls/t ask.rb as sho wn:
CODE TO TYPE:
class Task < ActiveRecord::Base
validates :name, :description, :duration, :presence => true
validates :name, :uniqueness => true
validates_numericality_of :duration
end
Save the task.rb file, o pen o r refresh the bro wser. Try changing the Duratio n fo r Mo rning run to 7 O (with the letter
"O") again.
Fix the Duratio n by changing it back to 7 0 , and click Updat e T ask. No w the Duratio n is back to 1.17 ho urs.
WARNING
validat e s_num e ricalit y_o f checks that an attribute co ntains a valid number, but it also means
that the attribute cannot be blank. Keep in mind that every time yo u say that an attribute is numeric,
yo u are implicitly also saying that it's required.
Once yo u kno w a field co ntains a number, there may be so me o ther checks yo u'd like to make as well. Fo r example,
the duratio n o f a task is supposed to be a number between 1 and 24 0 , but there's no thing in the system that prevents
the user fro m typing so mething else. In the Mo rning run task, change the Duratio n to -120 as sho wn:
Click Updat e T ask. Rails kno ws that durat io n is mandato ry and numeric, but we haven't to ld it anything abo ut the
kinds o f values it can take. In the same way that we co uld pass a rule to the validat e s metho d, we can pass a rule to
validat e s_num e ricalit y_o f :
CODE TO TYPE: app/mo dels/task.rb
class Task < ActiveRecord::Base
validates :name, :description, :duration, :presence => true
validates :name, :uniqueness => true
validates_numericality_of :duration, :greater_than_or_equal_to => 1
end
This co de change ensures that durat io n is a number and it's >= 0 . Save yo ur co de and see what happens if yo u try to
enter -120 again:
Change the Duratio n back to 70 and save the task.
There are seven rules available in the versio n o f Rails yo u're using, and mo re will pro bably be added. Fo r no w, yo u
can use these rules:
Rule
Me ans...
:greater_than => x
Must be > x
:greater_than_o r_equal_to => x Must be >= x
:equal_to => x
Must be == x
:less_than => x
Must be < x
:less_than_o r_equal_to => x
Must be <= x
:o dd => true
Must be o dd
:even => true
Must be even
What if yo u need to apply mo re than o ne rule at a time? Duratio n sho uld be in the range 1..24 0 , so yo u could write it
like this:
OBSERVE: One way o f applying mo re than o ne rule
class Task < ActiveRecord::Base
validates :name, :description, :duration, :presence => true
validates :name, :uniqueness => true
validates_numericality_of :duration, :greater_than_or_equal_to => 1
validates_numericality_of :duration, :less_than_or_equal_to => 240
end
But, like with o ur presence validatio n, there's a sho rter fo rmat yo u can use. Mo dify t ask.rb as sho wn:
CODE TO TYPE:
class Task < ActiveRecord::Base
validates :name, :description, :duration, :presence => true
validates :name, :uniqueness => true
validates_numericality_of :duration, {:greater_than_or_equal_to => 1, :less_than_or_e
qual_to => 240}
end
Save it, o pen o r refresh the bro wser, and test the new rules.
What Just Happened?
Yo u learnd that by default, Rails do esn't co mplain if yo u enter a no n-number into a numeric field.
Yo u learned that validat e s_num e ricalit y_o f checks to see whether an attribute is numeric.
Yo u can apply additio nal rules by adding o ptio ns to the functio n.
Yo u're o n yo ur way. Let's keep go ing! After yo u wo rk thro ugh the ho mewo rk, I'll meet yo u in the next lesso n...
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Adding Custom Ruby Code
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
create and use built-in and custo m validatio n functio ns.
run Ruby co de o n the Rails co nso le.
assign values to variables and use bo o lean expressio ns.
use Ruby lo gic inside a web page.
Making Your App Smarter with Ruby
Rails can cut do wn o n yo ur develo pment time by generating a lo t o f co de fo r yo u, but yo u'll likely want to change the
way the generated applicatio n lo o ks and behaves. Custo m co de can be added to every part o f a Rails applicatio n—
fro m the co de that talks to the database, to the co de that sends web pages back to the user. We've already lo o ked at
ways o f using Ruby to call built-in validatio ns. No w let's see ho w we can write o ur o wn validatio ns using Ruby.
Writing Custom Validation
Rails has a number o f built-in validato rs that can check the values that users enter o n a web page. We've seen that yo u
can check to see that a value is present, o r unique, o r a number in a certain range. So ho w do yo u perfo rm validatio n
that isn't co vered by any o f the standard validato rs? Yo u'll create yo ur o wn custo m validatio n in Ruby co de.
Let's lo o k at an example. Create this task:
Click Cre at e T ask:
It do esn't make sense to create a brand new task that's due so me time in the past, so let's write a validato r to prevent
that fro m happening.
All o f the existing validatio n is do ne in the t ask.rb file. This is the model co de that co ntro ls ho w data is written to and
read fro m the database, so this is where we'll add o ur custo m validatio n co de. Mo dify /app/m o de ls/t ask.rb as
sho wn:
CODE TO TYPE: app/mo dels/task.rb
class Task < ActiveRecord::Base
validates :name, :description, :duration, :presence => true
validates :name, :uniqueness => true
validates_numericality_of :duration, {:greater_than_or_equal_to => 1, :less_than_or_e
qual_to => 240}
validate :due_in_future
def due_in_future
if due_date < Date.today then
errors.add(:due_date, "cannot be in the past")
end
end
end
Save t ask.rb, then o pen o r refresh yo ur separate bro wser windo w at ht t p://loginid.o re illyst ude nt .co m /o st app/t asks and try to create a task with a due date in the past:
The custo m validatio n co de yo u just wro te prevents the new task fro m being saved. No w change the due date to
No vember 6 , 20 14 and create the task to see ho w that co de wo rks.
The first line we entered specified a custom validation function. A functio n is just a piece o f co de that yo u can run by
name. In this case, we gave validat e the name o f a functio n called due _in_f ut ure . Name-values are given as
symbols, so :due _in_f ut ure begins with a co lo n (:). Calling validat e in the t ask.rb file like this tells Rails that the
due _in_f ut ure functio n needs to be called whenever a T ask o bject is go ing to be saved o r updated in the database.
WARNING
Be careful no t to co nfuse "validate" with "validates". "validates" is used to call built-in validatio ns,
but "validate" is used fo r yo ur o wn custo m validatio ns.
Yo ur custo m due _in_f ut ure functio n will be run alo ngside all o f the o ther validato rs that yo u've created. The
due _in_f ut ure functio n checks to see that the due date is set in the future; o therwise, it creates an erro r message.
Validations Fail if T hey Create a Message
So ho w do es Rails kno w if a T ask failed validatio n? It lo o ks to see if any erro r messages were created.
There might be erro rs fro m built-in validato rs o r fro m due _in_f ut ure ; either way, if there's even o ne erro r
message, the user is sent back to the fo rm to co rrect the mistake.
Let's lo o k a little clo ser at the due _in_f ut ure functio n. The functio n is defined between the de f and e nd
keywo rds. These two wo rds mark the beginning and end o f the functio n. The name o f the functio n
—due _in_f ut ure —fo llo ws the de f keywo rd; after that yo u have the co de o f the functio n itself.
The functio n perfo rms a t e st —it co mpares the task's due date with to day's date. If the due date is in the past,
the functio n creates a new erro r message that displays next to the due _dat e field o n the page. By creating
this message, the validatio n functio n will prevent the T ask being sto red in the database and Rails will return
the page o f erro rs to the user.
A Little More Ruby
The due _in_f ut ure functio n co ntains a little mo re Ruby co de than we've seen befo re, and it's wo rth spending so me
time lo o king at ho w Ruby uses functio ns and tests.
When we lo o ked at Ruby expressio ns earlier, we ran a pro gram called Interactive Ruby (irb). Interactive Ruby is a great
way to try small pieces o f pure Ruby co de. But there's a pro blem with using the basic versio n o f irb when yo u want to
run Rails co de: so me Ruby expressio ns rely o n extra libraries that are lo aded specifically by Rails. A library is a set o f
pre-written co de that allo ws yo u to execute tasks like manage dates o r talk to a database. Fo r example, start irb and
then try to print o ut to day's date:
INTERACTIVE SESSION:
irb(main):001:0> Date.today
NameError: uninitialized constant Date
from (irb):1
from :0
irb(main):002:0> quit
cold1:~$
Yo ur Rails applicatio n can use dat e s, but the irb pro gram do esn't understand dates. Yo u'll need to address that if yo u
ever want to run co de fro m yo ur applicatio n. Yo u can't use the irb to o l, instead, yo u'll need an augmented versio n o f
irb called the Rails console. The Rails co nso le is a co py o f irb that can see all o f the co de that's available to yo ur
applicatio n.
To start the co nso le, o pen a shell, change to the ~/railsapps/o st app directo ry, and type in rails co nso le :
INTERACTIVE SESSION:
cold1:~$ cd railsapps/ostapp
cold1:~/railsapps/ostapp$ rails console
Loading development environment (Rails 3.0.3)
Yo u need to be in the same directo ry as yo ur applicatio n so that the Rails co nso le can lo ad the co de and libraries
fro m yo ur applicatio n. Reading all o f that co de may take a while, so it might take a few seco nds befo re the Rails
co nso le is up and running.
Once yo u're in the Rails co nso le, try printing to day's date again:
INTERACTIVE SESSION:
irb(main):001:0> Date.today
=> Tue, 12 Feb 2013
irb(main):002:0>
This time, Ruby understands the expressio n Dat e .t o day, because it has access to the relevant date co de inside
Rails. The Dat e co de is in a Rails library and the Rails co nso le lo aded that library when yo u started the co nso le.
No w let's try to create a new date fro m a string:
INTERACTIVE SESSION:
irb(main):002:0> Date.parse('2015-10-31')
=> Sat, 31 Oct 2015
irb(main):003:0>
By calling Dat e .parse and passing it a string in the fo rmat year-month-day, yo u can create a new Dat e o bject. When
yo u fill in a date field in a Rails web page and then submit it to the server, Rails co nverts the text yo u entered into a
Dat e value.
Like any piece o f data, yo u can sto re the Dat e in a variable, like this:
INTERACTIVE SESSION:
irb(main):003:0> due_date = Date.parse('2015-10-31')
=> Sat, 31 Oct 2015
irb(main):004:0>
The due _dat e variable no w co ntains the newly created date. We refer to sto ring data this way as assigning it to a
variable. Once the value is assigned to the variable, yo u can retrieve it by evaluating the variable:
INTERACTIVE SESSION:
irb(main):004:0> due_date
=> Sat, 31 Oct 2015
irb(main):005:0>
The value o f due _dat e is displayed in the co nso le right after the => symbo l (which means "has the value").
Take a lo o k at the current date:
INTERACTIVE SESSION:
irb(main):005:0> Date.today
=> Tue, 12 Feb 2013
irb(main):006:0>
The value yo u get here depends o n the date yo u run the co de. To day's date and the new date yo u created bo th have
the same fo rmat, because they are bo th the same kind o f data; they have the same type.
Yo u can co mpare Dat e s against each o ther. Fo r example, if yo u want to see whether the value in the due _dat e
variable is o lder than to day's date, yo u can do this:
INTERACTIVE SESSION:
irb(main):006:0> due_date < Date.today
=> false
irb(main):007:0>
When yo u're co mparing dates, the < o perato r means is before. So o ur expressio n means due_date is before today. In
this case, the expressio n has the value f alse .
Expressio ns that have the values t rue o r f alse are called boolean expressions. Bo o lean expressio ns are useful
because they help Rails make decisio ns. Fo r example, if yo u want to display a message o n the co nso le, yo u call the
put s functio n:
INTERACTIVE SESSION:
irb(main):007:0> puts("I am a message on the console")
I am a message on the console
=> nil
irb(main):008:0>
So , what if yo u want to display a message o n the co nso le only if so me co nditio n is true? Yo u co uld do it like this:
INTERACTIVE SESSION:
irb(main):008:0> if due_date > Date.today then
irb(main):009:1*
puts("Due date is in the future")
irb(main):010:1> end
Due date is in the future
=> nil
irb(main):011:0>
Unlike o ther pieces o f co de yo u've typed so far, this co de takes several lines and Ruby wo n't run the co de until yo u've
typed in all o f it. This is an if statement. An if statement checks the value o f so me expressio n; if that expressio n has
the value t rue , Ruby runs the co de between the line beginning if ... and the line e nd.
Note
The co nso le displayed an asterisk (*) o n the line fo llo wing the if ... line. The Rails co nso le kno ws when
yo u're inside a multi-line co mmand like if ; the * tells yo u that yo u've started a multi-line sectio n o f co de,
and the co nso le will wait until the co mmand is typed in befo re running it. In this case, the co nso le didn't
run the if ... co mmand until it saw the e nd statement that co mpleted it.
The if statement tells the co mputer to run so mething if a co nditio n is true, but what if yo u want the pro gram to do
so mething else if the co nditio n is false? The e lse keywo rd allo ws yo u to specify an alternative, like this:
INTERACTIVE SESSION:
irb(main):011:0> if due_date < Date.today then
irb(main):012:1*
puts("Due date is in the past")
irb(main):013:1> else
irb(main):014:1*
puts("Due date is not in the past")
irb(main):015:1>
puts("That's good")
irb(main):016:1> end
Due date is not in the past
That's good
=> nil
irb(main):017:0>
This co de will still display a message if the due _dat e value is in the past, but if the due _dat e is not in the past, it will
run the co de between the e lse and e nd lines and display a different set o f messages.
If yo u've used o ther languages like PHP, JavaScript, o r Java, if statements will be familiar to yo u. Ruby do es have a
few variatio ns o n the if statement tho ugh. Fo r example, Ruby has an unle ss statement that do es the exact opposite o f
the if statement; unle ss will run co de o nly if the expressio n it is given has the value f alse :
INTERACTIVE SESSION:
irb(main):017:0> unless due_date <= Date.today then
irb(main):018:1*
puts("Date is in the future")
irb(main):019:1> end
Date is in the future
=> nil
irb(main):020:0>
Yo u may cho o se to write a particular piece o f lo gic using an if o r an unle ss—but make sure yo u understand ho w
each wo rks.
Ano ther feature o f Ruby is the one-line-if. If yo u just have a single line o f co de that yo u want to run based o n a
co nditio n, yo u can write it o n a single line, like this:
INTERACTIVE SESSION:
irb(main):021:0> puts("Date is in the future") if due_date > Date.today
Date is in the future
=> nil
irb(main):022:0>
This co de can read a little like it was written by Yo da fro m the mo vie, Star Wars. The if co nditio n is at the end o f the line.
Ruby co ders no rmally write o ne-line-ifs if the expressio n is expected to have the value t rue mo st o f the time. Yo u can
do the same with the unle ss statement, so yo u might see Ruby co de that lo o ks like this:
Ruby pro grammers will switch between if s, e lse s and o ne-liners, just as yo u switch between sentence fo rms in
everyday speech. Ruby co de is meant to be expressive, which means that no t o nly is it meant to run, it's meant to be
read. A go o d Ruby pro grammer kno ws what her co de will do , and how to express it.
Okay, no w let's lo o k at functio ns. Functio ns are pieces o f co de with names. Here's ho w yo u create a functio n in Ruby:
INTERACTIVE SESSION:
irb(main):022:0> def happy_halloween
irb(main):023:1>
puts("Happy Horrid Halloweeeeen!!!")
irb(main):024:1> end
=> nil
irb(main):025:0>
This creates a functio n named happy_hallo we e n. The body o f the functio n (the co de it co ntains) is between the de f
and e nd keywo rds. When yo u type in a functio n, the co de it co ntains is no t run right away—it's sto red in memo ry.
Functio ns take several lines o f co de to create, but unlike if and unle ss co de, the co nso le wo n't display * characters at
the start o f each line.
The co de o f the functio n is indented with a co uple spaces. We did that with the co de inside the if and unle ss
statements abo ve as well. Yo u indent co de in Ruby to make it mo re readable; it's up to yo u to decide ho w many
spaces o r T abs to use.
Functio ns are a way o f extending the Ruby language. Creating a functio n is like creating a new Ruby co mmand. If yo u
want to run the co de inside the functio n, yo u just need to type its name:
INTERACTIVE SESSION:
irb(main):025:0> happy_halloween
Happy Horrid Halloweeeeen!!!
=> nil
irb(main):026:0>
Once yo u've created a functio n, yo u can call it as many times as yo u want witho ut having to type the functio n's co de
each time:
INTERACTIVE SESSION:
irb(main):026:0> happy_halloween
Happy Horrid Halloweeeeen!!!
=> nil
irb(main):027:0> happy_halloween
Happy Horrid Halloweeeeen!!!
=> nil
irb(main):028:0>
Finally, yo u can leave the Rails co nso le by typing quit :
INTERACTIVE SESSION:
irb(main):028:0> quit
cold1:~/railsapps/ostapp$
The Rails co nso le sho ws yo u the wo rld as Rails sees it. Every time Rails generates a web page, o r reads a fo rm, o r
saves so mething to the database, it's running pieces o f Ruby co de.
OK—no w that we've lo o ked in mo re depth at ho w the Ruby validatio n co de wo rks, let's get back to the applicatio n.
Validate Every T ime?
The custo m validatio n will prevent users fro m saving a task with a due date in the past.
It also makes perfect sense to prevent peo ple creating tasks that are due in the past.
No w suppo se a user wants to edit a task. Is it po ssible that a user might submit a task with an o ld due date? Fo r
example, what if a user wants to mark an o ld task as co mplete:
In that case yo u'd want Rails to run yo ur custo m validatio n when the task is being created, but no t when it's being
updated.
Take a lo o k at the existing t ask.rb file. Where do yo u think Rails wo uld allo w yo u the option to run a validatio n o nly at
creatio n time?
OBSERVE: app/mo dels/task.rb
class Task < ActiveRecord::Base
validates :name, :description, :duration, :presence => true
validates :name, :uniqueness => true
validates_numericality_of :duration, {:greater_than_or_equal_to => 1, :less_than_or_e
qual_to => 240}
validate :due_in_future
def due_in_future
if due_date < Date.today then
errors.add(:due_date, "cannot be in the past")
end
end
end
Yo u want the validat e line to run o nly when the task is created, so it wo uld be useful if the validat e co mmand had an
o ptio n to specify exactly when it sho uld be run. Sure eno ugh, it do es! The :o n o ptio n specifies which times in the
lifecycle o f an o bject the validatio n sho uld be run:
CODE TO TYPE: app/mo dels/task.rb
class Task < ActiveRecord::Base
validates :name, :description, :duration, :presence => true
validates :name, :uniqueness => true
validates_numericality_of :duration, {:greater_than_or_equal_to => 1, :less_than_or_e
qual_to => 240}
validate :due_in_future, :on=>:create
def due_in_future
if due_date < Date.today then
errors.add(:due_date, "cannot be in the past")
end
end
end
In this case, we want to run the validate functio n due _in_f ut ure o nly if we create a new task. We do n't want the
validatio n to run if the user is updating an existing task. So , we set the o ptio nal argument :o n to :cre at e —that tells
Rails to run this functio n o nly when a new task is created. When a user updates a task, the validatio n wo n't run at all.
To create a validatio n functio n that o nly runs when a reco rd is being updated, yo u'd need to set :o n to :updat e . Yo u
can also use the :o n o ptio n with the validat e s functio n and built-in validato rs.
Once yo u've made the amendment to the t ask.rb file, yo u can edit existing tasks with due dates that are in the past.
Hide T ime Remaining?
Did yo u no tice anything strange when yo u saved a task with a due-date in the past? The Time remaining field said the
task wasn't due fo r ano ther two years:
The co de that calculates the time remaining uses the dist ance _o f _t im e _in_wo rds helper:
The dist ance _o f _t im e _in_wo rds helper calculates the difference between two dates o r times, and then co nverts the
result into so mething readable. Here, it's giving the result "almo st 2 years," but the helper do esn't care abo ut the o rder
o f the times it's given. It do esn't tell yo u if the time difference go es into the past o r the future.
The page wo uld make a lo t mo re sense if time remaining was hidden fo r dates in the past. If yo ur Rails applicatio n is
go ing to hide the field, it will need co de to co mpare the current date with the due-date o f the task. That means yo u'll
need to add so me so rt o f co nditio nal if o r unle ss co de to the sho w.ht m l.e rb file. Yo u already have Ruby co de
inside this file:
OBSERVE: app/views/tasks/sho w.html.erb already co ntains Ruby expressio ns
<p id="notice"><%= notice =%></p>
<p>
<b>Name:</b>
<%= @task.name =%>
</p>
<p>
<b>Description:</b>
<%= simple_format(@task.description) =%>
</p>
<p>
<b>Duration:</b>
<%= number_with_precision(@task.duration / 60.0, :precision => 2) =%> hours
</p>
<p>
<b>Due date:</b>
<%= @task.due_date =%>
</p>
<p>
<b>Time remaining:</b>
<%= distance_of_time_in_words(Date.today, @task.due_date) =%>
</p>
<p>
<b>Complete:</b>
<%= @task.complete =%>
</p>
<%= link_to 'Edit', edit_task_path(@task) ==%> |
<%= link_to 'Back', tasks_path =%>
Each o f these Ruby e xpre ssio ns inserts values into the web page that's sent back to the user. If we want to add lo gic
to the web page, it's a little different fro m simply inserting ano ther Ruby value. Let's lo o k a little clo ser at what happens
when Rails generates a web page fro m a template file.
<%= ... %> Contains a VALUE
When Rails reads a template file, it replaces each o f the Ruby expressio ns surro unded by <% =...% > with the
value o f the Ruby expressio n:
Rails do es this by co nverting the template file into an intermediate Ruby script. Each o f the <% =...% >
expressio ns and each piece o f static HTML is replaced by a co mmand that will generate o utput to the user's
web bro wser. It's as if Rails co nverts the template file into a series o f put s() co mmands:
In reality, Rails do esn't use the put s co mmand—it generates co de to send the o utput o ver the netwo rk to the
user's bro wser—but the principle is the same.
When the intermediate script is run, the o utput fro m the script is sent to the web bro wser. If yo u want yo ur web
page to sho w o r hide a sectio n based o n a Ruby co nditio n, yo u need the intermediate script to co ntain Ruby
lo gic, like this:
<% ... %> Contains LOGIC
This co de is mo re than a series o f o utput statements; it co ntains actual Ruby lo gic.
The <%= ... %> no tatio n is great if yo u want to insert a value into a page, but if yo u want to add so me page
lo gic—fo r example, if yo u want to switch an entire sectio n o f a page o n o r o ff—yo u need a different kind o f
no tatio n.
The <%= ... %> markers specify a value that will be inserted into the web page, but to add lo gic yo u need to
use markers that lo o k like this:
If Rails sees a marker that begins with <% without the equals sign (=), it kno ws that marker co ntains logic
rather than a value. To see ho w this wo rks, let's use <% ... %> to hide the Time remaining info rmatio n in
app/vie ws/t asks/sho w.ht m l.e rb:
CODE TO TYPE:
<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
<%= @task.name %>
</p>
<p>
<b>Description:</b>
<%= simple_format(@task.description) %>
</p>
<p>
<b>Duration:</b>
<%= number_with_precision(@task.duration / 60.0, :precision => 2) %> hours
</p>
<p>
<b>Due date:</b>
<%= @task.due_date %>
</p>
<% unless @task.due_date < Date.today then %>
<p>
<b>Time remaining:</b>
<%= distance_of_time_in_words(Date.today, @task.due_date) %>
</p>
<% end %>
<p>
<b>Complete:</b>
<%= @task.complete %>
</p>
<%= link_to 'Edit', edit_task_path(@task) %> |
<%= link_to 'Back', tasks_path %>
Save sho w.ht m l.e rb and then lo o k at so me tasks with a bro wser. If the task's due date is in the past, the
time remaining is hidden; o therwise, it's visible:
What Just Happened?
We co vered a lo t o f gro und in this lesso n!
We learned:
So metimes yo u need to create yo ur o wn custo m validatio n.
Yo u can create a custo m validatio n functio n inside yo ur mo del co de.
Built-in validato rs are specified by calling the validat e s functio n.
Custo m validatio n can be specified by calling the validat e functio n.
The validat e functio n needs to be given the name o f yo ur functio n as a :symbol.
Yo u can run Ruby co de o n the Rails co nso le.
The Rails co nso le is just a co py o f Interactive Ruby (irb) that can see the co de inside yo ur
applicatio n.
Dat e s can be created with Dat e .parse (" 20 10 -10 -31" ).
Yo u can assign values to variables with =.
So me expressio ns have true/false values. These are called boolean expressio ns.
Bo o lean expressio ns can run co de co nditio nally with the if and unle ss statements.
Ruby has vario us ways o f writing if and unle ss co de.
The put s functio n displays co de o n the co nso le.
The :o n o ptio n allo ws yo u to specify when a (built-in/custo m) validato r will run.
To use Ruby lo gic inside a web page, use the <% ...% > markers.
Put yo ur new skills to wo rk in the ho mewo rk assignment and I'll see yo u sho rtly!
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Custom Database Queries
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
read single reco rds fro m the database.
search thro ugh the database to find all the reco rds that satisfy a set o f co nditio ns.
Asking New Questions
Let's get started by adding these tasks to yo ur database:
nam e
de script io n
durat io n
due dat e
co m ple t e
Write paper
Henry Kno x and the Siege o f Bo sto n 240
20 14-No v-0 6 true
Buy turkey
Do n't fo rget the cranberries.
60
20 14-No v-27 false
Library study Check that the wifi's wo rking
90
20 14-No v-0 6 true
Mo vie night
140
20 14-No v-12 false
Chinato wn
Once yo u have co llected a decent set o f task data, there are questio ns yo u may want to ask:
There are so me questio ns that the scaffo lded co de will never answer. In mo st applicatio ns that yo u create, yo u'll need
to create custo m database co de. If yo u kno w ho w to talk to the database, yo u can create features that will let users get
mo re o ut o f yo ur applicatio n.
Rails applicatio ns are database-centric, which means that pretty much everything they do invo lves reading o r writing
data to the database. In the database, everything is sto red in tables. Fo r example, the database sto res all o f the task
data in a table named t asks (yo ur data may be slightly different fro m o ur examples):
Each ro w o f data represents a single task and each co lumn represents an attribute o f that task. In additio n to the
attributes that yo u specified when yo u created the task scaffo lding, there are three attributes that Rails created fo r yo u
auto matically. They are kno wn as the magic attributes and they are:
id: a unique identifier that Rails assigns to every new task.
cre at e d_at : the date and time that the task was created.
updat e d_at : the date and time (if ever) the task was last edited.
Each o f the tables that Rails creates will include these three extra attributes. They help Rails keep track o f the data it's
managing. Fo r example, Rails uses the id attribute to asso ciate a task with a URL. Let's see ho w that wo rks.
Behind the Scenes: How Rails Displays a T ask
When a user submits a request to view a single task, the URL co ntains the id number o f the task to display. Rails
extracts the id number fro m the URL, reads the task fro m the database, and co nverts it into a Ruby value that will then
be displayed by a template:
All o f the task pages in yo ur applicatio n wo rk in a similar way. They read o r write so me value fro m a database and then
generate a web page respo nse. Rails needs to co nvert the database data into Ruby values fo r this to wo rk. The Ruby
values can be passed to the templates to generate the web page that will be returned to the bro wser that made the
request.
No w, if yo u want to add pages that display, fo r example, the tasks due to day, o r statistics abo ut the number o f
co mplete and inco mplete tasks, yo u'll need to kno w ho w to talk to the database in the same way as Rails.
co mplete and inco mplete tasks, yo u'll need to kno w ho w to talk to the database in the same way as Rails.
The Act ive Re co rd library co nverts database data into Ruby data. The scaffo lded co de uses the ActiveReco rd library
every time it needs to do anything with the database. To write custo m database co de, yo u need to kno w ho w to use
ActiveReco rd.
T he ActiveRecord Library T alks to the Database
Rails uses the Act ive Re co rd library to read and write data to the database. It also allo ws Rails to co nvert a single
database ro w in the task table, like this:
...to a piece o f Ruby data like this:
To see ho w this wo rks, o pen a Unix shell, change into the o st app applicatio n directo ry, and start the Rails co nso le:
INTERACTIVE SESSION:
cold1:~$ cd ~/railsapps/ostapp/
cold1:~/railsapps/ostapp$ rails console
Loading development environment (Rails 3.0.3)
irb(main):001:0>
On the co nso le, yo u can access the T ask data by typing its name:
INTERACTIVE SESSION:
irb(main):001:0> Task
=> Task(id: integer, name: string, description: text, duration: integer, due_date: date
, complete: boolean, created_at: datetime, updated_at: datetime)
irb(main):002:0>
It returns the names o f all o f the attributes o f a typical Task o bject. T ask fo und these by co nnecting to the database and
reading the co lumn names and data-types fro m the t asks table. It fo und the table name by co nverting its o wn name—
Task—into the lo wercase plural t asks.
How to Read All the T asks
As well as info rming yo u abo ut the structure o f the table, T ask can read its co ntents:
INTERACTIVE SESSION:
irb(main):003:0> Task.all
=> [#<Task id: 11, name: "Stock up on coffee", description: "Costa Rican medium roast",
duration: 30, due_date: "2014-11-06", complete: true, created_at: "2014-11-04 11:41:00
", updated_at: nil>, #<Task id: 12, name: "Write paper", description: "Henry Knox and t
he Siege of Boston", duration: 240, due_date: "2014-11-06", complete: true, created_at:
"2014-11-06 13:27:00", updated_at: nil>, #<Task id: 18, name: "Buy turkey", descriptio
n: "Don't forget the cranberries.", duration: 60, due_date: "2014-11-27", complete: fal
se, created_at: "2014-11-26 17:54:00", updated_at: nil>, #<Task id: 21, name: "Library
study", description: "Check the wifi's working.", duration: 90, due_date: "2014-11-06",
complete: true, created_at: "2014-11-02 08:36:00", updated_at: nil>, #<Task id: 22, na
me: "Movie night", description: "Chinatown", duration: 120, due_date: "2014-11-12", com
plete: false, created_at: "2014-11-09 11:13:00", updated_at: nil>, #<Task id: 23, name:
"Buy candy", description: "Buy extra candy.\n\nSee if they have any of those jel...",
duration: 45, due_date: "2010-10-31", complete: true, created_at: "2010-10-28 15:04:00"
, updated_at: nil>, #<Task id: 27, name: "Buy eggs", description: "Go to the market and
buy six eggs.\n\nTry to get orga...", duration: 60, due_date: "2014-11-12", complete:
false, created_at: "2014-11-11 12:38:00", updated_at: nil>]
irb(main):004:0>
That's a lo t o f data! The text that appears o n the screen represents a who le list o f T ask o bjects, surro unded by square
brackets and separated by co mmas:
In Ruby, a list o f o bjects like this is called an array. An array allo ws yo u to manage a list o f separate pieces o f data as a
single co llectio n. Ruby arrays can sto re any type o f data; fo r example:
INTERACTIVE SESSION:
irb(main):004:0> birthdays=['June 24th', 'February 1st', 'January 8th']
=> ["June 24th", "February 1st", "January 8th"]
irb(main):005:0> scores=[65,25,7,43,2]
=> [65, 25, 7, 43, 2]
irb(main):006:0>
Reading Single T asks from the Array
Once yo u have an array o f tasks, yo u need so me way to extract the individual tasks. Yo u can access individual items
in Ruby using an inde x. An index is a number that refers to the po sitio n o f an item in an array. Fo r example, to read the
first task fro m the T ask.all array, try this:
INTERACTIVE SESSION:
irb(main):007:0> Task.all[0]
=> #<Task id: 11, name: "Stock up on coffee", description: "Costa Rican medium roast",
duration: 30, due_date: "2014-11-06", complete: true, created_at: "2014-11-04 11:41:00"
, updated_at: nil>
irb(main):008:0>
That's right—the first item in an array has an index o f zero. The seco nd task in the list has index 1:
INTERACTIVE SESSION:
irb(main):008:0> Task.all[1]
=> #<Task id: 12, name: "Write paper", description: "Henry Knox and the Siege of Boston
", duration: 240, due_date: "2014-11-06", complete: true, created_at: "2014-11-06 13:27
:00", updated_at: nil>
irb(main):009:0>
WARNING
Do n't co nfuse the id o f a task o bject with the index o f the task array. The id o f a task o bject is a
unique number that identifies it. The index is its po sitio n in the array.
Indexes begin at zero because an index is actually an o ffset—it reco rds ho w far an item is fro m the start o f the array.
The first item in an array is 0 places fro m the start, the seco nd item is 1 place fro m the start, and so o n.
Yo u can find o ut ho w many tasks are sto red in the database by asking fo r the size o f the array:
INTERACTIVE SESSION:
irb(main):009:0> Task.all.size
=> 7
irb(main):010:0>
Given that yo u can find the size o f an array, and yo u kno w that indexes begin at zero , ho w might yo u display the last
task in the list? Yo u can do it like this:
INTERACTIVE SESSION:
irb(main):010:0> Task.all[Task.all.size - 1]
=> #<Task id: 27, name: "Buy eggs", description: "Go to the market and buy six eggs.\n\
nTry to get orga...", duration: 60, due_date: "2014-11-12", complete: false, created_at
: "2014-11-11 12:38:00", updated_at: nil>
irb(main):011:0>
Because there are 7 tasks sto red in the database, they will be returned with indexes ranging fro m 0 to 6 . The last
element's index—6 —is o ne less than the size o f the array. In any array, the last element's index is size - 1.
That's a co mplex expressio n; there's a much simpler way o f finding the last item in an array:
INTERACTIVE SESSION:
irb(main):011:0> Task.all.last
=> #<Task id: 27, name: "Buy eggs", description: "Go to the market and buy six eggs.\n\
nTry to get orga...", duration: 60, due_date: "2014-11-12", complete: false, created_at
: "2014-11-11 12:38:00", updated_at: nil>
irb(main):012:0>
That's better and much easier to remember. Yo u can find the first item in the array in the same way:
INTERACTIVE SESSION:
irb(main):012:0> Task.all.first
=> #<Task id: 11, name: "Stock up on coffee", description: "Costa Rican medium roast",
duration: 30, due_date: "2014-11-06", complete: true, created_at: "2014-11-04 11:41:00"
, updated_at: nil>
irb(main):013:0>
Whenever yo u're wo rking with a database, yo u need to co nsider performance. When yo u have a small amo unt o f data,
reading fro m the database takes almo st no time, but if yo u have a large amo unt o f data, yo u might find that yo ur
applicatio n spends mo st o f its time talking to the database. In all o f o ur examples, each time we queried T ask.all fro m
the co nso le, it co nnected to the database and asked fo r all o f the data sto red in the t asks table. If that table co ntained a
millio n reco rds, yo u might still be waiting fo r the respo nse! That's the reaso n mo st Rails pro grammers will sto re the
results in a variable befo re lo o king at the co ntents o f the array:
INTERACTIVE SESSION:
irb(main):013:0> @tasks = Task.all
=> [#<Task id: 11, name: "Stock up on coffee", description: "Costa Rican medium roast",
duration: 30, due_date: "2014-11-06", complete: true, created_at: "2014-11-04 11:41:00
", updated_at: nil>, #<Task id: 12, name: "Write paper", description: "Henry Knox and t
he Siege of Boston", duration: 240, due_date: "2014-11-06", complete: true, created_at:
"2014-11-06 13:27:00", updated_at: nil>, #<Task id: 18, name: "Buy turkey", descriptio
n: "Don't forget the cranberries.", duration: 60, due_date: "2014-11-27", complete: fal
se, created_at: "2014-11-26 17:54:00", updated_at: nil>, #<Task id: 21, name: "Library
study", description: "Check the wifi's working.", duration: 90, due_date: "2014-11-06",
complete: true, created_at: "2014-11-02 08:36:00", updated_at: nil>, #<Task id: 22, na
me: "Movie night", description: "Chinatown", duration: 120, due_date: "2014-11-12", com
plete: false, created_at: "2014-11-09 11:13:00", updated_at: nil>, #<Task id: 23, name:
"Buy candy", description: "Buy extra candy.\n\nSee if they have any of those jel...",
duration: 45, due_date: "2010-10-31", complete: true, created_at: "2010-10-28 15:04:00"
, updated_at: nil>, #<Task id: 27, name: "Buy eggs", description: "Go to the market and
buy six eggs.\n\nTry to get orga...", duration: 60, due_date: "2014-11-12", complete:
false, created_at: "2014-11-11 12:38:00", updated_at: nil>]
irb(main):014:0> @tasks.last
=> #<Task id: 27, name: "Buy eggs", description: "Go to the market and buy six eggs.\n\
nTry to get orga...", duration: 60, due_date: "2014-11-12", complete: false, created_at
: "2014-11-11 12:38:00", updated_at: nil>
irb(main):015:0> @tasks.size
=> 7
irb(main):016:0> @tasks[2]
=> #<Task id: 18, name: "Buy turkey", description: "Don't forget the cranberries.", dur
ation: 60, due_date: "2014-11-27", complete: false, created_at: "2014-11-26 17:54:00",
updated_at: nil>
irb(main):017:0>
Rails will read the data fro m the database o nce and sto re it in the @ t asks variable. Then, yo u can study the results in
@ t asks witho ut fo rcing Rails to co nnect to the database each time.
Searching for Data
Yo u may want to search fo r a single task by the value o f o ne o f its attributes. Fo r example, yo u might want to find the
task with the name "Buy eggs". Yo u can do that by using a f inde r:
INTERACTIVE SESSION:
irb(main):017:0> Task.find_by_name("Buy eggs")
=> #<Task id: 27, name: "Buy eggs", description: "Go to the market and buy six eggs.\n\
nTry to get orga...", duration: 60, due_date: "2014-11-12", complete: false, created_at
: "2014-11-11 12:38:00", updated_at: nil>
irb(main):018:0>
A finder is a request to the T ask class that begins with f ind_. All o f the co de that respo nds to a f ind_ request lives in
the ActiveReco rd library. When the T ask class is asked to f ind_by_attribute, it kno ws that yo u are asking it to find the
task with an attribute matching the o ne yo u supply. The value yo u're lo o king fo r is wrapped in parentheses.
Unlike T ask.all, the f ind_by_ finders o nly return a single task—the first task they find that matches the search value:
INTERACTIVE SESSION:
irb(main):018:0> Task.find_by_due_date('2014-11-27')
=> #<Task id: 18, name: "Buy turkey", description: "Don't forget the cranberries.", dur
ation: 60, due_date: "2014-11-27", complete: false, created_at: "2014-11-26 17:54:00",
updated_at: nil>
irb(main):019:0>
INTERACTIVE SESSION:
irb(main):019:0> Task.find_by_description('Chinatown')
=> #<Task id: 22, name: "Movie night", description: "Chinatown", duration: 120, due_dat
e: "2014-11-12", complete: false, created_at: "2014-11-09 11:13:00", updated_at: nil>
irb(main):020:0>
INTERACTIVE SESSION:
irb(main):020:0> Task.find_by_duration(90)
=> #<Task id: 21, name: "Library study", description: "Check the wifi's working.", dura
tion: 90, due_date: "2014-11-06", complete: true, created_at: "2014-11-02 08:36:00", up
dated_at: nil>
irb(main):021:0>
But what if there might be several tasks that match what yo u're lo o king fo r?
Finding All Matches
In so me cases, yo u'll want to find all the tasks—no t just the first o ne—that match particular criteria.
The simple finders will o nly find the first matching reco rd. To find all matches, yo u need f ind_all_by_...:
INTERACTIVE SESSION:
irb(main):021:0> Task.find_all_by_complete(false)
=> [#<Task id: 18, name: "Buy turkey", description: "Don't forget the cranberries.", du
ration: 60, due_date: "2014-11-27", complete: false, created_at: "2014-11-26 17:54:00",
updated_at: nil>,
#<Task id: 22, name: "Movie night", description: "Chinatown", duration: 120, due_date:
"2014-11-12", complete: false, created_at: "2014-11-09 11:13:00", updated_at: nil>,
#<Task id: 27, name: "Buy eggs", description: "Go to the market and buy six eggs.\n\nTr
y to get orga...", duration: 60, due_date: "2014-11-12", complete: false, created_at: "
2014-11-11 12:38:00", updated_at: nil>]
irb(main):022:0>
Note
We fo rmatted the o utput here to make it easier to read and understand. Yo ur o utput in the Rails co nso le
will be fo rmatted differently.
The f ind_all_by_... finders return an array o f results, like Find.all. That means yo u can co unt the results and read
them using an index in the same way:
INTERACTIVE SESSION:
irb(main):022:0> Task.find_all_by_complete(false).size
=> 3
irb(main):023:0> Task.find_all_by_complete(false).first
=> #<Task id: 18, name: "Buy turkey", description: "Don't forget the cranberries.", dur
ation: 60, due_date: "2014-11-27", complete: false, created_at: "2014-11-26 17:54:00",
updated_at: nil>
irb(main):024:0>
INTERACTIVE SESSION:
irb(main):024:0> Task.find_all_by_complete(false).last
=> #<Task id: 27, name: "Buy eggs", description: "Go to the market and buy six eggs.\n\
nTry to get orga...", duration: 60, due_date: "2014-11-12", complete: false, created_at
: "2014-11-11 12:38:00", updated_at: nil>
irb(main):025:0>
INTERACTIVE SESSION:
irb(main):025:0> Task.find_all_by_complete(false)[1]
=> #<Task id: 22, name: "Movie night", description: "Chinatown", duration: 120, due_dat
e: "2014-11-12", complete: false, created_at: "2014-11-09 11:13:00", updated_at: nil>
irb(main):026:0>
Here are so me examples that use f ind_all_by_...:
Which T asks Are Due on November 6th, 2014?
INTERACTIVE SESSION:
irb(main):026:0> Task.find_all_by_due_date('2014-11-06')
=> [#<Task id: 11, name: "Stock up on coffee", description: "Costa Rican medium
roast", duration: 30, due_date: "2014-11-06", complete: true, created_at: "201411-04 11:41:00", updated_at: nil>,
#<Task id: 12, name: "Write paper", description: "Henry Knox and the Siege of Bo
ston", duration: 240, due_date: "2014-11-06", complete: true, created_at: "201411-06 13:27:00", updated_at: nil>,
#<Task id: 21, name: "Library study", description: "Check the wifi's working.",
duration: 90, due_date: "2014-11-06", complete: true, created_at: "2014-11-02 08
:36:00", updated_at: nil>]
irb(main):027:0>
How Many T asks Are an Hour Long?
INTERACTIVE SESSION:
irb(main):027:0> Task.find_all_by_duration(60).size
=> 2
irb(main):028:0>
Sorting Results
The ActiveReco rd co de we've lo o ked at so far has no t to ld the database the order in which it wants the data to appear.
Typically this means that the data is returned fro m the database in the o rder that it was created, so yo u see the o ldest
reco rds first and the newest reco rds last. Still, this is no t guaranteed. Different database systems may o rder their
results differently. If yo u want to specify an o rder, yo u need to pass mo re info rmatio n to the database, like this:
INTERACTIVE SESSION:
irb(main):028:0> Task.find_all_by_complete(false, :order=>'due_date')
=> [#<Task id: 22, name: "Movie night", description: "Chinatown", duration: 120, due_da
te: "2014-11-12", complete: false, created_at: "2014-11-09 11:13:00", updated_at: nil>,
#<Task id: 27, name: "Buy eggs", description: "Go to the market and buy six eggs.\n\nTr
y to get orga...", duration: 60, due_date: "2014-11-12", complete: false, created_at: "
2014-11-11 12:38:00", updated_at: nil>,
#<Task id: 18, name: "Buy turkey", description: "Don't forget the cranberries.", durati
on: 60, due_date: "2014-11-27", complete: false, created_at: "2014-11-26 17:54:00", upd
ated_at: nil>]
irb(main):029:0>
Note
The images here sho w ho w the :o rde r o ptio n changes the o rder in the output array; the o rder in the
database do esn't change.
The :o rde r o ptio n specifies ho w the data will be so rted by the database befo re it's returned to yo ur applicatio n. Yo u
can use it with all o f the finders and with all:
INTERACTIVE SESSION:
irb(main):029:0> Task.all(:order=>'name')
=> [#<Task id: 23, name: "Buy candy", description: "Buy extra candy.\n\nSee if they hav
e any of those jel...", duration: 45, due_date: "2010-10-31", complete: true, created_a
t: "2010-10-28 15:04:00", updated_at: nil>,
#<Task id: 27, name: "Buy eggs", description: "Go to the market and buy six eggs.\n\nTr
y to get orga...", duration: 60, due_date: "2014-11-12", complete: false, created_at: "
2014-11-11 12:38:00", updated_at: nil>,
#<Task id: 18, name: "Buy turkey", description: "Don't forget the cranberries.", durati
on: 60, due_date: "2014-11-27", complete: false, created_at: "2014-11-26 17:54:00", upd
ated_at: nil>,
#<Task id: 21, name: "Library study", description: "Check the wifi's working.", duratio
n: 90, due_date: "2014-11-06", complete: true, created_at: "2014-11-02 08:36:00", updat
ed_at: nil>,
#<Task id: 22, name: "Movie night", description: "Chinatown", duration: 120, due_date:
"2014-11-12", complete: false, created_at: "2014-11-09 11:13:00", updated_at: nil>,
#<Task id: 11, name: "Stock up on coffee", description: "Costa Rican medium roast", dur
ation: 30, due_date: "2014-11-06", complete: true, created_at: "2014-11-04 11:41:00", u
pdated_at: nil>,
#<Task id: 12, name: "Write paper", description: "Henry Knox and the Siege of Boston",
duration: 240, due_date: "2014-11-06", complete: true, created_at: "2014-11-06 13:27:00
", updated_at: nil>]
irb(main):030:0>
To so rt by multiple co lumns, set the :o rde r o ptio n to a co mma-separated list o f attribute names. To so rt first by
durat io n and then by due _dat e , try this:
INTERACTIVE SESSION:
irb(main):030:0> Task.all(:order=>'duration, due_date')
=> [#<Task id: 11, name: "Stock up on coffee", description: "Costa Rican medium roast",
duration: 30, due_date: "2014-11-06", complete: true, created_at: "2014-11-04 11:41:00
", updated_at: nil>,
#<Task id: 23, name: "Buy candy", description: "Buy extra candy.\n\nSee if they have an
y of those jel...", duration: 45, due_date: "2010-10-31", complete: true, created_at: "
2010-10-28 15:04:00", updated_at: nil>,
#<Task id: 27, name: "Buy eggs", description: "Go to the market and buy six eggs.\n\nTr
y to get orga...", duration: 60, due_date: "2014-11-12", complete: false, created_at: "
2014-11-11 12:38:00", updated_at: nil>,
#<Task id: 18, name: "Buy turkey", description: "Don't forget the cranberries.", durati
on: 60, due_date: "2014-11-27", complete: false, created_at: "2014-11-26 17:54:00", upd
ated_at: nil>,
#<Task id: 21, name: "Library study", description: "Check the wifi's working.", duratio
n: 90, due_date: "2014-11-06", complete: true, created_at: "2014-11-02 08:36:00", updat
ed_at: nil>,
#<Task id: 22, name: "Movie night", description: "Chinatown", duration: 120, due_date:
"2014-11-12", complete: false, created_at: "2014-11-09 11:13:00", updated_at: nil>,
#<Task id: 12, name: "Write paper", description: "Henry Knox and the Siege of Boston",
duration: 240, due_date: "2014-11-06", complete: true, created_at: "2014-11-06 13:27:00
", updated_at: nil>]
irb(main):031:0>
Where multiple tasks have the same duratio n, they are further so rted by due_date.
To reverse the o rder o f the so rting, add "DESC" after the attribute name. "DESC" so rts in a DESCending o rder fro m
highest to lo west:
INTERACTIVE SESSION:
irb(main):031:0> Task.all(:order=>'duration DESC')
=> [#<Task id: 12, name: "Write paper", description: "Henry Knox and the Siege of Bosto
n", duration: 240, due_date: "2014-11-06", complete: true, created_at: "2014-11-06 13:2
7:00", updated_at: nil>,
#<Task id: 22, name: "Movie night", description: "Chinatown", duration: 120, due_date:
"2014-11-12", complete: false, created_at: "2014-11-09 11:13:00", updated_at: nil>,
#<Task id: 21, name: "Library study", description: "Check the wifi's working.", duratio
n: 90, due_date: "2014-11-06", complete: true, created_at: "2014-11-02 08:36:00", updat
ed_at: nil>,
#<Task id: 18, name: "Buy turkey", description: "Don't forget the cranberries.", durati
on: 60, due_date: "2014-11-27", complete: false, created_at: "2014-11-26 17:54:00", upd
ated_at: nil>,
#<Task id: 27, name: "Buy eggs", description: "Go to the market and buy six eggs.\n\nTr
y to get orga...", duration: 60, due_date: "2014-11-12", complete: false, created_at: "
2014-11-11 12:38:00", updated_at: nil>,
#<Task id: 23, name: "Buy candy", description: "Buy extra candy.\n\nSee if they have an
y of those jel...", duration: 45, due_date: "2010-10-31", complete: true, created_at: "
2010-10-28 15:04:00", updated_at: nil>,
#<Task id: 11, name: "Stock up on coffee", description: "Costa Rican medium roast", dur
ation: 30, due_date: "2014-11-06", complete: true, created_at: "2014-11-04 11:41:00", u
pdated_at: nil>]
irb(main):032:0>
What Just Happened?
In this lesso n, we learned:
Rails talks to the database with a library called Act ive Re co rd.
All o f yo ur Task data is sto red in a database table called t asks.
T ask tells yo u the structure o f the tasks table.
T ask.all returns all o f the tasks as an array o f Task o bjects.
Yo u can read single tasks with an index: T ask.all[2].
Yo u can find o ut the number o f tasks with T ask.all.size .
Yo u can read the first and last task in the table with T ask.all.f irst and T ask.all.last .
Yo u can search fo r a single task with a f inde r.
T ask.f ind_by_nam e ('Buy e ggs') will find a task by name.
There's a find_by_... finder fo r every attribute.
f ind_all_by_... finders return an array o f all matching tasks.
The :o rde r o ptio n will so rt the results fro m a finder o r Task.all.
That's quite an impressive list o f newly attained skills. Go o d jo b!
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Adding Custom Data Pages
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
use the co ntro ller.
use the actio n metho d in Rails and use it in the co ntro ller.
display the results o f a custo m query o n a custo m web page.
Putting Data on the Page
Yo u've seen ho w to create custo m queries in Rails with ActiveReco rd. Yo u can find o ut, fo r example, which tasks still
need to be co mpleted by typing this into the Rails co nso le:
INTERACTIVE SESSION:
irb(main):001:0> Task.find_all_by_complete(false)
=> [#<Task id: 18, name: "Buy turkey", description: "Don't forget the cranberries.", du
ration: 60, due_date: "2014-11-27", complete: false, created_at: "2014-11-26 17:54:00",
updated_at: nil>,
#<Task id: 22, name: "Movie night", description: "Chinatown", duration: 140, due_date:
"2014-11-12", complete: false, created_at: "2014-11-11 11:13:00", updated_at: nil>,
#<Task id: 27, name: "Buy eggs", description: "Go to the market and buy six eggs.\n\nTr
y to get orga...", duration: 60, due_date: "2014-11-12", complete: false, created_at: "
2014-11-11 12:38:00", updated_at: nil>]
irb(main):002:0>
It's useful to have this info rmatio n available o n the co mmand line, but what if yo u want to put it o n the web? Ho w can
yo u display the results o f a custo m query o n a custo m web page?
Introducing the Controller
When yo ur Rails applicatio n receives a request fo r a web page, typically, it reads the relevant data fro m the database
and then passes it as a variable to a page template. The template uses the data in the variable to generate the web
page that's returned:
So when a user asks to display the task with id 21, the Rails applicatio n reads the task fro m the database, sto res it in a
variable called @ t ask and then displays that variable using sho w.ht m l.e rb. When a user asks fo r any dynamic page,
the applicatio n perfo rms a similar sequence o f events. It might read a single task reco rd o r several. It might delete o r
update a reco rd instead o f reading it, but in every case, the applicatio n will talk to the database and then generate so me
so rt o f respo nse.
The co de that co ntains the exact instructio ns to execute in each case is called the controller. Yo ur co ntro llers are in
yo ur app/co ntro llers fo lder. The co ntro ller controls ho w the applicatio n respo nds to requests.
There's a co ntro ller fo r each type o f data that the applicatio n uses. The tasks co ntro ller lo o ks like this:
OBSERVE: app/co ntro llers/tasks_co ntro ller.rb
class TasksController < ApplicationController
# GET /tasks
# GET /tasks.xml
def index
@tasks = Task.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @tasks }
end
end
# GET /tasks/1
# GET /tasks/1.xml
def show
@task = Task.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @task }
end
end
# GET /tasks/new
# GET /tasks/new.xml
def new
@task = Task.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @task }
end
end
# GET /tasks/1/edit
def edit
@task = Task.find(params[:id])
end
# POST /tasks
# POST /tasks.xml
def create
@task = Task.new(params[:task])
respond_to do |format|
if @task.save
format.html { redirect_to(@task, :notice => 'Task was successfully created.') }
format.xml { render :xml => @task, :status => :created, :location => @task }
else
format.html { render :action => "new" }
format.xml { render :xml => @task.errors, :status => :unprocessable_entity }
end
end
end
# PUT /tasks/1
# PUT /tasks/1.xml
def update
@task = Task.find(params[:id])
respond_to do |format|
if @task.update_attributes(params[:task])
format.html { redirect_to(@task, :notice => 'Task was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @task.errors, :status => :unprocessable_entity }
end
end
end
# DELETE /tasks/1
# DELETE /tasks/1.xml
def destroy
@task = Task.find(params[:id])
@task.destroy
respond_to do |format|
format.html { redirect_to(tasks_url) }
format.xml { head :ok }
end
end
end
That's a very lo ng Ruby script—and so it sho uld be. The co ntro ller is the real brains o f the applicatio n. It's the co de
that's in charge o f exactly ho w the applicatio n wo rks. View co de, like the page templates, co ntro ls ho w the applicatio n
lo o ks. Mo del co de is in charge o f all o f the data—ho w it's sto red in the database and determining whether it's valid.
The co ntro ller, ho wever, is really the bo ss. It co o rdinates the mo del and the view and brings the applicatio n to life.
How to List Only Incomplete T asks
Suppo se that instead o f seeing every task listed o n the index page, yo u o nly wanted to see the tasks that are
inco mplete:
Ho w wo uld yo u change that? When yo u go to the index page, Rails generates the display using the inde x.ht m l.e rb:
The co ntro ller co de asks the mo del fo r all o f the tasks in the database, and then it sto res them in a variable called
@ t asks. Then it calls the inde x.ht m l.e rb template to generate a web page using the @ t asks data. Yo u can see this
happening in the inde x functio n in the co ntro ller co de:
OBSERVE:
...
def index
@tasks = Task.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @tasks }
end
...
This co de runs when a user requests inde x.html. It re ads all t he t asks and st o re s t he m in t he @ t asks variable .
The lines between re spo nd and e nd generate the respo nse, including generating the web page with the
index.html.erb template.
To make the index page sho w just the inco mplete tasks, change just o ne line o f
app/co nt ro lle rs/t asks_co nt ro lle r.rb:
CODE TO TYPE: app/co ntro llers/tasks_co ntro ller.rb
class TasksController < ApplicationController
# GET /tasks
# GET /tasks.xml
def index
@tasks = Task.all
@tasks = Task.find_all_by_complete(false)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @tasks }
end
end
...
By setting the @ t asks variable to T ask.f ind_all_by_co m ple t e (f alse ), we ensure that the inde x.ht m l.e rb sees
o nly the inco mplete tasks. Open a bro wser windo w o n the index page, and yo u'll see this:
When the applicatio n received the request fo r ...t asks/, first it ran the inde x functio n in t asks_co nt ro lle r.rb. That
read the inco mplete tasks fro m the database and sto red them in the @ t asks variable. Then the co ntro ller called the
inde x.ht m l.e rb template to generate a web page to pro vide a respo nse. The inde x.ht m l.e rb creates a web page
using the @ t asks just as it did befo re. Tho ugh no w, instead o f ho lding a list o f all o f the tasks in the database, the
@ t asks variable co ntains o nly the inco mplete tasks.
How Do You See the Complete T asks?
Of co urse the do wnside to mo difying the inde x functio n in the co ntro ller is that yo u can no lo nger see the co mpleted
tasks. In a perfect wo rld, yo u'd have the o riginal .../t asks/ URL sho w the list o f all tasks, and so me o ther URL set
aside to display the inco mplete tasks. That way, the user co uld decide which set o f tasks to view. Co uld yo u add a
custo m page to the set o f scaffo lded pages yo u created fo r task data?
The first step wo uld be to reset the index functio n in the task co ntro ller so that it generates a page fo r all tasks. Then
yo u'd create ano ther functio n called inco m ple t e that o nly reads the inco mplete tasks:
CODE TO TYPE: app/co ntro llers/tasks_co ntro ller.rb
class TasksController < ApplicationController
# GET /tasks
# GET /tasks.xml
def index
@tasks = Task.all
@tasks Task.find_all_by_complete(false)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @tasks }
end
end
def incomplete
@tasks = Task.find_all_by_complete(false)
respond_to do |format|
format.html
format.xml { render :xml => @tasks }
end
end
# GET /tasks/1
# GET /tasks/1.xml
def show
@task = Task.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @task }
end
end
...
No w, the co ntro ller kno ws ho w to read everything and ho w to read just the inco mplete tasks. Still, there's a pro blem. If
yo u make the mo dificatio ns to t asks_co nt ro lle r.rb, save the file, and then refresh the list page, yo u can certainly see
all o f the tasks again, but if yo u mo dify the URL to run the inco m ple t e functio n, this happens:
The applicatio n do esn't appear to be running the new inco m ple t e functio n at all. To see why the co de do esn't wo rk,
yo u'll need to learn a little abo ut Rails routes.
Routes Define a Path through the App
When Rails receives a request fo r a URL, it has to decide which co de it needs to run. Fo r example, when Rails receives
a request like this:
OBSERVE:
http://login-id/oreillystudent.com/ostapp/tasks/2
The applicatio n runs the sho w functio n in the tasks co ntro ller, and then uses the sho w.ht m l.e rb template to display
the task with id == 2. So , ho w do es it kno w that it needs to do that? It uses a Rails route.
A ro ute is a rule that tells Rails the co de it needs to run fo r a URL. The co de in Rails is gro uped into actions. An actio n
is a set o f different pieces o f co de that fo llo ws a naming co nventio n. Fo r example, the inde x functio n in
t asks_co nt ro lle r.rb and the inde x.ht m l.e rb template file to gether make up the index actio n. The sho w functio n in
the t asks_co nt ro lle r.rb and the sho w.ht m l.e rb template make up the show actio n. The functio ns in the co ntro ller
are called action methods.
A Rails ro ute asso ciates a particular set o f URLs with a particular actio n, fo r example:
If Rails is aske d f o r URLs like t his
it runs t his act io n
http://<lo gin-id>.o reillystudent.co m/o stapp/tasks/ index
http://login-id.o reillystudent.co m/o stapp/tasks/n
sho w
By default, scaffo lding creates a set o f standard ro utes. To see what these are, o pen a Terminal sessio n, change into
the ~/railsapps/o st app fo lder, and type this co mmand:
INTERACTIVE SESSION:
cold1:~$ cd ~/railsapps/ostapp/
cold1:~/railsapps/ostapp$ rake routes
(in /users/dgriffit1/railsapps/ostapp)
photos GET
/photos(.:format)
"}
POST
/photos(.:format)
s"}
new_photo GET
/photos/new(.:format)
edit_photo GET
/photos/:id/edit(.:format)
}
photo GET
/photos/:id(.:format)
}
PUT
/photos/:id(.:format)
s"}
DELETE /photos/:id(.:format)
os"}
tasks GET
/tasks(.:format)
}
POST
/tasks(.:format)
"}
new_task GET
/tasks/new(.:format)
edit_task GET
/tasks/:id/edit(.:format)
task GET
/tasks/:id(.:format)
PUT
/tasks/:id(.:format)
"}
DELETE /tasks/:id(.:format)
s"}
cold1:~/railsapps/ostapp$
{:action=>"index", :controller=>"photos
{:action=>"create", :controller=>"photo
{:action=>"new", :controller=>"photos"}
{:action=>"edit", :controller=>"photos"
{:action=>"show", :controller=>"photos"
{:action=>"update", :controller=>"photo
{:action=>"destroy", :controller=>"phot
{:action=>"index", :controller=>"tasks"
{:action=>"create", :controller=>"tasks
{:action=>"new", :controller=>"tasks"}
{:action=>"edit", :controller=>"tasks"}
{:action=>"show", :controller=>"tasks"}
{:action=>"update", :controller=>"tasks
{:action=>"destroy", :controller=>"task
These are the ro utes that are already defined in the applicatio n. Yo urs may lo o k a little different, depending o n the
changes yo u've made to yo ur o wn applicatio n. Each displayed line represents a single ro ute. This is the ro ute that tells
Rails what to do if so meo ne asks fo r a page that displays a single task:
OBSERVE:
task GET
/tasks/:id(.:format)
{:action=>"show", :controller=>"tasks"}
Here, we specify that t ask is the name o f the ro ute and /t asks/ is the path pattern. If a user requests /t asks/n, Rails will
use t he " sho w" act io n and t he " t asks" co nt ro lle r.
If so meo ne asks fo r a URL like:
OBSERVE:
http://login-id/oreillystudent.com/ostapp/tasks/2.html
Rails will match this URL with the t ask because the part o f the URL fo llo wing the ro o t o f the applicatio n (the
"http://<lo gin-id>.o reillystudent.co m/o stapp" part) is /t asks/2.ht m l. This matches the t ask path pattern
/t asks/:id(.:f o rm at ). The t ask ro ute tells Rails that it needs to run the " sho w" actio n using the " t asks" co ntro ller.
Note
This ro ute will match URLs that end .../t asks/2, as well as .../t asks/2.ht m l. The .ht m l fo rmat extensio n
is o ptio nal, which is why it is wrapped in parentheses in the path pattern: /t asks/:id(.:f o rm at ).
Rails didn't run o ur inco m ple t e co de because there was no matching ro ute. In o rder to make Rails run o ur co de, we
need to create a new ro ute fo r it.
Can yo u think o f ho w a ro ute fo r the incomplete tasks might lo o k?
Creating a Route
Open the ~/railsapps/o st app/co nf ig/ro ut e s.rb file. This is the Ruby script that defines all o f the ro utes in the
applicatio n.
OBSERVE: ~/railsapps/o stapp/co nfig/ro utes.rb
Ostapp::Application.routes.draw do
resources :tasks
get "intro/dynamic"
get "my_timer/show_current_time"
end
Yo ur versio n o f the file may lo o k slightly different. The co mments have been o mitted here to make the file mo re
readable. When Rails receives a request fo r a URL, it checks the ro utes in this file, and uses the first matching ro ute it
finds. The co mmands in ro ut e s.rb do n't co ntain a lo t o f detail abo ut each o f the ro utes. Rails has a very stro ng set o f
co nventio ns, so a single co mmand like:
OBSERVE:
resources :tasks
...tells Rails that it needs to create a set o f standard ro utes fo r the t ask data. This single re so urce s co mmand creates
all o f these ro utes:
OBSERVE: The ro utes created by 'reso urces :tasks'
tasks GET
/tasks(.:format)
{:action=>"index", :controller=>"tasks"
/tasks(.:format)
{:action=>"create", :controller=>"tasks
/tasks/new(.:format)
/tasks/:id/edit(.:format)
/tasks/:id(.:format)
/tasks/:id(.:format)
{:action=>"new", :controller=>"tasks"}
{:action=>"edit", :controller=>"tasks"}
{:action=>"show", :controller=>"tasks"}
{:action=>"update", :controller=>"tasks
/tasks/:id(.:format)
{:action=>"destroy", :controller=>"task
}
POST
"}
new_task GET
edit_task GET
task GET
PUT
"}
DELETE
s"}
Currently, when Rails receives a request fo r this page:
OBSERVE:
http://login-id/oreillystudent.com/ostapp/tasks/incomplete
...it is matching to the task ro ute, because Rails has been to ld that pages that match the task ro ute are structured like
this:
OBSERVE:
/tasks/:id(.:format)
They begin with /t asks/ and are fo llo wed by an :id. Rails do esn't kno w that we intend this id value to be a number. The
ro ute just says that something will fo llo w /t asks/ and that Rails needs to remember this as the id. In o ur URL there is
so mething fo llo wing /t asks/— it's the string "inco m ple t e ", so Rails interprets "inco m ple t e " as the id.
Next, the co ntro ller lo o ks fo r a task with database id value o f " inco m ple t e " . It do esn't find any, that's why yo u go t the
erro r message.
Yo u need to create a new ro ute to tell Rails to run the inco m ple t e functio n in the t asks co ntro ller. Mo dify
/railsapps/o st app/co nf ig/ro ut e s.rb like this:
CODE TO TYPE: ~/railsapps/o stapp/co nfig/ro utes.rb
Ostapp::Application.routes.draw do
get "tasks/incomplete"
resources :tasks
get "my_timer/show_current_time"
end
Note
Make sure yo u include a ge t " t asks/inco m ple t e " before re so urce s :t asks, to ensure that Rails will
match against yo ur new ro ute befo re any o f the o thers.
The single Ruby co mmand ge t " t asks/inco m ple t e " tells Rails all it needs to kno w abo ut the new ro ute. Because
Rails has very stro ng naming co nventio ns, it will imply that a ro ute with the path pattern " t asks/inco m ple t e " will call
the inco m ple t e actio n o n the tasks co ntro ller. Yo u can check this by running rake ro ut e s again:
INTERACTIVE SESSION:
cold1:~/railsapps/ostapp$ rake routes
(in /users/dgriffit1/railsapps/ostapp)
tasks_incomplete GET
/tasks/incomplete(.:format)
ction=>"incomplete"}
tasks GET
/tasks(.:format)
ction=>"index"}
POST
/tasks(.:format)
ction=>"create"}
new_task GET
/tasks/new(.:format)
ction=>"new"}
edit_task GET
/tasks/:id/edit(.:format)
ction=>"edit"}
task GET
/tasks/:id(.:format)
ction=>"show"}
PUT
/tasks/:id(.:format)
ction=>"update"}
DELETE /tasks/:id(.:format)
ction=>"destroy"}
cold1:~/railsapps/ostapp$
{:controller=>"tasks", :a
{:controller=>"tasks", :a
{:controller=>"tasks", :a
{:controller=>"tasks", :a
{:controller=>"tasks", :a
{:controller=>"tasks", :a
{:controller=>"tasks", :a
{:controller=>"tasks", :a
Creating the T emplate File
Yo u no w have a new ro ute named t asks_inco m ple t e . While the ro ute is no w in place, we've still go t wo rk to do .
Open o r relo ad the bro wser windo w at ht t p://login-id.o re illyst ude nt .co m /o st app/t asks/inco m ple t e , and yo u'll
see this:
That's right—Rails can't find a template file. The ro ute to ld Rails to use the tasks co ntro ller and the incomplete actio n,
but the actio n needs co de to generate the web page respo nse. Because the actio n is called incomplete, Rails tries to
generate a web page fro m a template file called inco m ple t e .ht m l.e rb, but do esn't find it.
The template file must co ntain the inco m ple t e functio n in the co ntro ller that is sto ring the tasks in a variable called
@ t asks, so the inco m ple t e .ht m l.e rb template will need to display the co ntents o f @ t asks. Yo u already have a
template file that do es that—inde x.ht m l.e rb:
OBSERVE: ~/railsapps/o stapp/app/views/tasks/index.html.erb
<h1>Listing tasks</h1>
<table>
<tr>
<th>Name</th>
<th>Description</th>
<th>Duration</th>
<th>Due date</th>
<th>Complete</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @tasks.each do |task| %>
<tr>
<td><%= task.name %></td>
<td><%= task.description %></td>
<td><%= task.duration %></td>
<td><%= task.due_date %></td>
<td><%= task.complete %></td>
<td><%= link_to 'Show', task %></td>
<td><%= link_to 'Edit', edit_task_path(task) %></td>
<td><%= link_to 'Destroy', task, :confirm => 'Are you sure?', :method => :delete %>
</td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New Task', new_task_path %>
No w yo u can create yo ur new inco m ple t e .ht m l.e rb template by co pying inde x.ht m l.e rb. In a Terminal sessio n,
type the co mmand sho wn:
INTERACTIVE SESSION:
cold1:~$ cp ~/railsapps/ostapp/app/views/tasks/index.html.erb ~/railsapps/ostapp/app/vi
ews/tasks/incomplete.html.erb
cold1:~$
Once yo u've co pied the file, yo u can edit it and change the heading:
CODE TO TYPE: ~/railsapps/o stapp/app/views/tasks/inco mplete.html.erb
<h1>Listing tasks</h1>
<h1>Incomplete tasks</h1>
<table>
<tr>
<th>Name</th>
<th>Description</th>
<th>Duration</th>
<th>Due date</th>
<th>Complete</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @tasks.each do |task| %>
<tr>
<td><%= task.name %></td>
<td><%= task.description %></td>
<td><%= task.duration %></td>
<td><%= task.due_date %></td>
<td><%= task.complete %></td>
<td><%= link_to 'Show', task %></td>
<td><%= link_to 'Edit', edit_task_path(task) %></td>
<td><%= link_to 'Destroy', task, :confirm => 'Are you sure?', :method => :delete %>
</td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New Task', new_task_path %>
No w, refresh the Inco mplete tasks page; it displays the inco mplete tasks:
What Just Happened?
In this lesso n, we learned:
The controller co o rdinates the mo del co de and the view co de to generate a respo nse.
An action is the set o f co de used to respo nd to a request.
An actio n is co mmo nly made up o f a functio n in the co ntro ller and a template file.
A functio n inside the co ntro ller is called an action method.
Actio ns are defined by naming co nventio ns; fo r example, an index functio n and an index.html.erb template.
When Rails receives a request, it uses a ro ute to decide which actio n and co ntro ller to use.
Rails will then call the co rrect actio n metho d in the co ntro ller.
The actio n metho d will read o r write data to the database and will then no rmally generate a respo nse with a
template with a matching name.
If yo u read data with the inde x actio n metho d, the respo nse will be generated using the inde x.ht m l.e rb
template.
Yo u've go t a who le lo t o f new stuff to try o ut in the ho mewo rk. Get to it and when yo u're do ne, I'll see yo u in the next
lesso n!
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Layouts
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
change the lo o k and feel o f yo ur co de.
use layo uts and URL helpers to manage large numbers o f pages efficiently.
T he Look and the Links
Let's begin by linking o ur new Incomplete tasks page to the rest o f the applicatio n. Peo ple are likely to o pen yo ur
applicatio n o n the main tasks index page:
To add a link to the to p o f that page, edit /app/vie ws/t asks/inde x.ht m l.e rb as sho wn:
CODE TO TYPE:
<a href="tasks/incomplete">Incomplete tasks</a>
<h1>Listing tasks</h1>
<table>
<tr>
<th>Name</th>
<th>Description</th>
<th>Duration</th>
<th>Due date</th>
<th>Complete</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @tasks.each do |task| %>
<tr>
<td><%= task.name %></td>
<td><%= task.description %></td>
<td><%= task.duration %></td>
<td><%= task.due_date %></td>
<td><%= task.complete %></td>
<td><%= link_to 'Show', task %></td>
<td><%= link_to 'Edit', edit_task_path(task) %></td>
<td><%= link_to 'Destroy', task, :confirm => 'Are you sure?', :method => :delete %>
</td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New Task', new_task_path %>
Save it and o pen o r refresh the task index page:
Even tho ugh this is the mo st straightfo rward way to add a link to the page, mo st Rails develo pers do n't create links this
way. Why? Because, if yo u decide to change the URL o f the Incomplete tasks page later, yo u'll also have to change all
o f the manual links yo u've created. There's a better to o l to use to add links to a page: the link_t o helper functio n.
Helper functio ns can add HTML co de to a page fo r yo u. We can use link_t o helper to add hyperlinks to pages, and
link_t o will create a link using a named route.
Open a Unix shell and lo o k at the ro utes in yo ur applicatio n. Many o f them have been given names. The names appear
in the first co lumn o f the o utput when yo u change into yo ur applicatio n's directo ry and run the rake ro ut e s co mmand:
INTERACTIVE SESSION:
cold1:~/railsapps/ostapp$ rake routes
(in /users/dgriffit1/railsapps/ostapp)
tasks_incomplete GET
/tasks/incomplete(.:format)
r=>"tasks"}
tasks GET
/tasks(.:format)
asks"}
POST
/tasks(.:format)
tasks"}
new_task GET
/tasks/new(.:format)
ks"}
edit_task GET
/tasks/:id/edit(.:format)
sks"}
task GET
/tasks/:id(.:format)
sks"}
PUT
/tasks/:id(.:format)
tasks"}
DELETE /tasks/:id(.:format)
"tasks"}
intro_dynamic GET
/intro/dynamic(.:format)
"intro"}
cold1:~/railsapps/ostapp$
{:action=>"incomplete", :controlle
{:action=>"index", :controller=>"t
{:action=>"create", :controller=>"
{:action=>"new", :controller=>"tas
{:action=>"edit", :controller=>"ta
{:action=>"show", :controller=>"ta
{:action=>"update", :controller=>"
{:action=>"destroy", :controller=>
{:action=>"dynamic", :controller=>
The ro utes have names like ne w_t ask and e dit _t ask. When yo u added the ro ute to the Incomplete tasks page, Rails
gave the ro ute the name t asks_inco m ple t e . Because the path to the Incomplete tasks page has a named ro ute, yo u
can create a hyperlink by passing the name o f the ro ute to the link_t o helper. Edit inde x.ht m l.e rb again and replace
the manual hyperlink with a call to the link_t o helper:
CODE TO TYPE: app/views/tasks/index.html.erb
<a href="tasks/incomplete">Incomplete tasks</a>
<%= link_to "Incomplete tasks", :tasks_incomplete %>
<h1>Listing tasks</h1>
<table>
<tr>
<th>Name</th>
<th>Description</th>
<th>Duration</th>
<th>Due date</th>
<th>Complete</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @tasks.each do |task| %>
<tr>
<td><%= task.name %></td>
<td><%= task.description %></td>
<td><%= task.duration %></td>
<td><%= task.due_date %></td>
<td><%= task.complete %></td>
<td><%= link_to 'Show', task %></td>
<td><%= link_to 'Edit', edit_task_path(task) %></td>
<td><%= link_to 'Destroy', task, :confirm => 'Are you sure?', :method => :delete %>
</td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New Task', new_task_path %>
We give the link_t o helper two pieces o f info rmatio n: the t e xt t o display in t he hype rlink and the nam e o f t he
ro ut e t o use :
OBSERVE:
<%= link_to "Incomplete tasks", :tasks_incomplete %>
Refresh the index page. It lo o ks exactly the same as befo re.
Still, we switch the manual hyperlink with a call to link_t o because it makes yo ur applicatio n mo re co nfigurable. If yo u
ever need to change the URL o f the Incomplete Tasks page, all o f yo ur hyperlinks will send the user to the co rrect
address auto matically, because they'll lo cate the page by the name o f its ro ute, rather than its address.
Note
The link_to helper inserts the full path to its destinatio n page: /o st app/t asks/inco m ple t e , so the link
will wo rk no matter which page co ntains it.
Adding the Link to the Other Pages
Users will pro bably want to get to the Incomplete Tasks fro m pretty much any o f the task pages in the applicatio n. To
pro vide that feature, yo u could just co py and paste the link-co de fro m index.html.erb into each o f the o ther task
templates, but that seems pretty inefficient. In additio n, if yo u wanted to add mo re links to the task pages and create a
menu, say, o f the index page, the inco mplete tasks page, and the New Task page, yo u'd have to go to each o f the task
pages and edit the list o f links. That defies the Rails Don't Repeat Yourself principle in a big way. If yo u need to create
so me piece o f shared co de, use a Rails layout instead.
What is a Rails Layout?
We saw early o n that Rails template files do n't generate full web pages—they just generate page fragments.
Each template co ntains the main co ntent o f a web page, witho ut the <html>, <head> sectio ns that will be
co mmo n to all pages.
When the page generated fro m a template is sent back to the bro wser tho ugh, it includes all o f tho se tags and
mo re. View the Listing tasks page, right-click it, and select Vie w So urce :
That extra HTML co mes fro m the layout.
A layo ut is a template file that co ntains so me bo ilerplate HTML to be shared by a few pages. The o utput fro m
o ne o f the o ther template files—like inde x.ht m l.e rb o r sho w.ht m l.e rb—is inserted into the layo ut file, and
this mo re co mplete web page is sent back to the web bro wser.
The o utput o f the template file is inserted into the layo ut at the yield point in the layo ut. Let's mo dify an actual
layo ut file and see ho w it wo rks.
Change the Page Layouts with application.html.erb
Open app/vie ws/layo ut s/applicat io n.ht m l.e rb:
OBSERVE: app/views/layo uts/applicatio n.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Ostapp</title>
<%= stylesheet_link_tag :all %>
<%= javascript_include_tag :defaults %>
<%= csrf_meta_tag %>
</head>
<body>
<%= yield %>
</body>
</html>
This is the layo ut used fo r all the pages. The o utput o f each template is placed at the yie ld po int. Whatever yo u change
in this file will be visible o n all the pages, so add the link at the to p o f the page like this:
CODE TO TYPE: app/views/layo uts/applicatio n.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Ostapp</title>
<%= stylesheet_link_tag :all %>
<%= javascript_include_tag :defaults %>
<%= csrf_meta_tag %>
</head>
<body>
<%= link_to "Incomplete tasks", :tasks_incomplete %>
<%= yield %>
</body>
</html>
Next, edit inde x.ht m l.e rb and remo ve the co de yo u added there:
CODE TO TYPE: app/views/tasks/index.html.erb
<%= link_to "Incomplete tasks", :tasks_incomplete %>
<h1>Listing tasks</h1>
<table>
<tr>
<th>Name</th>
<th>Description</th>
<th>Duration</th>
<th>Due date</th>
<th>Complete</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @tasks.each do |task| %>
<tr>
<td><%= task.name %></td>
<td><%= task.description %></td>
<td><%= task.duration %></td>
<td><%= task.due_date %></td>
<td><%= task.complete %></td>
<td><%= link_to 'Show', task %></td>
<td><%= link_to 'Edit', edit_task_path(task) %></td>
<td><%= link_to 'Destroy', task, :confirm => 'Are you sure?', :method => :delete %>
</td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New Task', new_task_path %>
Save bo th files, then refresh the bro wser to display the list o f all tasks. The link still appears at the to p o f the page:
No w the link co mes fro m the layo ut rather than the inde x.ht m l.e rb template, and the link appears o n each o f the
o ther pages:
Creating Separates for Different Page Groups
The pro blem with editing the applicat io n.ht m l.e rb layo ut file tho ugh, is that it changes the o utput o f all the pages.
No t just the pages that are used to manage the tasks, but every o ther generated page o n the site—even no n-taskrelated pages:
Each Rails applicatio n co ntains a single layo ut by default—applicat io n.ht m l.e rb—but yo u can also create a layo ut
that will be applied to a single catego ry. Fo r example, yo u can create a layo ut that will be used with o nly the t ask
pages.
First, remo ve the link co de that yo u just added to applicat io n.ht m l.e rb:
CODE TO TYPE: app/views/layo uts/applicatio n.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Ostapp</title>
<%= stylesheet_link_tag :all %>
<%= javascript_include_tag :defaults %>
<%= csrf_meta_tag %>
</head>
<body>
<%= link_to "Incomplete tasks", :tasks_incomplete %>
<%= yield %>
</body>
</html>
Save the applicat io n.ht m l.e rb file.
This new layo ut file will be used exclusively by the task pages, so we can add a little mo re than just a single link. We'll
add a co mplete side menu to the layo ut.
In the Co deRunner Edito r windo w, click New File (
) and add the co de as sho wn:
CODE TO TYPE:
<!DOCTYPE html>
<html>
<head>
<title>Ostapp</title>
<%= stylesheet_link_tag :all %>
<%= javascript_include_tag :defaults %>
<%= csrf_meta_tag %>
</head>
<body>
<img src="https://oreillyschool.com/images/OST_Logo.gif"/>
<br/>
<div style="display:table-cell; width: 200px;">
<h1>TASKS</h1>
<ul>
<li><%= link_to 'All tasks', :tasks %></li>
<li><%= link_to 'Incomplete tasks', :tasks_incomplete %></li>
<li><%= link_to 'New task', :new_task %></li>
</ul>
</div>
<div style="display:table-cell">
<%= yield %>
</div>
</body>
</html>
Save the file in /railsapps/o st app/app/vie ws/layo ut s as t asks.ht m l.e rb.
Yo u'll no tice we've created links to three o f the named ro utes in the applicatio n:
t asks links to main task index page.
t asks_inco m ple t e links to the Inco mplete tasks page yo u created.
ne w_t ask links to the page where a user can create a new link.
The styling in the HTML will add these links to the left side o f a typical task page. The main co ntent o f each page will be
inserted into this layo ut at the <% = yie ld % > co de.
Check a no n-task page in yo ur applicatio n to be sure that the link at the to p o f the page has disappeared:
Go back to yo ur task pages; they all have the new lo o k and feel:
The applicat io n.ht m l.e rb file is the layo ut that a page will use if no o thers are available. If a template finds a layo ut
that matches its fo lder name tho ugh—fo r example, t asks.ht m l.e rb—it uses that o ne instead.
How Do You Make a Link Not a Link?
Layo uts are o ften used to create standard menu bars which link to the majo r parts o f yo ur applicatio n, but if
yo u're o n the Inco mplete tasks page, yo u do n't need to have a link to it in the sidebar. Also , in o rder to
highlight the page yo u're currently o n, it's useful to switch off any links to the current page.
The current layo ut do esn't do that, so when yo u are o n the Incomplete tasks page, the side menu still has an
Incomplete tasks link:
We can fix that! The curre nt _page ? helper functio n (the questio n mark ? is required) tells yo u whether the
page o f the current page matches a given path. Fo r example, this will be t rue if yo u are currently o n the
Inco mplete tasks page:
Ho w do es that help? Remember that yo u can embed lo gic within a web page using <%...%> markers. That
means yo u can add lo gic to replace a link with a simple piece o f text like this:
CODE TO TYPE: app/views/layo uts/tasks.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Ostapp</title>
<%= stylesheet_link_tag :all %>
<%= javascript_include_tag :defaults %>
<%= csrf_meta_tag %>
</head>
<body>
<img src="https://oreillyschool.com/images/OST_Logo.gif"/>
<br/>
<div style="display:table-cell; width: 200px;">
<h1>TASKS</h1>
<ul>
<li><%= link_to 'All tasks', :tasks %></li>
<li><% if current_page?(:tasks) %>
All tasks
<% else %>
<%= link_to 'All tasks', :tasks %>
<% end %>
</li>
<li><%= link_to 'Incomplete tasks', :tasks_incomplete %></li>
<li><%= link_to 'New task', :new_task %></li>
</ul>
</div>
<div style="display:table-cell">
<%= yield %>
</div>
</body>
</html>
This co de checks to determine whether yo u are currently o n the index page (which matches the :t asks ro ute)
and if so , generates just the text, All t asks. If yo u are not o n the index page, it generates a link to the index
page instead. Save app/vie ws/layo ut s/t asks.ht m l.e rb and lo o k at the main index o f tasks; yo u will see
this:
When yo u change to ano ther page, the link appears:
Yo u co uld write a similar piece o f co de fo r each o f the links o n the menu, but yo u're no t go ing to do that!
There's a better way to switch links o n and o ff in Rails:
CODE TO TYPE: app/views/layo uts/tasks.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Ostapp</title>
<%= stylesheet_link_tag :all %>
<%= javascript_include_tag :defaults %>
<%= csrf_meta_tag %>
</head>
<body>
<img src="https://oreillyschool.com/images/OST_Logo.gif"/>
<br/>
<div style="display:table-cell; width: 200px;">
<h1>TASKS</h1>
<ul>
<li><% if current_page?(:tasks) %>
All tasks
<% else %>
<%= link_to 'All tasks', :tasks %>
<% end %>
</li>
<li><%= link_to 'Incomplete tasks', :tasks_incomplete %></li>
<li><%= link_to 'New task', :new_task %></li>
<li><%= link_to_unless_current 'All tasks', :tasks %></li>
<li><%= link_to_unless_current 'Incomplete tasks', :tasks_incomplete %></li>
<li><%= link_to_unless_current 'New task', :new_task %></li>
</ul>
</div>
<div style="display:table-cell">
<%= yield %>
</div>
</body>
</html>
The link_t o _unle ss_curre nt helper do es exactly what o ur piece o f embedded lo gic did—it checks to see if
the link it's been given go es to the current page address, and if so , it replaces the link with a small piece o f
text.
Mo st o f the helper functio ns that are used in Rails wo rk this way. So metime, so mewhere a pro grammer has
fo und themselves writing the same piece o f co de several times. It might no t be a lo ng piece o f co de—but if
yo u have write any co de even twice, it's better to have a helper functio n do it fo r yo u.
Note
As we've do ne befo re, we passed parameters to the link_t o _unle ss_curre nt without
parentheses, so we didn't write link_t o _unle ss_curre nt ('All t asks', :t asks). It's entirely yo ur
cho ice whether yo u include parentheses o r use poetry-style co de.
Okay—let's try o ut o ur new menu. Save the app/vie ws/layo ut s/t asks.ht m l.e rb file and then refresh a
bro wser that's sho wing the main list o f tasks. Yo u'll see this:
Because all o f the tasks are displayed already, the All tasks link is just a piece o f text. No w click o n the
Incomplete tasks link:
The same thing happens again, except this time Rails has switched o ff the Incomplete tasks link. This also
happens o n the New task page:
If yo u go to a page that do esn't appear o n the menu, all the links are enabled:
With just a little co de, we've added a lo t o f functio nality to the applicatio n! Yo u can treat a Rails template file
like a simple web page file, but by using the po wer o f layo uts and the page helpers, yo u can quickly manage
the structure o f yo ur site.
What Just Happened?
In this lesso n, we learned:
The link_t o helper can create hyperlinks to pages using named ro utes.
A layo ut file wraps additio nal co ntent aro und the basic co ntent generated by each template file.
The applicat io n.ht m l.e rb layo ut file is used by all templates by default.
Yo u can create a separate layo ut file fo r each gro up o f pages in yo ur applicatio n.
The name o f the file needs to match the gro up o f pages. Fo r example, t asks.html.erb is used fo r all the
templates in the /app/views/t asks directo ry.
The curre nt _page ? can tell yo u if the current page matches the given ro ute.
Yo u can use curre nt _page ? to switch links to the current page o ff.
It's much easier to switch links o ff auto matically with the link_t o _unle ss_curre nt helper.
Mo ving right alo ng...
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Partials
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
include partials in a template file.
Sharing Page Fragments with Partials
Welco me back! Let's get right to wo rk and learn abo ut ano ther to o l we have in Rails to write co de efficiently: partials. A
partial is a fragment o f a web page that can be pulled into a template file. Suppo se yo u want to add a standard piece o f
text to the pages in yo ur applicatio n. Fo r example, let's add a co pyright statement to the All tasks and Incomplete tasks
pages:
Yo u co uld type the co pyright text into o ne o f these pages, and then co py it into the o ther, but let me reiterate, in Rails,
as in mo st pro gramming situatio ns, we never want to duplicate co de: D.R.Y!
Instead o f typing the co de into bo th files, we co uld sto re the co mmo n text in a third file, then we co uld include it in the
All Tasks and Incomplete Tasks pages:
We do that in Rails using Partial Page Templates, which are usually just called Part ials.
Creating Your First Partial
In the text edito r, create a new file and type the co de as sho wn:
CODE TO TYPE:
<hr>
<h4>Copyright</h4>
<p> &#169; Copyright 2014 Taskmasters Unltd.</p>
<hr>
Save it as /railsapps/o st app/app/vie ws/t asks/_co pyright .ht m l.e rb. The name o f the file is impo rtant: all partials
have names that begin with an undersco re (_) character, to distinguish them fro m no rmal template files. No w that
we've created the partial, we can mo dify the app/vie ws/t asks/inde x.ht m l.e rb template to reference it:
CODE TO TYPE:
<h1>Listing tasks</h1>
<table>
<tr>
<th>Name</th>
<th>Description</th>
<th>Duration</th>
<th>Due date</th>
<th>Complete</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @tasks.each do |task| %>
<tr>
<td><%= task.name %></td>
<td><%= task.description %></td>
<td><%= task.duration %></td>
<td><%= task.due_date %></td>
<td><%= task.complete %></td>
<td><%= link_to 'Show', task %></td>
<td><%= link_to 'Edit', edit_task_path(task) %></td>
<td><%= link_to 'Destroy', task, :confirm => 'Are you sure?', :method => :delete %>
</td>
</tr>
<% end %>
</table>
<%= render "copyright" %>
<br />
<%= link_to 'New Task', new_task_path %>
We include the co ntents o f the _co pyright .ht m l.e rb partial with the re nde r functio n. The re nde r functio n evaluates
the co ntents o f a template o r partial file and co nverts it to a string. By including the re nde r functio n inside
inde x.ht m l.e rb, and wrapping it in the <%= ... %> tags, we're inserting the co ntents o f the partial into the template file.
Note
We do n't need to give the full filename fo r the partial; we just refer to the partial as " co pyright " because
all partial files begin with an undersco re and end with the .ht m l.e rb extensio n.
Lo ad o r refresh the All Tasks page; yo u can see the co pyright statement at the bo tto m:
We can no w make the same change to the inco m ple t e .ht m l.e rb template:
CODE TO TYPE: app/views/tasks/inco mplete.html.erb
<h1>Incomplete tasks</h1>
<table>
<tr>
<th>Name</th>
<th>Description</th>
<th>Duration</th>
<th>Due date</th>
<th>Complete</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @tasks.each do |task| %>
<tr>
<td><%= task.name %></td>
<td><%= task.description %></td>
<td><%= task.duration %></td>
<td><%= task.due_date %></td>
<td><%= task.complete %></td>
<td><%= link_to 'Show', task %></td>
<td><%= link_to 'Edit', edit_task_path(task) %></td>
<td><%= link_to 'Destroy', task, :confirm => 'Are you sure?', :method => :delete %>
</td>
</tr>
<% end %>
</table>
<%= render "copyright" %>
<br />
<%= link_to 'New Task', new_task_path %>
Take a lo o k at the Incomplete tasks page and yo u see the same co pyright statement. So no w yo u have two pages
reusing the text in the _co pyright .ht m l.e rb file:
By putting the shared co de into a separate file, we reduce the amo unt o f co de in the applicatio n, and make the
applicatio n easier to manage—no w when yo u need to change the year in the co pyright statement, yo u'll o nly need to
change it in o ne place.
Removing More Duplication
We used a partial to avo id duplicatio n in the inde x.ht m l.e rb and inco m ple t e .ht m l.e rb files, but what abo ut the
duplicated co de that was already in tho se files? We created inco m ple t e .ht m l.e rb by co pying the inde x.ht m l.e rb
file. Even tho ugh we made so me changes to the inco m ple t e .ht m l.e rb file—fo r example, we changed the heading—
mo st o f the co de is still exactly the same in bo th files:
Bo th templates generate an HTML table that co ntains a list o f tasks. To avo id this duplicatio n, we can mo ve the tablegeneratio n co de into a separate partial that inde x.ht m l.e rb and inco m ple t e .ht m l.e rb can share.
Yo u already typed this co de o nce, so we wo n't make yo u type it again. In the edito r, select the HTML table generatio n
co de and co py it to the clipbo ard:
Create a new file, right-click in line 1, and select Past e to paste the co pied co de:
CODE TO TYPE:
<table>
<tr>
<th>Name</th>
<th>Description</th>
<th>Duration</th>
<th>Due date</th>
<th>Complete</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @tasks.each do |task| %>
<tr>
<td><%= task.name %></td>
<td><%= task.description %></td>
<td><%= task.duration %></td>
<td><%= task.due_date %></td>
<td><%= task.complete %></td>
<td><%= link_to 'Show', task %></td>
<td><%= link_to 'Edit', edit_task_path(task) %></td>
<td><%= link_to 'Destroy', task, :confirm => 'Are you sure?', :method => :delete %>
</td>
</tr>
<% end %>
</table>
Save it as /railsapps/o st app/app/vie ws/t asks/_t askt able .ht m l.e rb. No w switch back to inde x.ht m l.e rb and
replace the HTML table co de with a call to the new partial:
CODE TO TYPE: app/views/tasks/index.html.erb
<h1>Listing tasks</h1>
<table>
<tr>
<th>Name</th>
<th>Description</th>
<th>Duration</th>
<th>Due date</th>
<th>Complete</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @tasks.each do |task| %>
<tr>
<td><%= task.name %></td>
<td><%= task.description %></td>
<td><%= task.duration %></td>
<td><%= task.due_date %></td>
<td><%= task.complete %></td>
<td><%= link_to 'Show', task %></td>
<td><%= link_to 'Edit', edit_task_path(task) %></td>
<td><%= link_to 'Destroy', task, :confirm => 'Are you sure?', :method => :delete %>
</td>
</tr>
<% end %>
</table>
<%= render "tasktable" %>
<%= render "copyright" %>
<br />
<%= link_to 'New Task', new_task_path %>
Save inde x.ht m l.e rb and then lo o k at the list o f All Tasks in the applicatio n. It's identical to the earlier versio n, but
no w the entire table co mes fro m a different file:
No w we can make the same change to the inco m ple t e .ht m l.e rb file:
CODE TO TYPE: app/views/tasks/inco mplete.html.erb
<h1>Incomplete tasks</h1>
<table>
<tr>
<th>Name</th>
<th>Description</th>
<th>Duration</th>
<th>Due date</th>
<th>Complete</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @tasks.each do |task| %>
<tr>
<td><%= task.name %></td>
<td><%= task.description %></td>
<td><%= task.duration %></td>
<td><%= task.due_date %></td>
<td><%= task.complete %></td>
<td><%= link_to 'Show', task %></td>
<td><%= link_to 'Edit', edit_task_path(task) %></td>
<td><%= link_to 'Destroy', task, :confirm => 'Are you sure?', :method => :delete %>
</td>
</tr>
<% end %>
</table>
<%= render "tasktable" %>
<%= render "copyright" %>
<br />
<%= link_to 'New Task', new_task_path %>
Save this change, and yo ur _t askt able .ht m l.e rb partial generates the HTML tables o n the Incomplete Tasks page:
Partials may co ntain the same kinds o f rich co ntent that yo u can put into normal template files. So partials are go o d fo r
sharing content, as well as code.
Sharing Code is Good
The two HTML tables lo o k just like they did befo re. By creating a partial, any future changes to the co de o nly need to be
made in o ne place, rather than two —o r three, o r fo ur, o r ten!
Let's wo rk thro ugh an example. Mo dify the _t askt able .ht m l.e rb partial. A user will pro bably want to kno w which
tasks will take a lo ng time to perfo rm, so let's highlight all the tasks that are expected to last lo nger than an ho ur.
Mo dify _t askt able .ht m l.e rb as sho wn:
CODE TO TYPE:
<table>
<tr>
<th>Name</th>
<th>Description</th>
<th>Duration</th>
<th>Due date</th>
<th>Complete</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @tasks.each do |task| %>
<tr <% if task.duration > 60 then%>bgcolor="MistyRose"<% end %>>
<td><%= task.name %></td>
<td><%= task.description %></td>
<td><%= task.duration %></td>
<td><%= task.due_date %></td>
<td><%= task.complete %></td>
<td><%= link_to 'Show', task %></td>
<td><%= link_to 'Edit', edit_task_path(task) %></td>
<td><%= link_to 'Destroy', task, :confirm => 'Are you sure?', :method => :delete %>
</td>
</tr>
<% end %>
</table>
Save it and relo ad the Listing tasks page:
This additio nal lo gic changed the backgro und co lo r o f the tasks that have a duratio n o f greater than 6 0 minutes.
Because we changed the co de in the _t askt able .ht m l.e rb partial, the change also appears o n the Incomplete tasks
page:
Adding a Second T able to a Page
The Incomplete Tasks page displays a single table o f the o utstanding tasks o n the system. What if we want to
add a seco nd table that sho ws the inco mplete tasks that are past their due date?
We can find this seco nd set o f inco mplete and o verdue tasks by adding a line to
/railsapps/o st app/app/co nt ro lle rs/t asks_co nt ro lle r.rb:
CODE TO TYPE:
class TasksController < ApplicationController
def index
@tasks = Task.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @tasks }
end
end
def incomplete
@tasks = Task.find_all_by_complete(false)
@overdue = Task.where(["due_date < ? and complete = ?", Date.today, false])
respond_to do |format|
format.html
format.xml { render :xml => @tasks }
end
end
def show
@task = Task.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @task }
end
end
def new
@task = Task.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @task }
end
end
def edit
@task = Task.find(params[:id])
end
def create
@task = Task.new(params[:task])
respond_to do |format|
if @task.save
format.html { redirect_to(@task, :notice => 'Task was successfully creat
ed.') }
format.xml { render :xml => @task, :status => :created, :location => @t
ask }
else
format.html { render :action => "new" }
format.xml { render :xml => @task.errors, :status => :unprocessable_ent
ity }
end
end
end
def update
@task = Task.find(params[:id])
respond_to do |format|
if @task.update_attributes(params[:task])
format.html { redirect_to(@task, :notice => 'Task was successfully updat
ed.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @task.errors, :status => :unprocessable_ent
ity }
end
end
end
def destroy
@task = Task.find(params[:id])
@task.destroy
respond_to do |format|
format.html { redirect_to(tasks_url) }
format.xml { head :ok }
end
end
end
This additio nal line o f co de will find all o f the tasks that are bo th inco mplete and past their due date:
OBSERVE:
@overdue = Task.where(["due_date < ? and complete = ?", Date.today, false])
This is the first time we've seen this type o f ActiveRecord co de. The T ask.whe re (...) functio n allo ws us to
find all the tasks in the database that satisfy a co mplex set o f co nditio ns. In this case, we want all o f the tasks
fo r which co m ple t e = f alse and due _dat e < t o day is true. The ? characters are placeho lders fo r the f alse
and Dat e .t o day values we want to find.
The co nditio ns are no t written in the Ruby language, but in a lo wer-level database language
called Structured Query Language, better kno wn by its initials SQL (pro no unced sequel). SQL is
beyo nd the sco pe o f this co urse, but the syntax is relatively straightfo rward.
Note
When the inco m ple t e .ht m l.e rb template is called, it will see two variables:
@ t asks is a list o f all o f the inco mplete tasks.
@ o ve rdue is list o f all o f the inco mplete tasks that are also past their due dates.
How Do We Display the Overdue T asks?
We have variables co ntaining the list o f inco mplete tasks, and the list o f o verdue inco mplete tasks, but can we use
_t askt able .ht m l.e rb to display bo th? The partial currently displays the co ntents o f o nly the @ t asks variable:
OBSERVE:
...
<% @tasks.each do |task| %>
<tr <% if task.duration > 60 then%>bgcolor="MistyRose"<% end %>>
<td><%= task.name %></td>
<td><%= task.description %></td>
<td><%= task.duration %></td>
<td><%= task.due_date %></td>
<td><%= task.complete %></td>
<td><%= link_to 'Show', task %></td>
<td><%= link_to 'Edit', edit_task_path(task) %></td>
<td><%= link_to 'Destroy', task, :confirm => 'Are you sure?', :method => :delete %>
</td>
</tr>
...
The partial will display the inco mplete tasks sto red in the @ t asks variable, but it wo n't display the o verdue tasks
sto red in @ o ve rdue . We can fix this pro blem by handing the data to the partial explicitly in a lo cal variable .
A lo cal variable is a variable that is visible to o nly a small amo unt o f co de—in this case we'll create a lo cal variable that
will be visible o nly to the _t askt able .ht m l.e rb partial. Instead o f using the @ t asks variable, we'll mo dify the
_t askt able .ht m l.e rb partial so that it displays the tasks sto red in a lo cal variable called t asks:
CODE TO TYPE: app/views/tasks/_tasktable.html.erb
<table>
<tr>
<th>Name</th>
<th>Description</th>
<th>Duration</th>
<th>Due date</th>
<th>Complete</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @taskstasks.each do |task| %>
<tr <% if task.duration > 60 then%>bgcolor="MistyRose"<% end %>>
<td><%= task.name %></td>
<td><%= task.description %></td>
<td><%= task.duration %></td>
<td><%= task.due_date %></td>
<td><%= task.complete %></td>
<td><%= link_to 'Show', task %></td>
<td><%= link_to 'Edit', edit_task_path(task) %></td>
<td><%= link_to 'Destroy', task, :confirm => 'Are you sure?', :method => :delete %>
</td>
</tr>
<% end %>
</table>
Lo cal variable names do n't begin with @ . The partial will need to be to ld each time which value to use fo r the lo cal
variable. That means that we'll need to change the co de in the inde x.ht m l.e rb and inco m ple t e .ht m l.e rb files:
CODE TO TYPE:
<h1>Listing tasks</h1>
<%= render :partial=>"tasktable", :locals=>{:tasks=>@tasks} %>
<%= render "copyright" %>
<br />
<%= link_to 'New Task', new_task_path %>
CODE TO TYPE: app/views/tasks/inco mplete.html.erb
<h1>Incomplete tasks</h1>
<%= render :partial=>"tasktable", :locals=>{:tasks=>@tasks} %>
<%= render "copyright" %>
<br />
<%= link_to 'New Task', new_task_path %>
No w that we pass the name o f a partial, as well as a set o f lo cals, we have to name o ur parameters. So , rather than
typing re nde r " t askt able " , we need to type re nde r :part ial=>" t askt able " .... Next, we pass the set o f lo cals using
:lo cals=>.... The :lo cals=>{:t asks=>@ t asks} co de tells the partial that it has a lo cal variable called t asks that will
have the same value as the @ t asks variable. As always in Ruby, because we are passing the name o f so mething to a
functio n, we do it with a symbol. Because the lo cal variable is called t asks, we pass the symbo l :t asks to the re nde r
functio n.
Once yo u've mo dified bo th inde x.ht m l.e rb and inco m ple t e .ht m l.e rb, save the files and then check to make sure
that the All Tasks and Incomplete Tasks pages still wo rk. They wo n't sho w any past-due tasks yet.
Why Do Local Variables Help?
Why did we intro duce a lo cal variable into the _t askt able .ht m l/e rb file? After all, the pages no w wo rk exactly the
same way they did befo re—the o nly difference is that we no w have a little mo re co de.
We added the lo cal variable to o ur _t askt able .ht m l.e rb partial to make it mo re reusable. Previo usly, the partial
wo uld o nly display the tasks sto red in the @ t asks variable. The @ t asks variable was defined just o nce per page, so
we co uldn't display two different tables in the same page.
No w that we specify the data the _t askt able .ht m l.e rb partial will display each time, we can use it to generate multiple
tables in the same page, with each co ntaining different data.
To see ho w this wo rks, mo dify inco m ple t e .ht m l.e rb as sho wn:
CODE TO TYPE: app/views/tasks/inco mplete.html.erb
<h1>Incomplete tasks</h1>
<%= render :partial=>"tasktable", :locals=>{:tasks=>@tasks} %>
<h2>Overdue</h2>
<%= render :partial=>"tasktable", :locals=>{:tasks=>@overdue} %>
<%= render "copyright" %>
<br />
<%= link_to 'New Task', new_task_path %>
No w we call the _t askt able .ht m l.e rb fo r a seco nd time o n the same page. This time the seco nd table passes the
overdue list o f tasks to the partials by setting the partial's lo cal t asks variable to the value o f the @ o ve rdue variable
that was created in the co ntro ller. To see the seco nd table, o pen a bro wser windo w and lo o k at the Incomplete Tasks
page.
Note
With the current data, yo u have no inco mplete tasks with a due date in the past. To make sure that the
table is wo rking, edit the Buy candy task and clear the Co m ple t e check bo x fo r it.
What Just Happened?
We've seen ho w partials can reduce duplicate co de and make yo ur applicatio n mo re manageable. When they're used
alo ngside templates and layo uts, partials help yo u create advanced HTML interfaces witho ut a significant increase the
amo unt o f co de yo u need to manage.
In this lesso n, we learned:
A partial is a page fragment that can be included in a template file.
Yo u include partials by calling the re nde r functio n.
The re nde r functio n evaluates the co ntents o f the partial and then returns a string, which can be inserted into
the template o utput.
When yo u create a partial, yo u can call it fro m several template files.
Lo cal variables increase the reusability o f partials.
Wo rk with partials so me mo re as yo u do the ho mewo rk assignement. Then meet me in the next lesso n!
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Logging In
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
secure yo ur applicatio n using authentication.
add authenticatio n to all o f yo ur metho ds using the before-filter.
Adding Security to the Incomplete T asks Page
We have a lo t o f features in o ur applicatio n no w, but there's o ne majo r feature missing: security.
All o f the data in o ur applicatio n can be edited by anybody, fro m anywhere, at any time. Under tho se co nditio ns, when
yo ur applicatio n is available o n the web, bad things can happen.
One way to secure yo ur applicatio n is with authentication which fo rces users to identify themselves. The first type o f
authenticatio n we'll go o ver is HTTP basic authentication. This kind o f security displays a username and passwo rd
dialo g bo x whenever yo u try to access a secure web page:
It takes a relatively small amo unt o f co de to add basic authenticatio n to yo ur applicatio n. Suppo se yo u want users to
lo g in befo re they can see the page displaying the list o f inco mplete tasks. Yo u can do that.
Mo dify /railsapps/o st app/app/co nt ro lle rs/t asks_co nt ro lle r.rb as sho wn:
CODE TO TYPE:
class TasksController < ApplicationController
def index
@tasks = Task.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @tasks }
end
end
def incomplete
authenticate_or_request_with_http_basic("Incomplete tasks") do |username, password|
username == 'admin' && password == 'kingFish'
end
if response_body == nil then
@tasks = Task.find_all_by_complete(false)
@overdue = Task.where(["due_date < ? and complete = ?", Date.today, false])
respond_to do |format|
format.html
format.xml { render :xml => @tasks }
end
end
end
def show
@task = Task.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @task }
end
end
...
This co de requires the user to sign in to the Incomplete Tasks page with the username adm in and the passwo rd
kingFish. Open o r refresh the applicatio n and go to the main list o f tasks. The list o f tasks appears just as it did
befo re:
Note
When yo u're writing co de to test HTTP authenticatio n, yo u may want to use a separate bro wser fro m the
o ne yo u use to access the O'Reilly Scho o l, because o nce yo u are lo gged in, yo u will stay lo gged in until
yo u clo se the bro wser. Yo u wo n't want to clo se do wn this lab, so if po ssible, access this lab in yo ur
usual bro wser, and use a different bro wser (Chro me, Safari, Firefo x...) to test yo ur applicatio n. Also , when
yo u lo g in, make sure yo u do n't ask the bro wser to remember the username and passwo rd. Yo u may
want to sign in with several different usernames and yo u wo n't be able to do this if the bro wser
remembers any previo us credentials.
When yo u click o n the link to display the inco mplete tasks, a username/passwo rd dialo g bo x appears. The dialo g bo x
that appears includes the re alm nam e . T his is t he st ring that we passed to the
aut he nt icat e _o r_re que st _wit h_ht t p_basic functio n:
OBSERVE:
authenticate_or_request_with_http_basic("Incomplete tasks") do |username, password|
username == 'admin' && password == 'kingFish'
It identifies the sectio n o f the website that is secured, which means that yo u can split yo ur website into separate
sectio ns that are secured independently. So , yo u might have a sectio n o f yo ur applicatio n that is accessible to
administrato rs o nly, a sectio n that is accessible to registered users, and a part o f yo ur applicatio n that is co mpletely
public.
How Basic Authentication Works
When yo u enter the URL o f a secured web page, the bro wser sends a request to the server. If a user asks to see the
Incomplete tasks page, Rails runs the inco m ple t e actio n-metho d in the t asks_co nt ro lle r. The co de we added
checks to see if there's a username and passwo rd present in the request.
If the bro wser includes a username and passwo rd, this co de checks to make sure that they're valid. If the username
and passwo rd are inco rrect o r no t present, the aut he nt icat e _o r_re que st _wit h_ht t p_basic functio n generates a
respo nse that tells the bro wser that the request was no t autho rized.
The first time yo ur bro wser tries to access the Incomplete Tasks page, it wo n't include a username o r passwo rd. The
server will respo nd immediately with a you-are-not-authorized respo nse. That's why the first time yo u go to the page,
the bro wser displays the username/passwo rd dialo g. The bro wser kno ws no w that it needs a username and
passwo rd, so it asks yo u fo r them. Once yo u sign in, the bro wser repeats the request, but this time it includes the
username and passwo rd yo u supplied.
If the username and passwo rd are co rrect, the aut he nt icat e _o r_re que st _wit h_ht t p_basic do esn't generate an
erro r respo nse. If there's no erro r, the co ntro ller runs the o riginal co de that generates the Incomplete Tasks page:
The co ntro ller co de checks fo r the existence o f an erro r respo nse by lo o king at the re spo nse _bo dy variable.
Whenever the applicatio n generates any kind o f respo nse, it sto res it in this variable. If the re spo nse _bo dy variable
still has the value nil, we kno w there was no authenticatio n erro r, so we can send back the Inco mplete Tasks page.
Once a user enters the co rrect username and passwo rd, the bro wser remembers them. If the server ever returns an
unautho rized respo nse fo r ano ther page in the same security do main (the place where yo ur applicatio n's files are
ho sted), the bro wser will make a seco nd request auto matically, including the username and passwo rd, witho ut asking
the user. This means that o nce yo u sign in, yo u will stay lo gged into an area o f the website until yo u clo se the bro wser,
pro vided that that area o f the website accepts the same username and passwo rd credentials.
Securing the Other T ask Pages
No w that yo u kno w ho w to secure a single actio n in the tasks co ntro ller, yo u'll want to secure all the o ther task pages.
Yo u can do this by repeating the same security co de at the start o f each o f the actio n metho ds, but that wo uld invo lve
duplicating co de. Fo rtunately, Rails has a feature that allo ws yo u to run a piece o f co de fo r a who le set o f metho ds.
Rails filters are functio ns that can be run each time Rails needs to access o ne o f the actio n-metho ds.
A before-filter is a functio n that Rails will run before it runs an actio n-metho d.
A befo re-filter wo rks like this:
1. A request co mes in that needs to run an actio n-metho d in the co ntro ller.
2. If a befo re-filter exists, Rails runs it.
3. If the befo re-filter generates any kind o f respo nse, Rails returns the respo nse and the actio n-metho d is
never called.
4. If the befo re-filter generates no respo nse, Rails co ntinues to run the actio n-metho d in the usual way.
So , if yo u put yo ur authenticatio n co de into a separate functio n, yo u can use that functio n as a befo re-filter fo r each o f
the actio n-metho ds in the co ntro ller. If the user hasn't pro vided a co rrect username and passwo rd, yo ur befo re-filter
will generate an erro r respo nse, and the actio n-metho d will no t be called.
In the app/co nt ro lle rs/t asks_co nt ro lle r.rb file, mo ve the security co de into a separate metho d named lo gin. We'll
register lo gin as a befo re-filter:
CODE TO TYPE:
class TasksController < ApplicationController
def index
@tasks = Task.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @tasks }
end
end
def incomplete
authenticate_or_request_with_http_basic("Incomplete tasks") do |username, password|
username == 'admin' && password == 'kingFish'
end
if response_body == nil then
@tasks = Task.find_all_by_complete(false)
@overdue = Task.where(["due_date < ? and complete = ?", Date.today, false])
respond_to do |format|
format.html
format.xml { render :xml => @tasks }
end
end
end
def show
@task = Task.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @task }
end
end
def new
@task = Task.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @task }
end
end
def edit
@task = Task.find(params[:id])
end
def create
@task = Task.new(params[:task])
respond_to do |format|
if @task.save
format.html { redirect_to(@task, :notice => 'Task was successfully created.') }
format.xml { render :xml => @task, :status => :created, :location => @task }
else
format.html { render :action => "new" }
format.xml { render :xml => @task.errors, :status => :unprocessable_entity }
end
end
end
def update
@task = Task.find(params[:id])
respond_to do |format|
if @task.update_attributes(params[:task])
format.html { redirect_to(@task, :notice => 'Task was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @task.errors, :status => :unprocessable_entity }
end
end
end
def destroy
@task = Task.find(params[:id])
@task.destroy
respond_to do |format|
format.html { redirect_to(tasks_url) }
format.xml { head :ok }
end
end
before_filter :login
def login
authenticate_or_request_with_http_basic("The task pages") do |username, password|
username == 'admin' && password == 'kingFish'
end
end
end
Yo u've mo ved the security co de fro m the inco m ple t e actio n-metho d, and placed it in its o wn functio n named lo gin.
The lo gin functio n is registered as a befo re-filter fo r the who le class with the line be f o re _f ilt e r :lo gin. The lo gin
functio n will no w run befo re any o f the actio n-metho ds are called. If the
aut he nt icat e _o r_re que st _wit h_ht t p_basic generates an erro r respo nse, Rails will no t run the actio n-metho d
and return the erro r back to the bro wser.
We need to add o ne mo re element to the befo re-filter in the co ntro ller. Mo dify t asks_co nt ro lle r.rb as sho wn:
CODE TO TYPE:
...
before_filter :login
private
def login
authenticate_or_request_with_http_basic("The task pages") do |username, password|
username == 'admin' && password == 'kingFish'
end
end
end
The privat e keywo rd hides the lo gin functio n fro m the o utside wo rld and prevents it fro m being called directly as an
actio n-metho d. Always use this security precautio n when yo u're using security filters. It reduces the ability o f an
attacker to create a request that will break thro ugh yo ur security co de.
T he before-filter Step by Step
Let's try o ut o ur new befo re-filter. Clo se the bro wser yo u're using to test the website to make sure that the
bro wser fo rgets any usernames and passwo rds that yo u sent previo usly. Then reo pen the bro wser and try to
access the main task page o f the website:
Rails received the request fo r the index page; because there was a befo re-filter registered o n the test
co ntro ller, it first ran the lo gin functio n:
The lo gin functio n checked fo r the existence o f a username and passwo rd in the request and fo und no ne, so
it returned a respo nse to the bro wser saying that the user wasn't lo gged in to this page:
So , the bro wser displays the username and passwo rd dialo g bo x.
Enter the username adm in and the passwo rd kingFish, and submit the request again:
The bro wser submits the request again, but this time it includes the username and passwo rd:
These match the credentials in the lo gin, so Rails runs the inde x actio n-metho d and returns the page
co rrectly.
If yo u try to access ano ther page, let's say the Incomplete Tasks, the bro wser remembers the username and
passwo rd, and can reuse them fo r any o f the pages in the same security do main.
The List ing t asks and Inco m ple t e t asks pages are lo cated within a realm named T he t ask page s. This
is the name pro vided by the co de in the lo gin functio n.
When yo u request the Inco m ple t e t asks page, the server returns an erro r respo nse that indicates that yo u
weren't lo gged in. Then the bro wser, auto matically, repeats the request and includes the username and
passwo rd that yo u've already pro vided. So o nce yo u're lo gged in to a website with HTTP basic authenticatio n,
yo u'll stay lo gged in until yo u clo se the bro wser.
Yo u've no w secured every task page in yo ur applicatio n using HTTP basic authenticatio n.
What Just Happened?
In this lesso n, we learned:
That Rails applicatio ns need security.
The simplest fo rm o f security is called HTTP basic authentication.
Yo u can add HTTP basic authenticatio n by calling the aut he nt icat e _o r_re que st _wit h_ht t p_basic
metho d.
This functio n checks fo r the existence o f a username and passwo rd in the request.
If the username and passwo rd are no t co rrect, the server returns an unauthorized erro r to the bro wser.
Yo u can apply authenticatio n to all o f the metho ds in yo ur co ntro ller by using a before-filter.
Befo re-filters are run befo re each actio n-metho d in the co ntro ller.
Befo re-filters need to be marked as privat e . This prevents wo uld-be attackers fro m accessing them directly.
No w that we're feeling mo re secure, I'm co nfident yo u'll do well o n the ho mewo rk.
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Managing Users
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
allo w multiple users to lo g in to yo ur applicatio ns simultaneo usly.
add security by creating an administrato r passwo rd.
display user info ramatio n.
Managing Multiple Users
We've created an applicatio n that allo ws a single user to lo g in with a username and passwo rd using HTTP
authenticatio n, but we pro bably want to allo w several different peo ple to lo g in simultaneo usly.
We co uld acco mplish that by mo difying the lo gin co de. Fo r example, to create a seco nd user, yo u can change the lo gin
functio n to take a seco nd username and passwo rd, like this:
OBSERVE:
def login
authenticate_or_request_with_http_basic("The task pages") do |username, password|
username == 'admin' && password == 'kingFish'
|| username == 'courtney' && password == 'palimpsest'
end
end
There's a pro blem with that appro ach tho ugh—every time yo u want to add a user to the system, yo u'll need to mo dify
this co de. That's no t practical. Ho w else might yo u do it?
Usernames and passwo rds are just data. Whenever we've had a set o f data to manage befo re, we've sto red the data
in the database and used scaffo lded web pages to edit it.
So let's create a users table and so me scaffo lded web pages. We need to pro vide a name fo r the table, and we need to
specify the name and data type o f each attribute that we want to sto re fo r each user. We'll reco rd a username, a
passwo rd, and the user's full name. Start a Terminal sessio n and enter this co mmand:
INTERACTIVE SESSION:
cold1:~$ cd railsapps/ostapp/
cold1:~/railsapps/ostapp$ rails generate scaffold User full_name:string username:string
password:string
invoke active_record
create
db/migrate/20121114134326_create_users.rb
create
app/models/user.rb
invoke
test_unit
create
test/unit/user_test.rb
create
test/fixtures/users.yml
route resources :users
invoke scaffold_controller
create
app/controllers/users_controller.rb
invoke
erb
create
app/views/users
create
app/views/users/index.html.erb
create
app/views/users/edit.html.erb
create
app/views/users/show.html.erb
create
app/views/users/new.html.erb
create
app/views/users/_form.html.erb
invoke
test_unit
create
test/functional/users_controller_test.rb
invoke
helper
create
app/helpers/users_helper.rb
invoke
test_unit
create
test/unit/helpers/users_helper_test.rb
invoke stylesheets
identical
public/stylesheets/scaffold.css
cold1:~/railsapps/ostapp$
We're generating co de, so we're using the rails co mmand. Scaffo lding creates all o f the web pages yo u need, as well
as the co ntro ller co de, the mo del co de, and the migratio n script to create a table in the database. A migratio n,
remember, is a script that's used to mo dify the structure o f the database. Altho ugh the scaffo lding co mmand will create
the migratio n, it wo n't run it. In o rder to do that, we need to use the rake co mmand:
INTERACTIVE SESSION:
cold1:~/railsapps/ostapp$ rake db:migrate
(in /users/dgriffit1/railsapps/ostapp)
== CreateUsers: migrating ====================================================
-- create_table(:users)
-> 0.0104s
== CreateUsers: migrated (0.0110s) ===========================================
cold1:~/railsapps/ostapp$
Whenever we want to manage the applicatio n's enviro nment, we use the rake co mmand. Here we're using rake to run
the new migratio n scripts we just created. Yo u've created the table, and yo u can no w access the page at
ht t p://username.o re illyst ude nt .co m /o st app/use rs, and create a new user as sho wn:
Are we fo rgetting so mething? We need to make sure that the user pages themselves are secured. If we do n't, anyo ne
co uld create their o wn user acco unt and then use it to lo g in to the main system.
We can secure the user pages exactly the same way we secured the task pages: by mo difying the co ntro ller. Open
/railsapps/o st app/app/co nt ro lle rs/use rs_co nt ro lle r.rb and make these changes:
CODE TO TYPE:
class UsersController < ApplicationController
def index
@users = User.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @users }
end
end
def show
@user = User.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @user }
end
end
def new
@user = User.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @user }
end
end
def edit
@user = User.find(params[:id])
end
def create
@user = User.new(params[:user])
respond_to do |format|
if @user.save
format.html { redirect_to(@user, :notice => 'User was successfully created.') }
format.xml { render :xml => @user, :status => :created, :location => @user }
else
format.html { render :action => "new" }
format.xml { render :xml => @user.errors, :status => :unprocessable_entity }
end
end
end
def update
@user = User.find(params[:id])
respond_to do |format|
if @user.update_attributes(params[:user])
format.html { redirect_to(@user, :notice => 'User was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @user.errors, :status => :unprocessable_entity }
end
end
end
def destroy
@user = User.find(params[:id])
@user.destroy
respond_to do |format|
format.html { redirect_to(users_url) }
format.xml { head :ok }
end
end
before_filter :login
private
def login
authenticate_or_request_with_http_basic("The admin area") do |username, password|
username == 'admin' && password == 'kingFish'
end
end
end
We set the realm name to "The admin area," which will be displayed fo r the user in the lo gin dialo g bo x.
Yo u've created a user in the database and yo u've secured the administrato r pages. Next, yo u need to mo dify the
t ask_co nt ro lle r so it validates the username and passwo rd against the data that yo u sto red in the users table.
Authenticating Against the Database
Currently, the t ask_co nt ro lle r authenticates against a single username and passwo rd. This is the existing lo gin co de
in the t ask_co nt ro lle r:
OBSERVE: app/co ntro llers/tasks_co ntro ller.rb
before_filter :login
private
def login
authenticate_or_request_with_http_basic("The task pages") do |username, password|
username == 'admin' && password == 'kingFish'
end
end
end
Yo u need to mo dify this co de so that instead o f checking the username and passwo rd variables against static values,
it checks them against the data sto red in the use rs table. Yo u can find the user reco rd in the database, using the
username like this:
OBSERVE:
user = User.find_by_username(username)
If the user reco rd exists in the database, this expressio n sto res it in the use r variable. If the user do esn't exist in the
database, the use r variable is set to the value nil. nil is a special Ruby value that means no value.
Yo u need to check the use r variable to make sure that it's no t nil, and that its passwo rd matches the o ne pro vided by
the user.
Mo dify app/co nt ro lle rs/t asks_co nt ro lle r.rb as sho wn:
CODE TO TYPE:
...
before_filter :login
private
def login
authenticate_or_request_with_http_basic("The task pages") do |username, password|
username == 'admin' && password == 'kingFish'
user = User.find_by_username(username)
user != nil && password == user.password
end
end
end
Once this co de is in place, try it o ut. Open a separate bro wser o n o ne o f the task pages, and then type in an invalid
username and passwo rd, like this:
No w, enter the username (co urtney) and the passwo rd (palimpsest) that yo u created in the database.
If yo u sign in co rrectly, yo u will have full access to the T ask pages.
Displaying the Current User in the Application
When yo u sign in, it might be useful to see yo ur current user's name appearing so mewhere o n each o f the web pages.
In o rder to display the current user, yo u need to make the data available to the page templates. The existing co de
sto res the user in a variable named use r. To make this data visible to the page templates, yo u'll need to prefix it with
an @ symbo l. Edit /railsapps/o st app/app/co nt ro lle rs/t asks_co nt ro lle r.rb to rename the use r variable:
CODE TO TYPE:
...
before_filter :login
private
def login
authenticate_or_request_with_http_basic("The task pages") do |username, password|
user = User.find_by_username(username)
@user = User.find_by_username(username)
user != nil && password == user.password
@user != nil && password == @user.password
end
end
end
No w, when a user lo gs in, their details will be sto red in the @ use r variable. Variables with names that begin with the @
symbo l are visible to page template files, so they can use the @ use r variable in o ur generated web pages. So , which
pages sho uld we mo dify? We co uld edit each o f the separate page template files and add in info rmatio n abo ut the
current user, but we do n't need to do that. If we mo dify the Task layout file, we can make sure that the user info rmatio n
appears o n each o f the task pages. Mo dify /railsapps/o st app/app/vie ws/layo ut s/t asks.ht m l.e rb as sho wn:
CODE TO TYPE:
<!DOCTYPE html>
<html>
<head>
<title>Ostapp</title>
<%= stylesheet_link_tag :all %>
<%= javascript_include_tag :defaults %>
<%= csrf_meta_tag %>
</head>
<body>
<div style="float: right">
Signed in as: <strong><%= @user.full_name %></strong>
</div>
<img src="http://www.oreillyschool.com/images/OST_Logo.gif"/>
<br/>
<div style="display:table-cell; width: 200px;">
<h1>TASKS</h1>
<ul>
<li><%= link_to_unless_current 'All tasks', :tasks %></li>
<li><%= link_to_unless_current 'Incomplete tasks', :tasks_incomplete %></li>
<li><%= link_to_unless_current 'New task', :new_task %></li>
</ul>
</div>
<div style="display:table-cell">
<%= yield %>
</div>
</body>
</html>
By mo difying the layo ut, we make sure that the same change will apply to all o f the task pages. Refresh the bro wser
that's lo gged in to o ne o f the task pages; yo u'll see yo ur current user's full name at the to p right co rner o f the screen.
The applicatio n no w suppo rts multiple users. They can be managed by an administrato r, who can enter and update all
o f the user info rmatio n. Each o f tho se users can lo g in to mo dify the tasks that are held o n the system.
Note
In a co mplete, ro bust applicatio n, yo u wo uld add mo re validatio n checking; fo r example, checking fo r the
presence o f a username and passwo rd when a user is created o r updated, and a check to validate the
uniqueness o f usernames.
What Just Happened?
In this lesso n, we learned:
If yo u want to suppo rt multiple lo gins, sto re the user info rmatio n in the database.
Yo u can create the use r pages and table with scaffo lding.
Yo u need to make sure that yo ur user pages are secure with an administrato r passwo rd.
Yo u need to make sure that yo ur user pages have a different security level fro m yo ur o ther pages by
pro viding a unique bo dy to the authenticate_o r_request_with_http_basic co mmand.
When yo u're checking the lo gin info rmatio n pro vided by the user, yo u need to make sure that their user
reco rd exists and their passwo rd is co rrect.
If yo u want to display the current user's info rmatio n o n the web pages, sto re it in a variable that begins with
the @ symbo l.
By mo difying the tasks layo ut, yo u can display the current user's info rmatio n o n all o f the task pages.
So no w yo u kno w. Practice using these new skills and I'll catch up with yo u in o ur next lesso n...
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Associating Data with Users
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
use ActiveRecord to co nnect users to their tasks.
Giving Users T heir Own T asks
Our applicatio n no w requires users to lo g in befo re they have access to the list o f tasks, but it's the same list o f tasks
fo r each user. We want users to see o nly the tasks they created, so we've still go t so me wo rk to do .
Mo st applicatio ns o n the web need to perso nalize data—if yo u check yo ur email, yo u want to see your email, no t
so meo ne else's. In this lab, we'll lo o k at ways that ActiveRecord can asso ciate o ne set o f data with ano ther so that yo u
can co nnect a user to her tasks.
Database Relationships
Our Task pages no w use two different kinds o f data: tasks and users, but we do n't have any way yet o f asso ciating a
user with the list o f tasks that they created.
In ActiveReco rd, we co nnect pieces o f data to gether using database relationships. A database relatio nship is a
co nnectio n fro m a reco rd in o ne table with o ne o r mo re reco rds in ano ther table; a database relatio nship co uld be
used to jump fro m a task to the user who o wns the task.
Ho w are relatio nships fo rmed between reco rds? Ho w do es the database indicate that Courtney Jones is the o wner o f
the Get a haircut task? It do es it with ids.
Each reco rd that is sto red in the database has an id value sto red with it. The id is a number that identifies the reco rd in
the table. Yo u do n't need to set the id yo urself because ActiveRecord do es it fo r yo u auto matically, every time yo u save
a new reco rd. Rails uses an id when it needs to display a single task:
ids are also useful if yo u want to co nnect a reco rd in o ne table to a reco rd in ano ther table. Fo r example, if yo u want to
say that the Stock up on coffee task belo ngs to the user Courtney Jones, yo u can do it by reco rding Co urtney's id
against the task reco rd:
By sto ring a user's id against a task reco rd, yo u can reco rd which users o wn which tasks.
Keys and Foreign Keys
In database pro gramming, the id that's reco rded against each reco rd in the database is called a key, because
it gives yo u access to the reco rd. If we want to reco rd a user-id o n a task, the user-id is called a f o re ign ke y.
It's called a foreign key because the user-id is a key in a different, o r fo reign, table.
In ActiveReco rd, the names yo u give to keys and fo reign keys are impo rtant. Keys are generally called id, and
Rails manages them fo r yo u. If yo u add a fo reign key, always name it name of foreign data_id:
OBSERVE:
user_id
In the co de abo ve, the fo reign key use r_id refers to the id in the use r table.
That means if yo u want to sto re the id o f a user in the tasks table, yo u sho uld call it use r_id. There are ways
to tell ActiveReco rd that yo u want to use a different name, but Rails develo pers almo st never do this, because
o f the principle o f Convention over Configuration. Stro ng naming co nventio ns allo w Rails to determine what
the applicatio n needs to do . When Rails sees the name use r_id, it kno ws that it refers to the id key o f the
Use r table.
In o rder to asso ciate users with tasks, yo u'll need to add an extra attribute to the tasks called use r_id.
Add the user_id with a Migration
We created the user and task pages with scaffo lding. That generated a bunch o f pages and Ruby co de fo r us—
including migrations. Migratio ns are the scripts that created the tables in the database.
Migratio ns aren't just used fo r creating tables tho ugh. A migratio n can be used to make any kind o f structural change to
the database. If yo u want to add an attribute to a data-type, create a table, o r delete a table, do it with a migratio n.
If we want to add a use r_id to the tasks table, we'll need to do it with a migratio n.
Whenever we need to generate co de, we do it with the rails co mmand. We've used rails several times befo re. In mo st
cases (with the exceptio n o f running the Rails co nso le), the rails co mmand has generated co de fo r us, but the rails
co mmand can generate so many different types o f co de, that it's easy to fo rget the fo rmat o f each co mmand. Lat's go
into the applicatio n directo ry and ask rails fo r help:
INTERACTIVE SESSION:
cold1:~$ cd railsapps/ostapp/
cold1:~/railsapps/ostapp$ rails -h
Usage: rails COMMAND [ARGS]
The most common rails commands are:
generate
Generate new code (short-cut alias: "g")
console
Start the Rails console (short-cut alias: "c")
server
Start the Rails server (short-cut alias: "s")
dbconsole
Start a console for the database specified in config/database.yml
(short-cut alias: "db")
new
Create a new Rails application. "rails new my_app" creates a
new application called MyApp in "./my_app"
In addition to those, there are:
application Generate the Rails application code
destroy
Undo code generated with "generate"
benchmarker See how fast a piece of code runs
profiler
Get profile information from a piece of code
plugin
Install a plugin
runner
Run a piece of code in the application environment
All commands can be run with -h for more information.
We want to ge ne rat e a migratio n script to change the database, so let's find o ut mo re abo ut the ge ne rat e o ptio ns:
INTERACTIVE SESSION:
cold1:~/railsapps/ostapp$ rails generate -h
Usage: rails generate GENERATOR [args] [options]
General options:
-h, [--help]
# Print generator's options and usage
-p, [--pretend] # Run but do not make any changes
-f, [--force]
# Overwrite files that already exist
-s, [--skip]
# Skip files that already exist
-q, [--quiet]
# Suppress status output
Please choose a generator below.
Rails:
controller
generator
helper
integration_test
mailer
migration
model
observer
performance_test
plugin
resource
scaffold
scaffold_controller
session_migration
stylesheets
T ip
To repeat a co mmand yo u've typed befo re in a terminal sessio n, press the up-arro w key. Then yo u can edit
the co mmand. Fo r example, to repeat the rails ge ne rat e -h co mmand abo ve and change it to the
co mmand belo w, press the up-arro w key o nce, and then use the left and right arro w keys to mo ve within
the co mmand line to insert the wo rd m igrat io n. Yo u can cycle thro ugh all co mmands entered in the
current sessio n with the up and do wn arro w keys.
The generators are built-in to o ls that generate co de. We want to generate a m igrat io n; find o ut ho w by typing this
co de:
INTERACTIVE SESSION:
cold1:~/railsapps/ostapp$ rails generate migration -h
Usage:
rails generate migration NAME [field:type field:type] [options]
Options:
-o, --orm=NAME # Orm to be invoked
# Default: active_record
Runtime options:
-s, [--skip]
# Skip files that already exist
-p, [--pretend] # Run but do not make any changes
-q, [--quiet]
# Supress status output
-f, [--force]
# Overwrite files that already exist
Description:
Create rails files for migration generator.
cold1:~/railsapps/ostapp$
So to generate o ur migratio n, we need to give the migratio n a NAME and also say which fields (co lumns) we want to
add. Enter this co mmand to create a migratio n to add a use r_id co lumn to the t asks table:
INTERACTIVE SESSION:
cold1:~/railsapps/ostapp$ rails generate migration AddUserIdColumnToTasks user_id:integ
er
invoke active_record
create
db/migrate/20141104110018_add_user_id_column_to_tasks.rb
cold1:~/railsapps/ostapp$
OBSERVE:
AddUserIdColumnToTasks user_id:integer
Rails determines fro m the name o f the migratio n what the migratio n script needs to do . In this case, because the
migratio n name begins with the wo rd Add, we pro bably want to add a co lumn to a table. The name ends with
T o T asks, which tells Rails to add the co lumn to the t asks table. After the name, we have the specificatio n fo r the
co lumn that we want to add to the table, which is an int e ge r value named use r_id.
When yo u generate the Rails migratio n, yo u do n't change the table—yo u create a script. Yo u can view the script in the
db/m igrat e fo lder; lo o k fo r a name like this:
The filename always begins with a timestamp, so yo ur filename wo n't lo o k exactly like this example. The rest o f the
filename co mes fro m the name we gave to the migratio n. The migratio n file co ntains this co de:
OBSERVE:
class AddUserIdColumnToTasks < ActiveRecord::Migration
def self.up
add_column :tasks, :user_id, :integer
end
def self.down
remove_column :tasks, :user_id
end
end
Migratio n scripts co ntain two functio ns:
The se lf .up functio n which makes the database change. This functio n co ntains the co mmand to add a
co lumn to the task table named use r_id o f type int e ge r.
The se lf .do wn functio n which can reverse the database change. This functio n will o nly be run if an
administrato r decides to undo a migratio n.
In o rder to run the migratio n and create the use r_id co lumn o n the table, we need to run the co mmand rake
db:m igrat e . In general, the rake co mmand is used to mo dify the develo pment enviro nment o n yo ur machine:
INTERACTIVE SESSION:
cold1:~/railsapps/ostapp$ rake db:migrate
(in /users/dgriffit1/railsapps/ostapp)
== AddUserIdColumnToTasks: migrating =========================================
-- add_column(:tasks, :user_id, :integer)
-> 0.0080s
== AddUserIdColumnToTasks: migrated (0.0082s) ================================
cold1:~/railsapps/ostapp$
This co mmand runs all o utstanding migratio ns. The o ther migratio n scripts in db/m igrat e have already been run, so
rake will o nly run the migratio n yo u just created. Once the migratio n has run, the user_id co lumn sho uld be in the
t asks table. To verify this, run the Rails co nso le and view the attributes o n the tasks table by typing T ask:
INTERACTIVE SESSION:
cold1:~/railsapps/ostapp$ rails console
Loading development environment (Rails 3.0.3)
irb(main):001:0> Task
=> Task(id: integer, name: string, description: text, duration: integer, due_date: date
, complete: boolean, created_at: datetime,
updated_at: datetime, user_id: integer)
irb(main):002:0> quit
cold1:~/railsapps/ostapp$
No w that we've created the use r_id attribute, we need to asso ciate it with the id attribute o n the use rs table.
Joining T ables T ogether
To asso ciate the use r_id attribute o f the t asks table with the id attribute o f the use r table, we need to create a
database relatio nship. Database relatio nships are specified in the mo del co de. Mo dify
/railsapps/o st app/app/m o de ls/t ask.rb as sho wn:
CODE TO TYPE:
class Task < ActiveRecord::Base
belongs_to :user
validates :description, :name, :duration, :presence => true
validates :name, :uniqueness => true
validates_numericality_of :duration, {:greater_than_or_equal_to => 1, :less_than_or_e
qual_to => 240}
validate :due_in_future, :on=>:create
def due_in_future
if due_date < Date.today then
errors.add(:due_date, "cannot be in the past")
end
end
end
The be lo ngs_t o line creates a relatio nship between the t asks table and the use rs table. Specifying a relatio nship
makes it easier to set the use r_id o f a task reco rd to the co rrect value, and jump fro m a t ask to the use r who o wns it.
Setting the user_id of a T ask
To see ho w to use the new relatio nship, go to a Unix shell and start the Rails co nso le.
WARNING
If yo u're already inside the Rails co nso le, exit the co nso le and then restart it to make sure that yo u
pick up the new changes to the task mo del.
No w, in the Rails co nso le, let's find a user reco rd fro m the database. It do esn't matter which user we find—fo r no w
we'll read the first user o n the database:
INTERACTIVE SESSION:
cold1:~/railsapps/ostapp$ rails console
Loading development environment (Rails 3.0.3)
irb(main):001:0> a_user = User.first
=> #<User id: 1, full_name: "Courtney Jones", username: "courtney", password: "palimpse
st", created_at: "2014-11-03 11:53:48", updated_at: "2014-11-03 11:53:48">
irb(main):002:0>
Do n't clo se the co nso le sessio n! This line o f co de sto res Co urtney's user details in the a_use r variable. No w that we
have a user fro m the database, we can asso ciate it with a task. Let's read the first task fro m the database and sto re it in
a variable called a_t ask:
INTERACTIVE SESSION:
irb(main):002:0> a_task = Task.first
=> #<Task id: 17, name: "Buy eggs", description: "Go to the market and buy six eggs.\r\
n\r\nTry to get or...", duration: 60, due_date: "2015-12-12", complete: false, created_
at: "2013-02-14 16:00:28", updated_at: "2013-02-20 16:45:33", user_id: nil>
irb(main):003:0>
No w we have a task and the user o bject that we've read fro m the database, so we can use the database relatio nship to
asso ciate the user with the task. If we didn't have that relatio nship, we co uld asso ciate the two o bjects by setting the
use r_id attribute o f the task to the id o f the user o bject, like this:
Even tho ugh this is a pretty small piece o f co de, yo u can still run into pro blems, especially if yo u play aro und much
with the ids. Every reco rd yo u read fro m the database will have an id attribute and it's easy to mix them up. The
be lo ngs_t o relatio nship helps to simplify this co de. Instead o f lo o king at the ids o f two o bjects, we can set the task's
user, like this:
This co de do es exactly the same thing as the previo us co de. It sets the use r_id o f the task to the id o f the user.
Ho wever, because the co de uses the relatio nship, we do n't need to refer to the ids o f the task and user o bjects. That
makes the co de mo re readable. Let's try the co de o n the co nso le:
INTERACTIVE SESSION:
irb(main):003:0> a_task.user = a_user
=> #<User id: 1, full_name: "Courtney Jones", username: "courtney", password: "palimpse
st", created_at: "2013-02-26 14:36:20", updated_at: "2013-02-26 14:36:20">
irb(main):004:0>
If yo u view the attributes o f a_t ask, yo u'll see that the use r_id has been set co rrectly:
INTERACTIVE SESSION:
irb(main):004:0> a_task
=> #<Task id: 17, name: "Buy eggs", description: "Go to the market and buy six eggs.\r\
n\r\nTry to get or...", duration: 60, due_date: "2015-12-12",
complete: false, created_at: "2013-02-14 16:00:28", updated_at: "2013-02-20 16:45:33",
user_id: 1>
irb(main):005:0>
Finding a T ask's User
The be lo ngs_t o relatio nship allo ws us to asso ciate a user with a task mo re efficiently, but it saves just a small
amo unt o f co de. If yo u already have a task, and yo u want to find o ut mo re abo ut its user, then the relatio nship will save
yo u a lot o f co de.
Let's say yo u already have a task sto red in the a_t ask variable, and yo u want to find the full name o f the task's user.
Without the relatio nship, yo u wo uld have to read the user reco rd fro m the database, using co de that lo o ks like this:
With the relatio nship, we can find the full name o f the task's user:
Yo u can leap straight fro m the a_t ask to the user with the expressio n a_t ask.use r. Yo u do n't have to tell
ActiveReco rd to perfo rm a seco nd read fro m the database—yo u can access the task's user as if that user was an
attribute o f the task:
INTERACTIVE SESSION:
irb(main):005:0> a_task.user.full_name
=> "Courtney Jones"
irb(main):006:0> quit
Note
Because o f the be lo ngs_t o relatio nship, yo u do n't have to read the user fro m the database explicitly
because ActiveReco rd will do it implicitly fo r yo u. ActiveReco rd will read the user details the f irst t im e
yo u ask fo r them. It will then cache them in memo ry, so , if yo u ask fo r the task's user a seco nd time,
ActiveReco rd can avo id reading them again. All o f this magic is hidden fro m yo u by the relatio nship.
The be lo ngs_t o relatio nship allo ws us to specify a task's o wner witho ut messing with ids, and helps yo u to find the
details o f a task's o wner witho ut having to make a separate call to the database. Once we've read the task fro m the
database, we have lo cated its o wner.
No w we can mo dify the applicatio n so that each user sees o nly the tasks that they've created.
What Just Happened?
In this lesso n, we learned:
All reco rds in the database have an id.
The id is called a key because it gives yo u access to the data.
Yo u can make o ne piece o f data, like a task, refer to ano ther piece o f data, like a user, by sto ring the user's
id o n the task.
The user-id sto red o n the task is called a foreign key.
ids can be tricky to manage.
A database relatio nship pro vides an easier way to asso ciate pieces o f data with each o ther.
A belongs_to relatio nship asso ciates a piece o f data with ano ther piece o f data that owns it.
Once yo u have a relatio nship, it will manage the ids fo r yo u.
The belongs_to relatio nship will read the owner data implicitly fro m the database when yo u need it.
Wo rk thro ugh so me relatio nship issues in the ho mewo rk and I'll see yo u in the next lesso n!
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Creating Tasks for a User
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
set the o wner o f a piece o f data in the co ntro ller.
co nnect users to their tasks o utside o f the Rails co nso le.
Recording Who Created a T ask
We've learned ho w ActiveReco rd relatio nships help us co nnect data fro m different tables to gether. We created a
be lo ngs_t o relatio nship between Use rs and T asks, and no w we can co nnect tasks and their o wners, like this:
So far, we've used this co de to asso ciate users and tasks o n the Rails co nso le. In this lab, we'll mo dify the applicatio n
so that each time a user creates a task, that user will be reco rded as the task's o wner. In o rder to do that, we'll need to
set the use r attribute o f the task when the task is created.
Determining Which Code Creates a T ask
In o rder to set the user o f each new task, mo dify the co de that creates a new task and saves it to the database.
Yo u can find the co de that needs to be mo dified by o bserving what happens when a user submits a new task.
Go to the New Task fo rm and view the so urce o f the web page:
The new task fo rm sends a po st request to the /o st app/t asks path. A po st is o ne way that web bro wsers
send fo rm data to the server, ano ther is by using ge t requests, which reco rd all o f the data in the URL. ge t
and po st s are request methods.
When Rails receives the request fro m the fo rm, it determines which co de to run using ro ut e s. Let's take a
clo ser lo o k at ro utes; o pen a Unix sessio n, change into the applicatio n directo ry, and view the ro utes using
the rake ro ut e s co mmand:
INTERACTIVE SESSION:
cold1:~/.build$ cd ~/railsapps/ostapp/
cold1:~/railsapps/ostapp$ rake routes
(in /users/dgriffit1/railsapps/ostapp)
users GET
/users(.:format)
ller=>"users"}
POST
/users(.:format)
oller=>"users"}
new_user GET
/users/new(.:format)
er=>"users"}
edit_user GET
/users/:id/edit(.:format)
ler=>"users"}
user GET
/users/:id(.:format)
ler=>"users"}
PUT
/users/:id(.:format)
oller=>"users"}
DELETE /users/:id(.:format)
roller=>"users"}
tasks_incomplete GET
/tasks/incomplete(.:format)
ontroller=>"tasks"}
tasks GET
/tasks(.:format)
ller=>"tasks"}
POST
/tasks(.:format)
oller=>"tasks"}
new_task GET
/tasks/new(.:format)
er=>"tasks"}
edit_task GET
/tasks/:id/edit(.:format)
ler=>"tasks"}
task GET
/tasks/:id(.:format)
ler=>"tasks"}
PUT
/tasks/:id(.:format)
oller=>"tasks"}
DELETE /tasks/:id(.:format)
roller=>"tasks"}
cold1:~/railsapps/ostapp$
{:action=>"index", :contro
{:action=>"create", :contr
{:action=>"new", :controll
{:action=>"edit", :control
{:action=>"show", :control
{:action=>"update", :contr
{:action=>"destroy", :cont
{:action=>"incomplete", :c
{:action=>"index", :contro
{:action=>"create", :contr
{:action=>"new", :controll
{:action=>"edit", :control
{:action=>"show", :control
{:action=>"update", :contr
{:action=>"destroy", :cont
Each ro ute has bo th a path and a method. Rails will use both the metho d (POST ) and the path to find a
matching ro ute.
The bro wser sends the New Task data to /o st app/t asks, but the O'Reilly servers are co nfigured so that all
the paths begin with /o st app. So , Rails will lo o k fo r a POST ro ute that matches just the /t asks po rtio n o f the
path.
This is the ro ute it will find:
If a user submits the details o f a new task fro m the web, Rails runs the cre at e actio n-metho d in the T asks
co ntro ller. Open the app/co nt ro lle rs/t asks_co nt ro lle r.rb file and take a lo o k at that actio n metho d:
railsapps/o stapp/app/co ntro llers/tasks_co ntro ller.rb
...
def create
@task = Task.new(params[:task])
respond_to do |format|
if @task.save
format.html { redirect_to(@task, :notice => 'Task was successfully creat
ed.') }
format.xml { render :xml => @task, :status => :created, :location => @t
ask }
else
format.html { render :action => "new" }
format.xml { render :xml => @task.errors, :status => :unprocessable_ent
ity }
end
end
end
...
The cre at e actio n metho d co nve rt s t he f o rm dat a int o a t ask. Then it de cide s what re spo nse is
ne e de d (usually, it's HTML). Then, it at t e m pt s t o save t he t ask t o t he dat abase and, if succe ssf ul,
se nds t he HT ML page displaying t he ne w t ask t o t he bro wse r.
In the cre at e actio n metho d, each new T ask o bject gets created and saved into the database. We wo uld also
set the user fo r each new task in the cre at e metho d. To set the user, we use the same kind o f co de we tried
o ut in the co nso le. Once we've created a brand new task o bject, we set the use r attribute o f the task to the
user who 's lo gged in. Fo rtunately, when a user lo gs in, we sto re their user details in the @ use r variable. This
was the co de we added earlier to make sure the user was lo gged in befo re they co uld access the task pages.
The lo gin metho d is lo cated at the end o f the tasks co ntro ller:
railsapps/o stapp/app/co ntro llers/tasks_co ntro ller.rb
before_filter :login
private
def login
authenticate_or_request_with_http_basic("The task pages") do |username, pa
ssword|
@user = User.find_by_username(username)
@user != nil && password == @user.password
end
end
end
In this metho d, the use r inf o rm at io n is sto red in the @ use r variable.
Note
The @ character in @user is impo rtant. Variables with names that begin with @ are called
instance variables. They can be read by co de in a page template file. Also , they can be read by
o ther actio n-metho ds in the co ntro ller. Because we sto red the current user in a variable called
@ use r, we can read its value in the cre at e metho d. If we'd sto red it in a variable named use r
instead, we wouldn't be able to read it.
To set the user o f a newly created task, add this co de to the cre at e actio n-metho d:
railsapps/o stapp/app/co ntro llers/tasks_co ntro ller.rb
class TasksController < ApplicationController
def index
@tasks = Task.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @tasks }
end
end
def incomplete
@tasks = Task.find_all_by_complete(false)
@overdue = Task.where(["due_date < ? and complete = ?", Date.today, false])
respond_to do |format|
format.html
format.xml { render :xml => @tasks }
end
end
def show
@task = Task.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @task }
end
end
def new
@task = Task.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @task }
end
end
def edit
@task = Task.find(params[:id])
end
def create
@task = Task.new(params[:task])
@task.user = @user
respond_to do |format|
if @task.save
format.html { redirect_to(@task, :notice => 'Task was successfully creat
ed.') }
format.xml { render :xml => @task, :status => :created, :location => @t
ask }
else
format.html { render :action => "new" }
format.xml { render :xml => @task.errors, :status => :unprocessable_ent
ity }
end
end
end
def update
@task = Task.find(params[:id])
respond_to do |format|
if @task.update_attributes(params[:task])
format.html { redirect_to(@task, :notice => 'Task was successfully updat
ed.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @task.errors, :status => :unprocessable_ent
ity }
end
end
end
def destroy
@task = Task.find(params[:id])
@task.destroy
respond_to do |format|
format.html { redirect_to(tasks_url) }
format.xml { head :ok }
end
end
before_filter :login
private
def login
authenticate_or_request_with_http_basic("The task pages") do |username, pass
word|
@user = User.find_by_username(username)
@user != nil && password == @user.password
end
end
end
That single line o f co de sets the use r_id value o f every new task that is created. Let's try it o ut.
Open a separate bro wser windo w and go to the task pages o f yo ur applicatio n. Yo u'll need to lo g in with a
username and passwo rd that yo u've created in the use rs table.
Note
If yo u can't remember the username and passwo rd o f an existing user, lo g in to the user
administratio n pages at ht t p://login-id.o re illyst ude nt .co m /o st app/use rs/ with the
admin/kingFish username and passwo rd, and either find an existing user o r create a new o ne.
Once yo u've lo gged in to the task pages, go to the New Task page and enter a new task:
Note
Even tho ugh we've added a use r_id attribute to all the tasks, we do n't see the user-id o n any o f
the pages. Because we generated the pages earlier and o nce Rails has generated a page, it
wo n't go back and change it later. We'll learn ho w to mo dify pages and fo rms to include any new
attributes in a later lesso n.
When yo u submit the new task, Rails runs the cre at e actio n-metho d in the t asks co ntro ller, which creates a
new task based o n task data fro m the fo rm. Yo ur new co de sets the use r o f the task just befo re the task is
sto red in the database.
To make sure that the new co de wo rked, yo u can either read all o f the data fro m the T ask table, o r use a
finder to track it do wn. Open a Terminal sessio n and enter the co mmands as sho wn:
INTERACTIVE SESSION:
cold1:~$ cd railsapps/ostapp
cold1:~/railsapps/ostapp$ rails console
Loading development environment (Rails 3.0.3)
irb(main):001:0> a_task = Task.find_by_name('Write presentation')
=> #<Task id: 29, name: "Write presentation", description: "Write filtration pre
sentation for lab.\r\n\r\nPad out w...", duration: 120, due_date: "2014-11-18",
complete: false, created_at: "2013-02-27 21:09:11", updated_at: "2013-02-27 21:0
9:11", user_id: 1>
irb(main):002:0>
In this case, we fo und the task by lo o king fo r it by name. Yo u can see that this new task has the use r_id set to
the value 1. That's presumably the co rrect user-id, but because we've saved the task into the a_t ask variable,
we can use the relatio nship to find the task creato r's details:
INTERACTIVE SESSION:
irb(main):002:0> a_task.user
=> #<User id: 1, full_name: "Courtney Jones", username: "courtney", password: "p
alimpsest", created_at: "2013-02-26 14:36:20", updated_at: "2013-02-26 14:36:20"
>
irb(main):003:0>
It wo rked! We're no w stamping each new task with the id o f the perso n who created it—but we're o nly half way
do ne. Sure, we kno w who o wns each task, but we still need to use that info rmatio n to make sure that a user
o nly sees their o wn tasks.
Finding All of a User's T asks
The be lo ngs_t o relatio nship we added to the app/m o de ls/t ask.rb script gave us a co nvenient way to set and find
the o wner o f a single task:
No w, we need to go back to the o ther way. If we kno w the current user, ho w do we find all o f the tasks that belo ng to
that user? We need a relatio nship that go es fro m the Use r mo del back to the T ask mo del:
If we had a relatio nship that wo rked in the reverse directio n o f the be lo ngs_t o relatio nship, then we co uld jump fro m a
user to the tasks that they o wn. Each user might o wn several tasks, so we need to tell Rails that each user po tentially
has many tasks. As it turns o ut, the reverse o f the be lo ngs_t o relatio nship is called the has_m any relatio nship.
has_many is the Reverse of belongs_to
When we created a be lo ngs_t o relatio nship fro m tasks to users, we created the co de in the t ask.rb mo del script. To
create the reverse relatio nship (fro m users to tasks), we need to do that in the use r.rb mo del script. Mo dify
app/m o de ls/use r.rb as sho wn:
railsapps/o stapp/app/mo dels/user.rb
class User < ActiveRecord::Base
has_many :tasks
end
The has_m any relatio nship allo ws us to find all o f the T asks a Use r o wns. We create the relatio nship by adding
has_m any :t asks to the use r.rb script. It's impo rtant to use the plural fo rm :t asks here. When yo u're writing
relatio nship co de the expressio ns are designed to be read like English.
Save the use r.rb file, then clo se and reo pen the Rails co nso le. No w we can find o ut if the new relatio nship helps us
find all o f the tasks belo nging to a user.
Note
When yo u start the Rails co nso le, it lo ads all o f the co de in yo ur applicatio n. If yo u change a script after
that, like app/m o de ls/use r.rb, the Rails co nso le wo n't see the new changes. So , if yo u're already
lo gged into a co nso le, yo u have to quit and then restart it. That way yo u'll be able to use the new
has_m any relatio nship. Also , yo u have to be in the ~/railsapps/o st app applicatio n directo ry fo r the
co nso le to wo rk co rrectly.
INTERACTIVE SESSION:
cold1:~/railsapps/ostapp$ rails console
Loading development environment (Rails 3.0.3)
irb(main):001:0>
Once yo u're in the co nso le, find a user fro m the database and sto re it in a variable, like this:
INTERACTIVE SESSION:
irb(main):002:0> a_user=User.find_by_username('courtney')
=> #<User id: 1, full_name: "Courtney Jones", username: "courtney", password: "palimpse
st", created_at: "2013-02-26 14:36:20", updated_at: "2013-02-26 14:36:20">
irb(main):003:0>
No w the has_m any relatio nship will let yo u find all o f the tasks that were created by this user, like this:
INTERACTIVE SESSION:
irb(main):003:0> a_user.tasks
=> [#<Task id: 29, name: "Write presentation", description: "Write filtration presentat
ion for lab.\r\n\r\nPad out w...", duration: 120, due_date: "2014-11-18", complete: fal
se, created_at: "2013-02-27 21:09:11", updated_at: "2013-02-27 21:09:11", user_id: 1>]
irb(main):004:0>
Behind the scenes, ActiveReco rd used the has_m any relatio nship to find all o f the tasks in the database with a
use r_id that matches Co urtney's id. No w that we kno w ho w to find all o f Co urtney's tasks, we can mo dify the list page
so that instead o f displaying every task o n the database, it just displays tho se that belo ng to Co urtney:
The All Tasks page displays data read fro m the database in the inde x actio n-metho d inside the t asks_co nt ro lle r.rb
script:
railsapps/o stapp/aspp/co ntro llers/tasks_co ntro ller.rb
...
def index
@tasks = Task.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @tasks }
end
end
...
The inde x actio n-metho d reads all o f the tasks in the database using T ask.all, and then sto res them in the @ t asks
variable. The inde x.ht m l.e rb page template then displays whatever's sto red in the @ t asks variable.
If yo u just sto re the tasks fo r the current user in the @ t asks variable, then tho se will be the o nly tasks displayed o n this
page.
Let's set @ t asks to the current user's tasks—@ use r.t asks. Mo dify the app/co nt ro lle rs/t asks_co nt ro lle r.rb file
like this:
railsapps/o stapp/aspp/co ntro llers/tasks_co ntro ller.rb
class TasksController < ApplicationController
def index
@tasks = Task.all
@tasks = @user.tasks
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @tasks }
end
end
...
No w refresh the bro wser windo w that lists the tasks. Yo u'll see o nly the tasks that yo ur lo gged-in user created:
Test yo ur co de further. Go to the New Task page and enter ano ther task as sho wn:
Then go back to the All Tasks page and view yo ur new task in the list:
The applicatio n is no w much mo re user-specific, but yo u can still see o ther peo ple's tasks o n the Inco mplete tasks
page:
The inco mplete tasks are read fro m the database in the inco m ple t e actio n-metho d, using a finder:
We kno w ho w to find all o f the tasks fo r a given user, but ho w can we find just the inco mplete tasks fo r a user?
T he Collection of T asks Has Its Own Finders
We've already seen that yo u can search fo r data o n the database using finders:
T ask has a set o f finders that can search the database fo r tasks that match so me co nditio n—yo u can find all o f the
tasks that are inco mplete, yo u can search fo r a task with a particular name, and so o n. Instead o r reading all o f the
tasks fro m the database, the finders give yo u a restricted list o f tasks.
No w, instead o f searching thro ugh all o f the tasks o n the database to lo cate tho se that are inco mplete, yo u just search
thro ugh the tasks given by @ use r.t asks to lo cate tho se that are inco mplete.
This is where ActiveReco rd is really smart. The expressio n @ use r.t asks looks like a simple list o f tasks, but it co mes
co mplete with a full set o f T ask finders. That means that in the same way that yo u can find all o f the inco mplete tasks
with T asks.f ind_all_by_co m ple t e (f alse ), yo u can also find the inco mplete tasks belo nging to @ use r, like this:
The @ use r.t asks part finds all o f the tasks fo r @ use r, and then the .f ind_all_by_co m ple t e (f alse ) piece filters that
list so that it co ntains o nly tho se tasks that are inco mplete. So , yo u can perso nalize the Inco mplete tasks pages by
mo difying app/co nt ro lle rs/t asks_co nt ro lle r.rb as sho wn:
railsapps/o stapp/aspp/co ntro llers/tasks_co ntro ller.rb
class TasksController < ApplicationController
def index
@tasks = @user.tasks
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @tasks }
end
end
def incomplete
@tasks = Task.find_all_by_complete(false)
@overdue = Task.where(["due_date < ? and complete = ?", Date.today, false])
@tasks = @user.tasks.find_all_by_complete(false)
@overdue = @user.tasks.where(["due_date < ? and complete = ?", Date.today, false])
respond_to do |format|
format.html
format.xml { render :xml => @tasks }
end
end
...
In bo th cases, the wo rd T ask can be replaced by @ use r.t asks and instantly the sco pe is restricted to the list o f tasks
o wned by the current user.
Save the file and refresh the Incomplete Tasks page o n the applicatio n. No w yo u see o nly the inco mplete tasks o wned
by the perso n who 's lo gged in.
Each user who lo gs in to the applicatio n, will see his o wn distinct view o f the data.
Mo st web applicatio ns need to asso ciate data to gether because info rmatio n in the real wo rld can rarely be sto red as a
simple, linear list o f items. Database relatio nships like be lo ngs_t o and has_m any allo w yo u to deal with that
co mplexity by co nnecting data sets to gether, and allo w yo u to navigate between them. In this lesso n, yo u added the
be lo ngs_t o relatio nship between the tasks and users database tables, to co nnect tasks to users, and added the
has_m any relatio nship between the users and tasks database tables, to co nnect users to tasks. That's pretty
advanced stuff!
What Just Happened?
In this lesso n, we learned:
Yo u can set the o wner o f a piece o f data in the cre at e actio n-metho d o f the co ntro ller.
The be lo ngs_t o co nnects a piece o f data to its o wner.
The has_m any relatio nship is the reverse o f the be lo ngs_t o relatio nship.
The has_m any relatio nship returns a list o f the owneddata.
The list returned by has_m any has its o wn set o f finders.
Yo ur relatio nships are beco ming really co mplicated. Go o d luck with yo ur ho mewo rk and I'll see yo u after yo u get all o f
yo ur vario us relatio nships handled!
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Creating a Search Feature
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
Yo u'll access the parameters sent by a fo rm.
use abso lute paths to allo w fo rms to be used by any page in yo ur applicatio n.
Forms: Adding Search
In this lesso n, we'll lo o k at fo rms. We've used fo rms thro ugho ut the co urse, but so far every o ne has been generated
using scaffo lded co de. In o rder to see at ho w fo rms wo rk, let's add a new feature: search.
We've already co vered mo st o f the features we'll need fo r search. If a user submits a search request, the applicatio n
will need to find matching tasks o n the database, sto re them in a variable, and then display them in a web page. Let's
go ahead and add tho se basics to the applicatio n.
Adding a Custom Search Route
If we're go ing to add a brand-new functio n to the applicatio n, we need to add a ro ute first. The ro ute will
asso ciate an action (a set o f co de) with a path. Open the co nf ig/ro ut e s.rb file in the text edito r and add this
ro ute:
CODE TO TYPE: railsapps/o stapp/co nfig/ro utes.rb
Ostapp::Application.routes.draw do
resources :users
get "tasks/incomplete"
get "tasks/search"
resources :tasks
get "my_timer/show_current_time"
end
Note
Yo ur ro ut e s.rb will lo o k a little different if yo u've been do ing yo ur ho mewo rk!
The ge t " t asks/se arch" line tells Rails to asso ciate any GET requests to the ../t asks/se arch path with a
se arch actio n using the t asks co ntro ller. Remember—Rails can infer this because o f its stro ng naming
co nventio ns. So if yo u o pen a bro wser at ht t p://login-id.o re illyst ude nt .co m /o st app/t asks/se arch, Rails
will immediately lo o k fo r an actio n-metho d in the t asks_co nt ro lle r.rb file called se arch. Let's add that
metho d next.
Adding an action-method
Open the app/co nt ro lle rs/t asks_co nt ro lle r.rb and add a new se arch actio n-metho d as sho wn:
CODE TO TYPE: railsapps/o stapp/app/co ntro llers/tasks_co ntro ller.rb
class TasksController < ApplicationController
def search
terms = 'CHEMICAL'
@tasks = @user.tasks.where(["upper(name) like ? OR upper(description) like ?
", "%#{terms}%", "%#{terms}%"])
respond_to do |format|
format.html
format.xml { render :xml => @tasks }
end
end
def index
@tasks = @user.tasks
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @tasks }
end
end
...
Let's lo o k at that new se arch actio n-metho d and break it do wn a little:
OBSERVE:
def search
terms = 'CHEMICAL'
@tasks = @user.tasks.where(["upper(name) like ? OR upper(description) like ?
", "%#{terms}%", "%#{terms}%"])
respond_to do |format|
format.html
format.xml { render :xml => @tasks }
end
end
First, we pro vide a de f ault st ring t o se arch f o r (the user will be able to enter a different term). Then, we
se arch t he dat abase f o r t asks whe re t he nam e o r de script io n m at che s t he se arch t e rm . We
indicate ho w we want Rails t o re spo nd (wit h XML o r HT ML), and we tell Rails to re nde r t he page as
we did with the inde x and inco m ple t e metho ds. Fo r all three metho ds, the co de will:
find a list o f matching data.
sto re the data in a variable called @ t asks.
tell Rails to generate a respo nse.
The o nly significant difference is that the se arch actio n-metho d finds its data using the co de:
OBSERVE:
@tasks = @user.tasks.where(["upper(name) like ? OR upper(description) like ?", "
%#{terms}%", "%#{terms}%"])
This is a co mplex piece o f co de. It begins with @ use r.t asks, which means that we'll o nly search fo r tasks
that belo ng to the current user—the @ use r variable sto res the user who 's lo gged in. Then we create a query
using the whe re functio n. This passes a custo m co nditio n to the database and returns the reco rds that
match. In this case, the co nditio n means "Give me all the tasks with a Name o r Descriptio n that co ntains the
string in the terms variable."
The t e rm s variable co ntains the uppercase string " CHEMICAL" . The whe re functio n co nverts each name
and description to uppercase befo re searching fo r the string. That means that we sho uld find any tasks
co ntaining "Chemical," "chemical," "cHeMiCAl," and so o n, regardless o f the letters' case.
Note
whe re (...) can take an array o f co nditio ns, all o f which will need to be t rue fo r a task to be
selected. In o ur co de, we have o nly a single co nditio n ("Do es the name o f co nditio n co ntain..."),
but we still need to place the co nditio n in square brackets.
The call to @ use r.t asks.whe re (...) returns a list o f Tasks, which we then sto re in the @ t asks variable.
Adding a T emplate for the Search Results
No w we need a template file to display the co ntents o f the @ t asks search results. This is a straightfo rward
task because we've already created similar templates fo r the inde x and inco m ple t e actio ns. In the text
edito r, start a new file and type the co de as sho wn:
CODE TO TYPE: railsapps/o stapp/app/views/tasks/search.html.erb
<h1>Search</h1>
<%= render :partial=>"tasktable", :locals=>{:tasks=>@tasks} %>
<%= render :partial=>"copyright" %>
<br />
<%= link_to 'New Task', new_task_path %>
Save it as app/vie ws/t asks/se arch.ht m l.e rb. This pro bably lo o ks familiar—it's almo st identical to the
inde x.ht m l.e rb template:
OBSERVE: ~/railsapps/o stapp/app/views/tasks/index.html.erb
<h1>Listing tasks</h1>
<%= render :partial=>"tasktable", :locals=>{:tasks=>@tasks} %>
<%= render :partial=>"copyright" %>
<br />
<%= link_to 'New Task', new_task_path %>
Bo th templates display the co ntents o f the @ t asks variable by calling t he _t askt able .ht m l.e rb part ial
template. The o nly difference between inde x.ht m l.e rb and se arch.ht m l.e rb is the title in the <h1>...</h1>
tag.
No w we can try o ut the new search co de by o pening a bro wser at ht t p://loginid.o re illyst ude nt .co m /o st app/t asks/se arch. If yo u're no t already lo gged in, yo u may be asked to do so .
The page lo o ks like this:
When yo u enter the search URL, Rails finds a matching ro ute in the ro ut e s.rb file and then runs the search
actio n o n the Tasks co ntro ller. The search actio n metho d inside the tasks co ntro ller finds all o f the reco rds fo r
the current user that co ntain the wo rd che m ical in the name o r descriptio n. These are sto red in the @ t asks
variable, which is then displayed with the se arch.ht m l.e rb template. No w, we need to mo dify the search so
that instead o f searching fo r the selected wo rd ("CHEMICAL"), it will search fo r a wo rd o r phrase pro vided by
the user.
Passing Data to the Application
There are two ways that a bro wser can send data to a web server—as part o f the URL, o r in the hidden part o f the
request. A request is when a bro wser co ntacts a web server. Let's take a lo o k at ho w to send the search terms in the
URL. We've enco untered this type o f request befo re—it's called a GET request. Let's mo dify the applicatio n so that if
the user makes the call to ht t p://login-id.o re illyst ude nt .co m /o st app/t asks/se arch?t e rm s=chart s, the
applicatio n will search all o f the user's tasks fo r the wo rd charts. The info rmatio n at the end o f the URL after the ?
character is called the query. The query can co ntain a number o f parameters. A parameter is a name with an
asso ciated value. In this case we have the t e rm s parameter, with the value chart s.
Note
Yo u do n't have to use the name t e rm s fo r the search value. Yo u can name parameters in whatever way
is meaningful to yo u, so lo ng as yo u are co nsistent thro ugho ut yo ur co de, and yo u avo id using
characters that have specific URL meanings (such as "?" o r "&").
In o ur co ntro ller co de, instead o f searching fo r the fixed string CHEMICAL, we need to search fo r the string passed in
as the terms parameter:
Rails makes this value available with the param s o bject. The param s o bject allo ws yo u to find a particular parameter
value by specifying the parameter's name in brackets, like this:
The name o f the parameter, as with all names in Rails, is specified with a symbol. This o ne begins with a
colon—:t e rm s. The expressio n param s[:t e rm s] evaluates to the string value passed in the URL—in the abo ve
example URL, this is " chart s" . The existing co de lo o ks fo r an uppercase string ("CHEMICAL"), so befo re we replace
that static value with the parameter value, we will need to co nvert the parameter value to uppercase. In Rails we can find
the uppercase value o f any string by adding .upcase to the end o f it. Fo r example, if we want an uppercase versio n o f
the t e rm s parameter, we can find it with this expressio n:
Replace the static value in t asks_co nt ro lle r.rb with the value o f the t e rm s variable, as sho wn:
CODE TO TYPE: railsapps/o stapp/app/co ntro llers/tasks_co ntro ller.rb
class TasksController < ApplicationController
def search
terms = 'CHEMICAL'
terms = params[:terms].upcase
@tasks = @user.tasks.where(["upper(name) like ? OR upper(description) like ?", "%#{
terms}%", "%#{terms}%"])
respond_to do |format|
format.html
format.xml { render :xml => @tasks }
end
end
...
Save it and go to ht t p://login-id.o re illyst ude nt .co m /o st app/t asks/se arch?t e rm s=de part ure ; yo u're results
will lo o k like this:
WARNING
If yo u mistype the name o f yo ur "terms" parameter in the co de, yo u'll see an erro r message when
yo u try to perfo rm a search. If yo u try to read the wro ng parameter fro m param s[...], Ruby wo n't
be able to return a meaningful value and yo ur applicatio n will generate an array when it tries to
co nvert the unusable value to uppercase.
If yo u have a different set o f task data, yo ur search results will vary. To make sure that the co de is really picking up the
search terms fro m the URL, try so me different values:
Adding a Form
No w that we've changed the co de to include the search terms in the URL, we can create a fo rm fo r the user to specify
which search terms they want. If we create an HTML fo rm with a t e rm s text field that sends the fo rm to the
../t asks/se arch URL, o ur co de will search fo r whatever string the user entered in the fo rm.
It wo uld be useful if the search fo rm was available o n each o ne o f the tasks pages. We can add to required HTML co de
to every task page by adding the co de to the tasks layout file at t asks.ht m l.e rb:
CODE TO TYPE: railsapps/o stapp/app/views/layo uts/tasks.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Ostapp</title>
<%= stylesheet_link_tag :all %>
<%= javascript_include_tag :defaults %>
<%= csrf_meta_tag %>
</head>
<body>
<div style="float: right">
Signed in as: <span style="font-weight: bold;"><%= @user.full_name %></span>
<form action="/ostapp/tasks/search" method="get">
<label for="terms">Search for:</label>
<input id="terms" name="terms" type="text">
<input type="submit" value="Search">
</form>
</div>
<img src="http://www.oreillyschool.com/images/OST_Logo.gif"/>
<br/>
<div style="display:table-cell; width: 200px;">
<h1>TASKS</h1>
<ul>
<li><%= link_to_unless_current 'All tasks', :tasks %></li>
<li><%= link_to_unless_current 'Incomplete tasks', :tasks_incomplete %></li>
<li><%= link_to_unless_current 'New task', :new_task %></li>
</ul>
</div>
<div style="display:table-cell">
<%= yield %>
</div>
</body>
</html>
Save the t asks.ht m l.e rb file and then go to any o f the task pages (it do esn't matter which o ne; they all co ntain the
search fo rm no w):
The fo rm accepts a text value called t e rm s and then sends the fo rm to /o st app/t asks/se arch. Because the fo rm's
metho d is GET, when the fo rm is sent to the server, the value o f the parameter is added to the URL.
Enter so me text in the search bo x, then click Se arch. The bro wser sends the fo rm to the server, and generates the
search results:
Absolute vs. Relative Paths
Our site no w has a fully functio nal search fo rm. Let's take a clo ser lo o k at the HTML fo r the fo rm:
The fo rm uses an absolute path fo r the actio n—that means it specifies the full path fro m the ro o t o f the website URL to
the lo catio n o f the search co de. This allo ws the search fo rm to be called fro m anywhere.
Fo r example, if the fo rm had set the actio n to the relative path " se arch" like this:
The fo rm wo uld still wo rk great fro m the main tasks Index page, ht t p://login-id.o re illyst ude nt .co m /o st app/t asks,
because fro m there the fo rm wo uld send its data to ht t p://login-id.o re illyst ude nt .co m /o st app/t asks/se arch.
But what if the user calls the search page fro m a single task page, like ht t p://loginid.o re illyst ude nt .co m /o st app/t asks/22?
Fro m that URL, a relative se arch path wo uld lead to ht t p://login-id.o re illyst ude nt .co m /o st app/t asks/22/se arch.
It's really impo rtant that o ur search fo rm has an abso lute path. Witho ut it, the search simply will no t wo rk o n so me
pages.
Ho wever, the big downside to setting an abso lute path is that we lo se flexibility. We're tying o urselves do wn to a
particular URL structure. Yo ur Rails applicatio n might be deplo yed o n different web servers in different ways. On the
O'Reilly Scho o l web servers, everything is published under the /o st app path; all o f yo ur URLs begin with ht t p://loginid.o re illyst ude nt .co m /o st app/. But what if yo u gave yo ur applicatio n to ano ther website? They might deplo y it o n
ht t p://www.m e gaco rp.co m /pro duct ivit y/t askapp/.... That means yo ur search feature wo uld live o n
ht t p://www.m e gaco rp.co m /pro duct ivit y/t askapp/t asks/se arch, which wo uld break all o f yo ur co de immediately.
So we have a co nflict:
We need to have an abso lute path fo r the search fo rm so that it will wo rk o n all pages.
We do n't want to sto re the abso lute path in the fo rm because it will break the applicatio n if it's deplo yed
elsewhere.
Do es any o f this so und familiar? We had a similar pro blem in an earlier lab when we wanted to create links between
pages. Instead o f creating a link in the Tasks layo ut:
We used a he lpe r functio n:
Instead o f creating a hyperlink with raw HTML, we used a helper functio n to generate the HTML fo r us. The helper
functio n generated an abso lute path fo r the link, and the abso lute path wo uld wo rk, even if the applicatio n was
deplo yed in a different way o n a different machine.
This is just one reaso n that Rails co ders prefer to use helper functio ns rather than raw HTML. In additio n, helper
functio ns:
have extra functio nality that can be called o n witho ut requiring lo ts o f extra co de.
are usually smaller than the HTML that they generate.
are easier to read.
make typo s easier to spo t. Fo r example, in the link_t o helper abo ve, misspelling :t asks_inco m ple t e
wo uld have generated an erro r fo r the page, but mistyping " /o st app/t asks/inco m ple t e " wo uldn't.
Fo r all o f these reaso ns, it's better to generate o ur fo rm in a helper functio n than to write it in raw HTML.
Using a Form Helper Function
We need a fo rm-helper that generates HTML co de equivalent to this HTML:
Here's ho w to write the equivalent co de with helpers:
We're using no t o ne, but several helpers. Each helper generates a specific type o f HTML tag. HTML fo rms co ntain
fields; in HTML we place the fields between the <f o rm > and </f o rm > tags. Field helpers are included inside fo rm
helpers by using do and e nd markers. In Ruby, the co de between do and e nd markers is called a block. So the co de
abo ve creates a fo rm with the f o rm _t ag helper and then passes it a blo ck o f fields created by calling the labe l_t ag,
t e xt _f ie ld_t ag, and subm it _t ag helpers. Here's a list o f the helpers we just used, alo ng with a descriptio n o f each:
f o rm _t ag is a helper to generate <f o rm >...</f o rm > tags. By passing in values fo r the :act io n and
:m e t ho d, the f o rm _t ag can create an abso lute path fo r the co rrect co de. The f o rm _t ag is passed a blo ck
o f co de between do and e nd markers.
labe l_t ag creates a <labe l/> fo r a fo rm field. It's given a symbo l that represents the field it is labeling
(:t e rm s) and a string that specifies the text o f the label.
t e xt _f ie ld_t ag generates a <input t ype =" t e xt " .../> field. It is given a symbo l that represents its field
value (:t e rm s).
subm it _t ag creates a <input t ype =" subm it " .../> tag. Yo u can give subm it _t ag a string to replaces the
default Subm it text o n the butto n.
Mo dify t asks.ht m l.e rb as sho wn:
CODE TO TYPE: ~/railsapps/o stapp/app/views/layo uts/tasks.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Ostapp</title>
<%= stylesheet_link_tag :all %>
<%= javascript_include_tag :defaults %>
<%= csrf_meta_tag %>
</head>
<body>
<div style="float: right">
Signed in as: <span style="font-weight: bold;"><%= @user.full_name %></span>
<form action="/ostapp/tasks/search" method="get">
<label for="terms">Search for:</label>
<input id="terms" name="terms" type="text">
<input type="submit" value="Search">
</form>
<%= form_tag({:action=> "search"}, :method=>"GET") do %>
<%= label_tag(:terms, "Search for:") %>
<%= text_field_tag(:terms) %>
<%= submit_tag("Search") %>
<% end %>
</div>
<img src="http://www.oreillyschool.com/images/OST_Logo.gif"/>
<br/>
<div style="display:table-cell; width: 200px;">
<h1>TASKS</h1>
<ul>
<li><%= link_to_unless_current 'All tasks', :tasks %></li>
<li><%= link_to_unless_current 'Incomplete tasks', :tasks_incomplete %></li>
<li><%= link_to_unless_current 'New task', :new_task %></li>
</ul>
</div>
<div style="display:table-cell">
<%= yield %>
</div>
</body>
</html>
Save it and do a search o n o ne o f yo ur tasks pages:
Okay—the search fo rm lo o ks the same and wo rks the same way as befo re. View the so urce co de fo r the page and
see if it lo o ks like the same kind o f HTML is being pro duced:
The HTML co de lo o ks similar to the co de yo u wro te by hand, but it's actually slightly mo re co mplicated because it
includes info rmatio n abo ut ho w the character set o f the fo rm is enco ded. These kinds o f details can be tedio us to
create by hand, but yo u get them fo r free if yo u use a fo rm helper!
What Just Happened?
In this lesso n, we learned:
param s gives yo u access to the parameters sent by a fo rm.
To access a parameter named t e rm s, use param s[:t e rm s].
Adding .upcase to the end o f a string gives yo u an uppercase versio n o f the string.
Abso lute paths allo w fo rms to be used by any page in yo ur applicatio n.
Abso lute paths may cause pro blems if yo u deplo y yo ur applicatio n to a different server.
Fo rm helpers avo id path pro blems by generating abso lute paths at runtime.
The f o rm _t ag helper creates a fo rm tag with smart paths that will match actio ns in yo ur applicatio n.
labe l_t ag, t e xt _t ag, and subm it _t ag can generate labels and fields within a fo rm.
Wo w. We are heading into the ho me stretch! Where do es the time go ? Wo rk thro ugh the ho mewo rk and I'll see yo u
so o n in the next and final lesso n!
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Adding Fields
Lesson Objectives
When yo u co mplete this lesso n, yo u will be able to :
add fields to yo ur applicatio n.
Adding Fields to Your Application
No applicatio n remains the same fo r lo ng. As requirements evo lve, yo u'll o ften need new data, which means yo u'll
need to add fields to yo ur applicatio ns. In this lesso n, we'll add a Priority field to the system which will allo w users to
prio ritize their tasks. In do ing that, we'll need to revise many o f the skills we've learned thro ugho ut the co urse.
The priority field will co ntain a value fro m 1 to 10 , o r the user might cho o se to leave the field blank.
If we want to add this field to the applicatio n, what do we need to do ?
Add the Field to the Database
Befo re we do anything else, we need to add the prio rity field to the t asks table in the database. We've added a field to
this database befo re, back when we created a relatio nship between the t asks and use rs tables.
To mo dify the structure o f the database, fo r instance to add a co lumn to a table, we create a migration. If we generate a
migratio n with an appro priate name, Rails will be able to infer the change we want to make. Open a Terminal sessio n,
change into the applicatio n directo ry, then create the migratio n, like this:
INTERACTIVE SESSION:
cold1:~$ cd railsapps/ostapp/
cold1:~/railsapps/ostapp$ rails generate migration AddPriorityColumnToTasks priority:in
teger
invoke active_record
create
db/migrate/20130113110542_add_priority_column_to_tasks.rb
cold1:~/railsapps/ostapp$
The name tells Rails exactly what to do , so yo u do n't have to write any additio nal co de yo urself.
At the end o f the migratio n co mmand, prio rit y:int e ge r tells Rails that we want this new co lumn to be an integer.
The rails ge ne rat e m igrat io n co mmand, creates the migratio n script in the db/m igrat e directo ry. The name o f the
migratio n script will vary, because it begins with a timestamp and ends with _add_prio rit y_co lum n_t o _t asks. Open
the migratio n in the edito r; it will lo o k like this:
OBSERVE:
class AddPriorityColumnToTasks < ActiveRecord::Migration
def self.up
add_column :tasks, :priority, :integer
end
def self.down
remove_column :tasks, :priority
end
end
The se lf .up functio n inside the script will create the co lumn o n the table, but befo re that can happen, we need to run
the script, using the rake co mmand that co mes with Rails. Remember—if we're generating co de, we use the rails
co mmand, but if we're managing o ur enviro nment, we almo st always use the rake co mmand.
INTERACTIVE SESSION:
cold1:~/railsapps/ostapp$ rake db:migrate
(in /users/dgriffit1/railsapps/ostapp)
== AddPriorityColumnToTasks: migrating =======================================
-- add_column(:tasks, :priority, :integer)
-> 0.0080s
== AddPriorityColumnToTasks: migrated (0.0090s) ==============================
cold1:~/railsapps/ostapp$
To make sure that the co lumn was added to the table, o pen the Rails co nso le and take a lo o k at the structure o f the
t ask o bject. We've used the rails co nso le thro ugho ut the co urse whenever we wanted to try o ut co de in o ur
applicatio n. The Rails co nso le is an extensio n o f the basic Ruby Interactive Ruby to o l (irb), but unlike irb, the Rails
co nso le also has access to all o f the co de in o ur applicatio n.
INTERACTIVE SESSION:
cold1:~/railsapps/ostapp$ rails console
Loading development environment (Rails 3.0.3)
irb(main):001:0> Task
=> Task(id: integer, name: string, description: text, duration: integer, due_date: date
, complete: boolean, created_at: datetime, updated_at: datetime, user_id: integer, prio
rity: integer)
irb(main):002:0> quit
cold1:~/railsapps/ostapp$
By typing T ask in the co nso le, we can examine the structure o f the task o bjects. The prio rit y attribute is listed at the
end o f the T ask structure.
So , we've added the prio rity co lumn to the table, no w what?
Add the Field to the View
Every Rails app splits into three parts: the model, the controller, and the view. The mo del is the part o f the applicatio n
that manages the data. When we add a priority to the task o bjects, we make a change to the mo del. The co ntro ller
handles the basic flo w o f the applicatio n; we've spent a lo t o f time mo difying the co ntro ller co de.
View co de manages the applicatio n's appearance. We've added the prio rity co lumn to the database, but if we go to the
web pages in the applicatio n, there's still no sign o f it.
It's no t there because we haven't changed any o f the view co de to display the new prio rity attribute.
Note
When we first created the applicatio n using scaffo lding, the view co de was generated fo r us auto matically.
Yo u might wo nder why Rails didn't also add the new attribute to the view co de when we created the
migratio n. It do esn't do that because the view co de may well have been mo dified since it was created by
Rails. Once Rails generates co de, it never go es back to mo dify it. Once the co de is generated, it's up to
yo u, the pro grammer, to manage everything fro m then o n.
First, let's add the Prio rity field to the Ne w T ask and Edit ing T ask fo rms. Open the app/vie ws/t asks/e dit .ht m l.e rb
template file; the fo rm is called in fro m a partial template named f o rm :
OBSERVE:
<h1>Editing task</h1>
<%= render 'form' %>
<%= link_to 'Show', @task %> |
<%= link_to 'Back', tasks_path %>
This template file is called after the co ntro ller has read the relevant task fro m the database and sto red it in a variable
named @ t ask. The e dit .ht m l.e rb displays a fo rm to edit this task by calling the f o rm partial template.
A partial page template is a small template file that can be called by ano ther template, using the re nde r functio n. The
re nde r 'f o rm ' call will include the co ntents o f the _f o rm .ht m l.e rb file (all partial page template files begin with the
undersco re character _ and end with the extensio n .ht m l.e rb). Open the app/vie ws/t asks/_f o rm .ht m l.e rb partial
file:
OBSERVE:
<%= form_for(@task) do |f| %>
<% if @task.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@task.errors.count, "error") %> prohibited this task from being
saved:</h2>
<ul>
<% @task.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %><br />
<%= f.text_area :description %>
</div>
<div class="field">
<%= f.label :duration %><br />
<%= f.text_field :duration %>
</div>
<div class="field">
<%= f.label :due_date %><br />
<%= f.date_select :due_date %>
</div>
<div class="field">
<%= f.label :complete %><br />
<%= f.check_box :complete %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
This partial co ntains a f o rm . When we add the search functio n, we create a fo rm that co nsists o f a text field—the data
in the fo rm do esn't co rrespo nd to any particular piece o f info rmatio n in the database. This fo rm is a little different. It's
used to edit the task o bject sto red in the @ t ask variable. Whereas the search fo rm co uld simply co ntain a blank text
field, the fo rm co ntained in the _f o rm .ht m l.e rb file needs to display the existing attributes o f the @ t ask o bject.
Because the fo rm needs to include the existing attributes o f the @ t ask o bject, it has to be generated with so me slightly
different fo rm helper functio ns. When we created the search fo rm, we used a fo rm helper called f o rm _t ag:
In o rder to create a fo rm that's based o n a particular task o bject, we need to use the f o rm _f o r(@ t ask) helper. This
helper functio n examines the Task o bject it's been passed, and uses the task's attributes to set default values fo r the
fields in the fo rm. The f o rm _f o r(...) helper creates a fo rm o bject named f that can generate fo rm fields fo r each o f the
attributes o f the @ t ask o bject. Fo r example, this is ho w the Descriptio n field is created:
OBSERVE:
<%= form_for(@task) do |f| %>
...
<div class="field">
<%= f.label :description %><br />
<%= f.text_field :description %>
</div>
...
<% end %>
f o rm _f o r is given a @ t ask o bject, creating the |f | fo rm o bject (the |bars| indicate that f was created by the f o rm _f o r
call). The fo rm o bject creates the labe l and the t e xt _f ie ld fo r the @ t ask's de script io n attribute.
That means that this fo rm wo rks in a way similar to o ur search fo rm—the main difference is that this fo rm will set
default values fo r each o f the fields based o n the values o f the attributes o f the @ t ask variable.
We've added a prio rit y attribute to each task o bject, so add it to app/vie ws/t asks/_f o rm .ht m l.e rb like this:
CODE TO TYPE:
<%= form_for(@task) do |f| %>
<% if @task.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@task.errors.count, "error") %> prohibited this task from being
saved:</h2>
<ul>
<% @task.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %><br />
<%= f.text_area :description %>
</div>
<div class="field">
<%= f.label :duration %><br />
<%= f.text_field :duration %>
</div>
<div class="field">
<%= f.label :due_date %><br />
<%= f.date_select :due_date %>
</div>
<div class="field">
<%= f.label :complete %><br />
<%= f.check_box :complete %>
</div>
<div class="field">
<%= f.label :priority %><br />
<%= f.text_field :priority %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
That extra co de generates a label and a text field fo r the prio rit y attribute. If there's an existing prio rity value o n the task
being edited, the text field is set to that value.
Save the _f o rm .ht m l.e rb partial and then refresh the Editing task page. The prio rity field no w appears o n the page:
Next, we'll need to add the prio rity field to the New task fo rm. But wait—the ne w.ht m l.e rb file calls the same f o rm
partial as e dit .ht m l.e rb:
OBSERVE:
<h1>New task</h1>
<%= render 'form' %>
<%= link_to 'Back', tasks_path %>
That means that we do n't have to do anything at all to the New task page—it just wo rks!:
Try o ut the fo rm by creating a task with a prio rity as sho wn:
Adding the Priority to the Show T ask Page
The user can no w enter a prio rity against each task, but if they lo o k at a single task, the prio rity do esn't appear:
We can fix that. Mo dify the sho w.ht m l.e rb template to include the prio rit y value as sho wn:
CODE TO TYPE: ~/railsapps/o stapp/app/views/tasks/sho w.html.erb
<p id="notice"><%= notice %></p>
<p>
<b>Name:</b>
<%= @task.name %>
</p>
<p>
<b>Description:</b>
<%= simple_format(@task.description) %>
</p>
<p>
<b>Duration:</b>
<%= number_with_precision(@task.duration / 60.0, :precision => 2) %> hours
</p>
<p>
<b>Due date:</b>
<%= @task.due_date %>
</p>
<% unless @task.due_date < Date.today then %>
<p>
<b>Time remaining:</b>
<%= distance_of_time_in_words(Date.today, @task.due_date) %>
</p>
<% end %>
<p>
<b>Complete:</b>
<%= @task.complete %>
</p>
<p>
<b>Priority:</b>
<%= @task.priority %>
</p>
<%= link_to 'Edit', edit_task_path(@task) %> |
<%= link_to 'Back', tasks_path %>
Save it and view the new task. No w the Prio rity appears:
Validating the Priority Attribute
We lo o ked at validation in an earlier lesso n. Let's see ho w that might be useful here. We want the prio rity to be a
number in the range 1 - 10 . What happens if a user enters so mething else?
Rails model co de manages everything to do with data, so if we want to validate so mething, we'll add the co de to the
app/m o de ls/t ask.rb mo del script. We already have quite a few validatio ns in there:
OBSERVE:
class Task < ActiveRecord::Base
belongs_to :user
validates :description, :name, :duration, :presence => true
validates :name, :uniqueness => true
validates_numericality_of :duration, {:greater_than_or_equal_to => 1, :less_than_or_e
qual_to => 240}
validate :due_in_future, :on=>:create
def due_in_future
if due_date < Date.today then
errors.add(:due_date, "cannot be in the past")
end
end
end
If we want to add validatio n to the prio rit y, we'd put it in this script. These are the rules we need to apply to the prio rity:
It must be a number.
It must be in the range 1-10 .
It can be left blank.
We perfo rm validatio n o n the durat io n attribute; let's create a similar validatio n fo r the prio rit y attribute. Mo dify the file
as sho wn:
CODE TO TYPE:
class Task < ActiveRecord::Base
belongs_to :user
validates :description, :name, :duration, :presence => true
validates :name, :uniqueness => true
validates_numericality_of :duration, {:greater_than_or_equal_to => 1, :less_than_or_e
qual_to => 240}
validates_numericality_of :priority, {:greater_than_or_equal_to => 1, :less_than_or_e
qual_to => 10}
validate :due_in_future, :on=>:create
def due_in_future
if due_date < Date.today then
errors.add(:due_date, "cannot be in the past")
end
end
end
Save it and refresh the page. Yo u can no lo nger enter a large number o r a no n-numeric value in the Prio rity field:
So , what happens if yo u try to leave the prio rity field blank?:
The validatio n rule says that the prio rity must be numeric—a blank space is not numeric. So , we have to mo dify o ur
validatio n slightly and use an o ptio n we haven't seen befo re—:allow_nil. Mo dify t ask.rb again, as sho wn:
CODE TO TYPE:
class Task < ActiveRecord::Base
belongs_to :user
validates :description, :name, :duration, :presence => true
validates :name, :uniqueness => true
validates_numericality_of :duration, {:greater_than_or_equal_to => 1, :less_than_or_e
qual_to => 240}
validates_numericality_of :priority, {:greater_than_or_equal_to => 1, :less_than_or_e
qual_to => 10, :allow_nil => true}
validate :due_in_future, :on=>:create
def due_in_future
if due_date < Date.today then
errors.add(:due_date, "cannot be in the past")
end
end
end
Again, nil is a special value in Ruby which means "no value". The :allow_nil o ptio n means the user can leave the value
blank. Save t ask.rb again, refresh the page, and then try saving a task with a blank prio rity:
What Just Happened?
In this lesso n, we tied to gether a lo t o f the skills we've acquired thro ugho ut the co urse, and learned a few new o nes:
Rails co de is split into mo del, view, and co ntro ller co de.
Database co de is mo del co de.
Yo u can add a co lumn to a database table using a migratio n script.
Rails can infer what the migratio n has to do by the name yo u give it.
Use rake db:m igrat e to run the migratio n.
If yo u add a co lumn to the database, the view co de will not auto matically display it.
The main fo rm fo r creating/editing is in the _f o rm .ht m l.e rb partial template file.
Fo rms that are based o n existing data use the f o rm _f o r helper.
The the co ntro ller will read the existing task fro m the database and sto re it in the @ t ask variable.
f o rm _f o r creates a fo rm with default values based o n attribute values.
Validatio n is perfo rmed by mo del co de.
The validat e s_num e ricalit y_o f validato r has an :allo w_nil o ptio n if a value is no t mandato ry.
We've arrived at the end o f the lesso n, and the end o f the co urse. Thank yo u fo r immersing yo urself in o ur Rails
co urse, it's been a real pleasure! Co ngratulatio ns o n the new skills yo u've earned so far, and go o d luck with yo ur final
pro ject!
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.