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> © 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.