So an ORM layer maps tables to classes, rows to objects, and columns to
attributes of those objects. Class methods are used to perform table-level
operations, and instance methods perform operations on the individual rows. In a typical ORM library, you supply confiuration data to specify the map- pins between thins in the database and thins in the proram. !roram- mers usin these ORM tools often find themselves creatin and maintain- in a boatload of "M# confiuration files. $ctive Record $ctive Record is the ORM layer supplied with Rails. It closely follows the standard ORM model% tables map to classes, rows to objects, and columns to object attributes. It differs from most other ORM libraries in the way it is confiured. &y relyin on convention and startin with sensible defaults, $ctive Record minimi'es the amount of confiuration that developers per- form. (o illustrate this, here)s a proram that uses $ctive Record to wrap our orders table. re*uire + active,record + class Order - $ctiveRecord%%&ase end order . Order.find/01 order.discount . 2.3 order.save (his code uses the new Order class to fetch the order with an id of 0 and modify the discount. /4e)ve omitted the code that creates a database con- nection for now.1 $ctive Record relieves us of the hassles of dealin with the underlyin database, leavin us free to wor5 on business loic. &ut $ctive Record does more than that. $s you)ll see when we develop our shoppin cart application, startin on pae 67, $ctive Record interates seamlessly with the rest of the Rails framewor5. If a web form contains data related to a business object, $ctive Record can e8tract it into our model. $ctive Record supports sophisticated validation of model data, and if the form data fails validations, the Rails views can e8tract and format errors with just a sinle line of code. $ctive Record is the solid model foundation of the Rails M9C architecture. (hat)s why we devote two chapters to it, startin on pae 0:2. Report erratum !repared e8clusively for Rida $l &ara'i $ C(IO; ! $C< % ( => 9 I>4 $;? C O;(RO##>R 0@ A.7 $ction !ac5% (he 9iew and Controller 4hen you thin5 about it, the view and controller parts of M9C are pretty intimate. (he controller supplies data to the view, and the controller receives bac5 events from the paes enerated by the views. &ecause of these interactions, support for views and controllers in Rails is bundled into a sinle component, $ction !ac5. ?on)t be fooled into thin5in that your application)s view code and con- troller code will be jumbled up just because $ction !ac5 is a sinle compo- nent. Buite the contraryC Rails ives you the separation you need to write web applications with clearly demarcated code for control and presenta- tion loic. 9iew Support In Rails, the view is responsible for creatin either all or part of a pae to be displayed in a browser. 7 $t its simplest, a view is a chun5 of =(M# code that displays some fi8ed te8t. More typically you)ll want to include dynamic content created by the action method in the controller. In Rails, dynamic content is enerated by templates, which come in two flavors. One embeds snippets of Ruby code within the view)s =(M# usin a Ruby tool called >Rb /or >mbedded Ruby1. 6 (his approach is very fle8- ible, but purists sometimes complain that it violates the spirit of M9C. &y embeddin code in the view we ris5 addin loic that should be in the model or the controller. (his complaint is larely roundless% views con- tained active code even in the oriinal M9C architectures. Maintainin a clean separation of concerns is part of the job of the developer. /4e loo5 at =(M# templates in Section 0@.7, R=(M# (emplates, on pae 772.1 Rails also supports builder-style views. (hese let you construct "M# doc- uments usin Ruby codeDthe structure of the enerated "M# will auto- matically follow the structure of the code. 4e discuss builder templates startin on pae 7A:. $nd the ControllerE (he Rails controller is the loical center of your application. It coordinates the interaction between the user, the views, and the model. =owever, 7 Or an "M# response, or an e-mail, or.... (he 5ey point is that views enerate the response bac5 to the user. 6 (his approach miht be familiar to web developers wor5in with !=! or Fava)s FS! technoloy. Report erratum !repared e8clusively for Rida $l &ara'i $ C(IO; ! $C< % ( => 9 I>4 $;? C O;(RO##>R 0G Rails handles most of this interaction behind the scenesC the code you write concentrates on application-level functionality. (his ma5es Rails controller code remar5ably easy to develop and maintain. (he controller is also home to a number of important ancillary services. H It is responsible for routin e8ternal re*uests to internal actions. It handles people-friendly IR#s e8tremely well. H It manaes cachin, which can ive applications orders-of-manitude performance boosts. H It manaes helper modules, which e8tend the capabilities of the view templates without bul5in up their code. H It manaes sessions, ivin users the impression of an onoin inter- action with our applications. (here)s a lot to Rails. Rather than attac5 it component by component, let)s roll up our sleeves and write a couple of wor5in applications. In the ne8t chapter we)ll install Rails. $fter that we)ll write somethin simple, just to ma5e sure we have everythin installed correctly. In Chapter 3, (he ?epot $pplication, on pae 67, we)ll start writin somethin more substantialDa simple online store application. Report erratum !repared e8clusively for Rida $l &ara'i Chapter 7 Installin Rails &efore you can start writin a Rails application, you)ll need to download the Rails framewor5 and install it on your computer. $ll you need to run Rails is a Ruby interpreter /version 0.G.A or later1 and the Rails code. =ow- ever, thins o easier if you also have the RubyJems pac5ae manaement system available, so we)ll tal5 about ettin that installed too. Kinally, if you use a database other than MySB#, you may need to install the appro- priate Ruby libraries to interface with it. Kair warnin% this is a tedious chapter, full of Lclic5 thatM and Ltype thisM instructions. Kortunately, it)s short, and we)ll et on to the e8citin stuff shortly. #et)s loo5 at the installation instructions for 4indows, OS ", and #inu8. 7.0 Installin on 4indows 0. Kirst, let)s chec5 to see if you already have Ruby installed. &rin up a command prompt /usin Start N Run N cmd , or Start N !rorams N $ccessories N Command !rompt 1, and type ruby -v . If Ruby responds, and if it shows a version number at or above 0.G.A, we may well be in business. One more chec5Dlet)s see if you have RubyJems installed. (ype em --version . If you don)t et an error, s5ip to step 7. Otherwise, we)ll install a fresh Ruby. A. If Ruby is not installed, there)s a convenient one-clic5 installer at http%OOrubyinstaller.rubyfore.or . Kollow the download lin5, and run the resultin installer. Pou may as well install everythinDit)s a very small pac5ae, and you)ll et RubyJems as well. !repared e8clusively for Rida $l &ara'i I ;S($##I;J O; M $C OS " A2 7. ;ow we)ll use RubyJems to install Rails and a few thins that Rails needs. C%QN em install rails --include-dependencies ConratulationsE Pou)re now on Rails. 7.A Installin on Mac OS " 0. OS " version 02.6 /(ier1 ships with Ruby 0.G.A. Pou can verify this by startin the terminal application /use the Kinder to naviate to $pplications R Itilities and double-clic5 on (erminal 1 and enterin ruby -v at the prompt. /If you)re not runnin (ier, you)ll need to install Ruby 0.G.A or later yourself. (he Ini8 instructions that follow should help.1 A. ;e8t you have to install RubyJems. Jo to http%OOrubyems.rubyfore.or and follow the download lin5. OS " will typically unpac5 the archive file for you, so all you have to do is naviate to the downloaded direc- tory and /in the (erminal application1 type daveN tar 8'f rubyems-2.G.02.tar.' daveN cd rubyems-2.G.02 rubyems-2.G.02N sudo ruby setup.rb !assword% -enter your passwordN 7. 4e)ll now use RubyJems to install Rails. Still in the (erminal appli- cation, issue the followin command. daveN sudo em install rails --include-dependencies ConratulationsE Pou)re now on Rails. 7.7 Installin on Ini8O#inu8 Pou)ll need to have Ruby 0.G.A /or later1 and RubyJems installed in order to install Rails. 0. Many modern distributions come with Ruby installed. &rin up your favorite shell and type ruby -v . If Ruby responds, and is at least version 0.G.A, s5ip to step 7. A. Pou)ll probably be able to find a prepac5aed version of Ruby for your distribution. If not, Ruby is simple to install from source. a1 ?ownload ruby-8.y.'.tar.' from http%OOwww.ruby-lan.orOenO . b1 Intar the distribution, and enter the top-level directory. c1 ?o the usual open-source build. Report erratum !repared e8clusively for Rida $l &ara'i R $I#S $;? ? $($&$S>S A0 Ruby on Mac OS " (ier It)s ood that $pple includes Ruby in OS ". Infortunately, Ruby isn)t confi- ured particularly well in OS " version 02.6 /(ier1. Support for the readline library isn)t confiured, ma5in interactive tools such as irb a lot harder to use. (he Ruby build environment is also incorrect, so you can)t build e8tension libraries until you fi8 it. $nd, just to ma5e matters worse, we have reports that the Ruby MySB# e8tension library doesn)t wor5 properly. One way of addressin both issues is to follow #ucas Carlson)s instructions at http%OOtech.rufy.comOentryO6S /you)ll need the developer tools installed1. Once you)ve done this, you should be able to reinstall the Ruby MySB# em and thins should start wor5in. $n alternative for the adventurous is to reinstall Ruby usin fin5 or ?arwin !orts. (hat)s a pretty bi topic, and not one that we)ll cover here. daveN tar 8'f ruby-8.y.'.tar.' daveN cd ruby-8.y.' ruby-8.y.'N .Oconfiure ruby-8.y.'N ma5e ruby-8.y.'N ma5e test ruby-8.y.'N sudo ma5e install !assword% -enter your passwordN 7. Install RubyJems. Jo to http%OOrubyems.rubyfore.or , and follow the download lin5. Once you have the file locally, enter the followin in your shell window. daveN tar 8'f rubyems-2.G.02.tar.' daveN cd rubyems-2.G.02 rubyems-2.G.02N sudo ruby setup.rb !assword% -enter your passwordN 6. 4e)ll now use RubyJems to install Rails. Still in the shell, issue the followin command. daveN sudo em install rails --include-dependencies $nd /one last time1, conratulationsE Pou)re now on Rails. 7.6 Rails and ?atabases If your Rails application uses a database /and most do1, there)s one more installation step you may have to perform before you can start develop- ment. Report erratum !repared e8clusively for Rida $l &ara'i R $I#S $;? ? $($&$S>S AA Rails wor5s with the ?&A, MySB#, Oracle, !ostres, SB# Server, and SB#ite databases. Kor all but MySB#, you)ll need to install a database driver, a library that Rails can use to connect to and use your database enine. (his section contains the lin5s and instructions to et that done. &efore we et into the uly details, let)s see if we can s5ip the pain alto- ether. If you don)t care what database you use because you just want to e8periment with Rails, our recommendation is that you try MySB#. It)s easy to install, and Rails comes with a built-in driver /written in pure Ruby1 for MySB# databases. Pou can use it to connect a Rails application to MySB# with no e8tra wor5. (hat)s one of the reasons that the e8amples in this boo5 all use MySB#. 0 If you do end up usin MySB#, remember to chec5 the license if you)re distributin your application commercially. If you already have MySB# installed on your system, you)re all done. Oth- erwise, visit http%OOdev.mys*l.com , and follow their instructions on installin a MySB# database on your machine. Once you have MySB# runnin, you can safely s5ip ahead to Section 7.S, Rails and IS!s. If you)re still readin this, it means you)re wantin to connect to a database other than MySB#. (o do this, you)re oin to have to install a database driver. (he database libraries are all written in C and are primarily dis- tributed in source form. If you don)t want to o to the bother of buildin a driver from source, have a careful loo5 on the driver)s web site. Many times you)ll find that the author also distributes binary versions. If you can)t find a binary version, or if you)d rather build from source anyway, you)ll need a development environment on your machine to build the library. Inder 4indows, this means havin a copy of 9isual CTT. Inder #inu8, you)ll need cc and friends /but these will li5ely already be installed1. Inder OS ", you)ll need to install the developer tools /they come with the operatin system, but aren)t installed by default1. Once you)ve done that, you)ll also need to fi8 a minor problem in the $pple version of Ruby /unless you already installed the fi8 from #ucas Carlson described in the sidebar on the precedin pae1. Run the followin commands. daveN U Pou only need these commands under OS " V(ierV daveN sudo em install fi8rbconfi daveN sudo fi8rbconfi 0 =avin said that, if you want to put a hih-volume application into production, and you)re basin it on MySB#, you)ll probably want to install the low-level MySB# interface library anyway, as it offers better performance. Report erratum !repared e8clusively for Rida $l &ara'i R $I#S $;? ? $($&$S>S A7 ?atabases and (his &oo5 $ll the e8amples in this boo5 were developed usin MySB# /version 6.0.G or thereabouts1. If you want to follow alon with our code, it)s probably simplest if you use MySB# too. If you decide to use somethin different, it won)t be a major problem. Pou)ll just have to ma5e minor adjustments to the ??# we use to create tables, and you)ll need to use that database)s synta8 for some of the SB# we use in *ueries. /Kor e8ample, later in the boo5 we)ll use the MySB# now /1 function to compare a database column aainst the current date and time. ?ifferent databases will use a different name for the now /1 function.1 (he followin table lists the available database adapters and ives lin5s to their respective home paes. ?&A http%OOraa.ruby-lan.orOprojectOruby-dbA MySB# http%OOwww.tmtm.orOenOmys*lOruby Oracle http%OOrubyfore.orOprojectsOruby-ociG !ostres http%OOruby.scriptin.caOpostresO SB# Server /see note after table1 SB#ite http%OOrubyfore.orOprojectsOs*lite-ruby (here is a pure-Ruby version of the !ostres adapter available. ?ownload postres-pr from the Ruby-?&I pae at http%OOrubyfore.orOprojectsOruby-dbi . MySB# and SB#ite are also available for download as RubyJems / mys*l and s*lite respectively1. Interfacin to SB# Server re*uires a little effort. (he followin is based on a note written by Foey Jibson, who wrote the Rails adapter. $ssumin you used the one-clic5 installer to load Ruby onto your system, you already have most of the libraries you need to connect to SB# Server. =owever, the $?O module is not installed. Kollow these steps. 0. Kind the directory tree holdin your Ruby installation / C%QRuby by default1. &elow it is the folder QRubyQlibQrubyQsite,rubyQ0.GQ?&? . Inside this folder, create the directory $?O . A. 4ander over to http%OOruby-dbi.sourcefore.net and et the latest source distribution of Ruby-?&I. 7. In'ip the ?&I distribution into a local folder. ;aviate into this folder, and then to the directory srcQlibQdbd,ado . Copy the file $?O.rb from Report erratum !repared e8clusively for Rida $l &ara'i < >>!I;J I ! - (O -? $(> A6 this directory into the $?O directory in the Ruby tree that you created in step 0. (he SB# Server adapter will wor5 only on 4indows systems, as it relies on 4in7AO#>. 7.3 <eepin Ip-to-?ate $ssumin you installed Rails usin RubyJems, 5eepin up-to-date is rel- atively easy. Issue the command daveN em update rails and RubyJems will automatically update your Rails installation. (he ne8t time you restart your application it will pic5 up this latest version of Rails. /4e have more to say about updatin your application in production in the ?eployment and Scalin chapter, startin on pae 662.1 7.S Rails and IS!s If you)re loo5in to put a Rails application online in a shared hostin envi- ronment, you)ll need to find a Ruby-savvy IS!. #oo5 for one that supports Ruby, has the Ruby database drivers you need, and offers KastCJI andOor lihttpd support. 4e)ll have more to say about deployin Rails applications in Chapter AA, ?eployment and Scalin, on pae 662. ;ow that we have Rails installed, let)s use it. On to the ne8t chapter. Report erratum !repared e8clusively for Rida $l &ara'i Chapter 6 Instant Jratification #et)s write a trivial web application to verify we)ve ot Rails snuly installed on our machines. $lon the way, we)ll et a limpse of the way Rails applications wor5. 6.0 Creatin a ;ew $pplication 4hen you install the Rails framewor5, you also et a new command-line tool, rails , which is used to construct each new Rails application that you write. 4hy do we need a tool to do thisDwhy can)t we just hac5 away in our favorite editor, creatin the source for our application from scratchW 4ell, we could just hac5. $fter all, a Rails application is just Ruby source code. &ut Rails also does a lot of maic behind the curtain to et our applica- tions to wor5 with a minimum of e8plicit confiuration. (o et this maic to wor5, Rails needs to find all the various components of your applica- tion. $s we)ll see later /in Section 07.A, ?irectory Structure, on pae 0@71, this means that we need to create a specific directory structure, slottin the code we write into the appropriate places. (he rails command simply creates this directory structure for us and populates it with some standard Rails code. (o create your first Rails application, pop open a shell window and navi- ate to a place in your filesystem where you)ll want to create your applica- tion)s directory structure. In our e8ample, we)ll be creatin our projects in a directory called wor5 . In that directory, use the rails command to create an application called demo . &e slihtly careful hereDif you have an e8ist- in directory called demo , you will be as5ed if you want to overwrite any e8istin files. !repared e8clusively for Rida $l &ara'i C R>$(I;J $ ; >4 $ !!#IC$(IO; AS daveN cd wor5 wor5N rails demo create create appOapis create appOcontrollers create appOhelpers % % % create loOdevelopment.lo create loOtest.lo wor5N (he command has created a directory named demo . !op down into that directory, and list its contents /usin ls on a Ini8 bo8 or dir under 4in- dows1. Pou should see a bunch of files and subdirectories. wor5N cd demo demoN ls -p C=$;J>#OJ appO dbO loO testO R>$?M> componentsO docO publicO vendorO Ra5efile confiO libO scriptO $ll these directories /and the files they contain1 can be intimidatin to start with, but we can inore most of them when we start out. Kor now, we need to use only one of them, the public directory. $s its name suests, the public directory contains the files that we e8pose to our end users, the people usin our application. (he 5ey files are the dispatchers% dispatch.ci , dispatch.fci , and dispatch.rb . (he dispatch- dispatchers ers are responsible for acceptin incomin re*uests from users sittin at their browsers and directin those re*uests to the code in our application. (hey)re important files, but we won)t need to touch them for now. Pou)ll also notice that there)s a script directory underneath demo . It con- tains some utility scripts that we)ll be usin as we develop our applications. Kor now, we)ll use the script called server . It starts a stand-alone web server that can run our newly created Rails application under 4>&ric5. 0 So, without further ado, let)s start the application you just wrote. demoN ruby scriptOserver .N Rails application started on http%OO2.2.2.2%7222 XA223-2A-AS 2:%0S%67Y I;KO 4>&ric5 0.7.0 XA223-2A-AS 2:%0S%67Y I;KO ruby 0.G.A /A226-2G-A61 Xpowerpc-darwin@.3.2Y XA223-2A-AS 2:%0S%67Y I;KO 4>&ric5%%=((!Server-start% pid.AG7S port.7222 $s the last line of the start-up tracin indicates, we just started a web server on port 7222. A 4e can access the application by pointin a browser at http%OOlocalhost%7222 . (he result is shown in Kiure 6.0. 0 4>&ric5 is a pure-Ruby web server that is distributed with Ruby 0.G.0 and later. A (he 2.2.2.2 part of the address means that 4>&ric5 will accept connections on all inter- faces. On ?ave)s OS " system, that means both local interfaces /0A@.2.2.0 and %%01 and his #$; connection. Report erratum !repared e8clusively for Rida $l &ara'i = >##O , R $I#S E A@ Kiure 6.0% ;ewly Created Rails $pplication 4e)re oin to leave 4>&ric5 runnin in this console window. #ater on, as we write application code and run it via our browser, we)ll see this console window tracin the incomin re*uests. 4hen you)re done usin the application, you can press control-C to stop 4>&ric5. $t this point, we have a new application runnin, but it has none of our code in it. #et)s rectify this situation. 6.A =ello, RailsE ?ave spea5in% I can)t help itDI just have to write a =ello, 4orldE proram to try out a new system. (he e*uivalent in Rails would be an application that sends our cheery reetin to a browser. $s we discussed in Chapter A, (he $rchitecture of Rails $pplications, on pae :, Rails is a Model-9iew-Controller framewor5. Rails accepts incom- in re*uests from a browser, decodes the re*uest to find a controller, and calls an action method in that controller. (he controller then invo5es a particular view to display the results bac5 to the user. (he ood news is that Rails ta5es care of most of the internal plumbin that lin5s all these thins toether. (o write our simple =ello, 4orldE application, we need code for a controller and a view. 4e don)t need code for a model, as we)re not dealin with any data. #et)s start with the controller. Report erratum !repared e8clusively for Rida $l &ara'i = >##O , R $I#S E AG In the same way that we used the rails command to create a new Rails application, we can also use a enerator script to create a new controller for our project. (his command is called enerate , and it lives in the script subdirectory of the demo project we created. So, to create a controller called say , we ma5e sure we)re in the demo directory and run the script, passin in the name of the controller we want to create. 7 demoN ruby scriptOenerate controller Say e8ists appOcontrollersO e8ists appOhelpersO create appOviewsOsay e8ists testOfunctionalO create appOcontrollersOsay,controller.rb create testOfunctionalOsay,controller,test.rb create appOhelpersOsay,helper.rb (he script los the files and directories it e8amines, notin when it adds new Ruby scripts or directories to your application. Kor now, we)re inter- ested in one of these scripts and /in a minute1 the new directory. (he source file we)ll be loo5in at is the controller. Pou)ll find it in the file appOcontrollersOsay,controller.rb . #et)s have a loo5 at it. definin classes W R pae 6@0 Kile A2: class SayController - $pplicationController end !retty minimal, ehW SayController is an empty class that inherits from $ppli- cationController , so it automatically ets all the default controller behavior. #et)s spice it up. 4e need to add some code to have our controller handle the incomin re*uest. 4hat does this code have to doW Kor now, it)ll do nothinDwe simply need an empty action method. So the ne8t *uestion is, what should this method be calledW $nd to answer this *uestion, we need to loo5 at the way Rails handles re*uests. Rails and Re*uest IR#s #i5e any other web application, a Rails application appears to its users to be associated with a IR#. 4hen you point your browser at that IR#, you are tal5in to the application code, which enerates a response bac5 to you. =owever, the real situation is somewhat more complicated than that. #et)s imaine that your application is available at the IR# http%OOprapro.comO onlineOdemo . (he web server that is hostin your application is fairly smart 7 (he concept of the Lname of the controllerM is actually more comple8 than you miht thin5, and we)ll e8plain it in detail in Section 07.6, ;amin Conventions, on pae 0G2. Kor now, let)s just assume the controller is called Say. Report erratum !repared e8clusively for Rida $l &ara'i = >##O , R $I#S E A: http%OOprapro.comOonlineOdemoOsayOhello 0. Kirst part of IR# identifies the application A. ;e8t part selects a controller /say1 7. #ast part identifies the action to invo5e Kiure 6.A% IR#s $re Mapped to Controllers and $ctions about paths. It 5nows that once it sees the onlineOdemo part of the path, it must be tal5in to the application. $nythin past this in the incomin IR# will not chane thatDthe same application will still be invo5ed. $ny additional path information is passed to the application, which can use it for its own internal purposes. Rails uses the path to determine the name of the controller to use and the name of the action to invo5e on that controller. 6 (his is illustrated in Kiure 6.A . (he first part of the path followin the application is the name of the controller, and the second part is the name of the action. (his is shown in Kiure 6.7, on the followin pae. Our Kirst $ction #et)s add an action called hello to our say controller. Krom the discussion in the previous section, we 5now that addin a hello action means creatin a method called hello in the class SayController . &ut what should it doW Kor now, it doesn)t have to do anythin. Remember that a controller)s job is to set up thins so that the view 5nows what to display. In our first appli- cation, there)s nothin to set up, so an empty action will wor5 fine. Ise methods W R pae 6S: your favorite editor to chane the file say,controller.rb in the appOcontrollers directory, addin the hello /1 method as shown. Kile A02 class SayController - $pplicationController def hello end end 6 Rails is fairly fle8ible when it comes to parsin incomin IR#s. In this chapter, we describe the default mechanism. 4e)ll show how to override this in Section 0S.7, Routin Re*uests, on pae AG2. Report erratum !repared e8clusively for Rida $l &ara'i = >##O , R $I#S E 72 http%OOprapro.comOonlineOdemoOsayOhello class CustomerController - ... def hello U actions... end end class OrderController - ... def hello U actions... end end class IpdateController - ... def hello U actions... end end class SayController - ... def hello U actions... end end class #oinController - ... def hello U actions... end end class SayController - ... def hello U code for hello action... end end (hen invo5e its hello/1 method Create an instance of class SayController Kiure 6.7% Rails Routes to Controllers and $ctions ;ow let)s try callin it. Kind a browser window, and naviate to the IR# http%OOlocalhost%7222OsayOhello . /;ote that in this test environment we don)t have any application strin at the front of the pathDwe route directly to the controller.1 Pou)ll see somethin that loo5s li5e the followin. It miht be annoyin, but the error is perfectly reasonable /apart from the weird path1. 4e created the controller class and the action method, but we haven)t told Rails what to display. $nd that)s where the views come in. Remember when we ran the script to create the new controllerW (he command added three files and a new directory to our application. (hat directory contains the template files for the controller)s views. In our case, we created a controller named say , so the views will be in the directory appOviewsOsay . (o complete our =ello, 4orldE application, let)s create a template. &y default, Rails loo5s for templates in a file with the same name as the Report erratum !repared e8clusively for Rida $l &ara'i = >##O , R $I#S E 70 action it)s handlin. In our case, that means we need to create a file called appOviewsOsayOhello.rhtml . /4hy . rhtml W 4e)ll e8plain in a minute.1 Kor now, let)s just put some basic =(M# in there. Kile A00 -htmlN -headN -titleN=ello, RailsE-OtitleN -OheadN -bodyN -h0N=ello from RailsE-Oh0N -ObodyN -OhtmlN Save the file hello.rhtml , and refresh your browser window. Pou should see it display our friendly reetin. ;otice that we didn)t have to restart the application to see the update. ?urin development, Rails automatically interates chanes into the runnin application as you save files. So far, we)ve added code to two files in our Rails application tree. 4e added an action to the controller, and we created a template to display a pae in the browser. (hese files live in standard locations in the Rails hier- archy% controllers o into appOcontrollers , and views o into subdirectories of appOviews . (his is shown in Kiure 6.6, on the ne8t pae. Ma5in It ?ynamic So far, our Rails application is pretty borinDit just displays a static pae. (o ma5e it more dynamic, let)s have it show the current time each time it displays the pae. (o do this, we need to ma5e a chane to the template file in the viewDit now needs to include the time as a strin. (hat raises two *uestions. Kirst, how do we add dynamic content to a templateW Second, where do we et the time fromW ?ynamic Content (here are two ways of creatin dynamic templates in Rails. One uses a technoloy called &uilder , which we discuss in Section 0@.A, &uilder tem- plates, on pae 7A:. (he second way, which we)ll use here, is to embed Report erratum !repared e8clusively for Rida $l &ara'i = >##O , R $I#S E 7A demoO appO controllersO viewsO modelsO hello.rhtml say,controller.rb sayO class SayController - $pplicationController def hello end end -htmlN -headN -titleN=ello, RailsE-OtitleN -OheadN -bodyN -h0N=ello from RailsE-Oh0N -ObodyN -OhtmlN Kiure 6.6% Standard #ocations for Controllers and 9iews Ruby code in the template itself. (hat)s why we named our template file hello.rhtml % the . rhtml suffi8 tells Rails to e8pand the content in the file usin a system called >Rb /for >mbedded Ruby1. >Rb is a filter that ta5es an . rhtml file and outputs a transformed version. (he output file is often =(M# in Rails, but it can be anythin. ;ormal con- tent is passed throuh without bein chaned. =owever, content between -Z. and ZN is interpreted as Ruby code and e8ecuted. (he result of that e8ecution is converted into a strin, and that value is substituted into the file in place of the -Z. ... ZN se*uence. Kor e8ample, chane hello.rhtml to contain the followin. Kile 07S -ulN -liN$ddition% -Z. 0TA ZN -OliN -liNConcatenation% -Z. VcowV T VboyV ZN -OliN 0.hour.from,now W R pae 0G3 -liN(ime in one hour% -Z. 0.hour.from,now ZN -OliN -OulN 4hen you refresh your browser, the template will enerate the followin =(M#. -ulN -liN$ddition% 7 -OliN -liNConcatenation% cowboy -OliN -liN(ime in one hour% Sat Keb AS 0G%77%03 CS( A223 -OliN -OulN Report erratum !repared e8clusively for Rida $l &ara'i = >##O , R $I#S E 77 Ma5in ?evelopment >asier Pou miht have noticed somethin about the development we)ve been doin so far. $s we)ve been addin code to our application, we haven)t had to touch the runnin application. It)s been happily chuin away in the bac5round. $nd yet each chane we ma5e is available whenever we access the application throuh a browser. 4hat ivesW It turns out that the 4>&ric5-based Rails dispatcher is pretty clever. In development mode /as opposed to testin or production1, it automati- cally reloads application source files when a new re*uest comes alon. (hat way, when we edit our application, the dispatcher ma5es sure it)s runnin the most recent chanes. (his is reat for development. =owever, this fle8ibility comes at a costDit causes a short pause after you enter a IR# before the application responds. (hat)s caused by the dis- patcher reloadin stuff. Kor development it)s a price worth payin, but in production it would be unacceptable. &ecause of this, this feature is disabled for production deployment /see Chapter AA, ?eployment and Scalin, on pae 6621. In the browser window, you)ll see somethin li5e the followin. H $ddition% 7 H Concatenation% cowboy H (ime in one hour% Sat Keb AS 0G%77%03 CS( A223 In addition, stuff in rhtml between -Z and ZN /without an e*uals sin1 is interpreted as Ruby code that is e8ecuted with no substitution bac5 into the output. (he interestin thin about this 5ind of processin, thouh, is that it can be intermi8ed with non-Ruby code. Kor e8ample, we could ma5e a festive version of hello.rhtml . 7.times W R pae 6@@ -Z 7.times do ZN =oE-br ON -Z end ZN Merry ChristmasE Refresh aain, and you)ll be listenin for sleih bells. =oE =oE =oE Merry ChristmasE ;ote how the te8t in the file within the Ruby loop is sent to the output stream once for each iteration of the loop. Report erratum !repared e8clusively for Rida $l &ara'i = >##O , R $I#S E 76 4e can mi8 the two forms. In this e8ample, the loop sets a variable that is interpolated into the te8t each time the loop e8ecutes. Kile 07G -Z 7.downto/01 do [count[ ZN -Z. count ZN...-br ON -Z end ZN #ift offE (hat will send the followin to the browser. 7...-br ON A...-br ON 0...-br ON #ift offE (here)s one last thin with >Rb. Buite often the values that you as5 it to substitute usin -Z....ZN contain less-than and ampersand characters that are sinificant to =(M#. (o prevent these messin up your pae /and, as we)ll see in Chapter A0, Securin Pour Rails $pplication, on pae 6A@, to avoid potential security problems1, you)ll want to escape these characters. Rails has a helper method, h /1, that does this. Most of the time, you)re oin to want to use it when substitutin values into =(M# paes. Kile 07: >mail% -Z. h/V$nn \ &ill -fra'ers]isp.emailNV1 ZN In this e8ample, the h /1 method prevents the special characters in the e-mail address from arblin the browser displayDthey)ll be escaped as =(M# entities. (he browser sees >mail% $nn \ &ill -fra'ers]isp.emailN Dthe special characters are displayed appropriately. $ddin the (ime Our oriinal problem was to display the time to users of our application. 4e now 5now how to ma5e our application display dynamic data. (he second issue we have to address is wor5in out where to et the time from. One approach would be to embed a call to Ruby)s (ime.now /1 method in our say.rhtml template. -htmlN -headN -titleN=ello, RailsE-OtitleN -OheadN -bodyN -h0N=ello from RailsE-Oh0N -pN (he time is -Z. (ime.now ZN -OpN -ObodyN -OhtmlN Report erratum !repared e8clusively for Rida $l &ara'i = >##O , R $I#S E 73 (his wor5s. >ach time we access this pae, the user will see the current time substituted into the body of the response. $nd for our trivial applica- tion, that miht be ood enouh. In eneral, thouh, we probably want to do somethin slihtly different. 4e)ll move the determination of the time to be displayed into the controller and leave the view the simple job of dis- playin it. 4e)ll chane our action method in the controller to set the time value into an instance variable called ]time . instance variable W R pae 6@A Kile A0A class SayController - $pplicationController def hello ]time . (ime.now end end In the . rhtml template we)ll use this instance variable to substitute the time into the output. Kile A07 -htmlN -headN -titleN=ello, RailsE-OtitleN -OheadN -bodyN -h0N=ello from RailsE-Oh0N -pN It is now -Z. ]time ZN. -OpN -ObodyN -OhtmlN 4hen we refresh our browser window, we see the time displayed, as shown in Kiure 6.3. ;otice that if you hit Refresh in your browser, the time updates each time the pae is displayed. #oo5s as if we)re really eneratin dynamic content. 4hy did we o to the e8tra trouble of settin the time to be displayed in the controller and then usin it in the viewW Jood *uestion. In this appli- Kiure 6.3% =ello, 4orldE with a (ime ?isplay Report erratum !repared e8clusively for Rida $l &ara'i = >##O , R $I#S E 7S Foe $s5s... =ow ?oes the 9iew Jet the (imeW In the description of views and controllers, we showed the controller set- tin the time to be displayed into an instance variable. (he . rhtml file used that instance variable to substitute in the current time. &ut the instance data of the controller object is private to that object. =ow does >Rb et hold of this private data to use in the templateW (he answer is both simple and subtle. Rails does some Ruby maic so that the instance variables of the controller object are injected into the template object. $s a conse*uence, the view template can access any instance variables set in the controller as if they were its own. cation, you could just embed the call to (ime.now /1 in the template, but by puttin it in the controller instead, you buy yourself some benefits. Kor e8ample, we may want to e8tend our application in the future to support users in many countries. In that case we)d want to locali'e the display of the time, choosin both the format appropriate to the user)s locale and a time appropriate to their time 'one. (hat would be a fair amount of application-level code, and it would probably not be appropriate to embed it at the view level. &y settin the time to display in the controller, we ma5e our application more fle8ibleDwe can chane the display format and time 'one in the controller without havin to update any view that uses that time object. (he Story So Kar #et)s briefly review how our current application wor5s. 0. (he user naviates to our application. In our case, we do that usin a local IR# such as http%OOlocalhost%7222OsayOhello . A. Rails analy'es the IR#. (he say part is ta5en to be the name of a con- troller, so Rails creates a new instance of the Ruby class SayController /which it finds in appOcontrollersOsay,controller.rb 1. 7. (he ne8t part of the IR# path, hello , identifies an action. Rails invo5es a method of that name in the controller. (his action method Report erratum !repared e8clusively for Rida $l &ara'i # I;<I;J ! $J>S ( OJ>(=>R 7@ creates a new (ime object holdin the current time and tuc5s it away in the ]time instance variable. 6. Rails loo5s for a template to display the result. It searchs the direct- ory appOviews for a subdirectory with the same name as the con- troller / say 1, and in that subdirectory for a file named after the action / hello.rhtml 1. 3. Rails processes this template throuh >Rb, e8ecutin any embedded Ruby and substitutin in values set up by the controller. S. (he result is sent bac5 to the browser, and Rails finishes processin this re*uest. (his isn)t the whole storyDRails ives you lots of opportunities to over- ride this basic wor5flow /and we)ll be ta5in advantae of these shortly1. $s it stands, our story illustrates convention over confiuration, one of the fundamental parts of the philosophy of Rails. &y providin conve- nient defaults and by applyin certain conventions, Rails applications are typically written usin little or no e8ternal confiurationDthins just 5nit themselves toether in a natural way. 6.7 #in5in !aes (oether It)s a rare web application that has just one pae. #et)s see how we can add another stunnin e8ample of web desin to our =ello, 4orldE application. ;ormally, each style of pae in your application will correspond to a sep- arate view. In our case, we)ll also use a new action method to handle the pae /althouh that isn)t always the case, as we)ll see later in the boo51. 4e)ll use the same controller for both actions. $ain, this needn)t be the case, but we have no compellin reason to use a new controller riht now. 4e already 5now how to add a new view and action to a Rails application. (o add the action, we define a new method in the controller. #et)s call this action oodbye. Our controller now loo5s li5e the followin. Kile A06 class SayController - $pplicationController def hello ]time . (ime.now end def oodbye end end Report erratum !repared e8clusively for Rida $l &ara'i # I;<I;J ! $J>S ( OJ>(=>R 7G Kiure 6.S% $ &asic Joodbye Screen ;e8t we have to create a new template in the directory appOviewsOsay . (his time it)s called oodbye.rhtml , because by default templates are named after the associated actions. Kile A03 -htmlN -headN -titleNSee Pou #aterE-OtitleN -OheadN -bodyN -h0NJoodbyeE-Oh0N -pN It was nice havin you here. -OpN -ObodyN -OhtmlN Kire up our trusty browser aain, but this time point to our new view usin the IR# http%OOlocalhost%7222OsayOoodbye . Pou should see somethin li5e Kiure 6.S . ;ow we need to lin5 the two screens toether. 4e)ll put a lin5 on the hello screen that ta5es us to the oodbye screen, and vice versa. In a real application we miht want to ma5e these proper buttons, but for now we)ll just use hyperlin5s. 4e already 5now that Rails uses a convention to parse the IR# into a taret controller and an action within that controller. So a simple approach would be to adopt this IR# convention for our lin5s. (he file hello.rhtml would contain the followin. Report erratum !repared e8clusively for Rida $l &ara'i # I;<I;J ! $J>S ( OJ>(=>R 7: -htmlN .... -pN Say -a href.VOsayOoodbyeVNJood&ye-OaNE -OpN -OhtmlN and the file oodbye.rhtml would point the other way. -htmlN .... -pN Say -a href.VOsayOhelloVN=ello-OaNE -OpN -OhtmlN (his approach would certainly wor5, but it)s a bit fraile. If we were to move our application to a different place on the web server, the IR#s would no loner be valid. It also encodes assumptions about the Rails IR# format into our codeC it)s possible a future version of Rails miht chane this. Kortunately, these aren)t ris5s we have to ta5e. Rails comes with a bunch of helper methods that can be used in view templates. =ere, we)ll use the helper method lin5,to /1, which creates a hyperlin5 to an action. 3 Isin lin5,to /1, hello.rhtml becomes Kile A0@ -htmlN -headN -titleN=ello, RailsE-OtitleN -OheadN -bodyN -h0N=ello from RailsE-Oh0N -pN It is now -Z. ]time ZN. -OpN -pN (ime to say -Z. lin5,to VJood&yeEV, %action .N VoodbyeV ZN -OpN -ObodyN -OhtmlN (here)s a lin5,to /1 call within an >Rb -Z....ZN se*uence. (his creates a lin5 to a IR# that will invo5e the oodbye /1 action. (he first parameter in the call to lin5,to /1 is the te8t to be displayed in the hyperlin5, and the ne8t parameter tells Rails to enerate the lin5 to the oodbye action. $s we don)t specify a controller, the current one will be used. #et)s stop for a minute to consider that last parameter to lin5,to /1. 4e wrote lin5,to VJood&yeEV, %action .N VoodbyeV 3 (he lin5,to /1 method can do a lot more than this, but let)s ta5e it ently for now.... Report erratum !repared e8clusively for Rida $l &ara'i # I;<I;J ! $J>S ( OJ>(=>R 62 Kiure 6.@% =ello !ae #in5ed to the Joodbye !ae (he %action part is a Ruby symbol. Pou can thin5 of the colon as meanin the thin named..., so %action means the thin named action. (he .N Vood- byeV associates the strin oodbye with the name action . In effect, this ives us 5eyword parameters for methods. Rails ma5es e8tensive use of this facilityDwhenever a method ta5es a number of parameters and some of those parameters are optional, you can use this 5eyword parameter facility to ive those parameters values. O<. &ac5 to the application. If we point our browser at our hello pae, it will now contain the lin5 to the oodbye pae, as shown in Kiure 6.@ . 4e can ma5e the correspondin chane in oodbye.rhtml , lin5in it bac5 to the initial hello pae. Kile A0S -htmlN -headN -titleNSee Pou #aterE-OtitleN -OheadN -bodyN -h0NJoodbyeE-Oh0N -pN It was nice havin you here. -OpN -pN Say -Z. lin5,to V=elloV, %action.NVhelloV ZN aain. -OpN -ObodyN -OhtmlN Report erratum !repared e8clusively for Rida $l &ara'i 4 =$( 4 > F IS( ? I? 60 6.6 4hat 4e Fust ?id In this chapter we constructed a toy application. ?oin so showed us H how to create a new Rails application and how to create a new con- troller in that application, H how Rails maps incomin re*uests into calls on your code, H how to create dynamic content in the controller and display it via the view template, and H how to lin5 paes toether. (his is a reat foundation. ;ow let)s start buildin real applications. Report erratum !repared e8clusively for Rida $l &ara'i !art II &uildin an $pplication !repared e8clusively for Rida $l &ara'i Chare itE 4ilma Klintstone and &etty Rubble Chapter 3 (he ?epot $pplication 4e could mess around all day hac5in toether simple test applications, but that won)t help us pay the bills. So let)s et our teeth into somethin meatier. #et)s create a web-based shoppin cart application called ?epot. ?oes the world need another shoppin cart applicationW ;ope, but that hasn)t stopped hundreds of developers from writin one. 4hy should we be differentW More seriously, it turns out that our shoppin cart will illustrate many of the features of Rails development. 4e)ll see how to create simple main- tenance paes, lin5 database tables, handle sessions, and create forms. Over the ne8t eiht chapters, we)ll also touch on peripheral topics such as unit testin, security, and pae layout. 3.0 Incremental ?evelopment 4e)ll be developin this application incrementally. 4e won)t attempt to specify everythin before we start codin. Instead, we)ll wor5 out enouh of a specification to let us start and then immediately create some function- ality. 4e)ll try thins out, ather feedbac5, and continue on with another cycle of mini-desin and development. (his style of codin isn)t always applicable. It re*uires close cooperation with the application)s users, because we want to ather feedbac5 as we o alon. 4e miht ma5e mista5es, or the client miht discover they)d as5ed for one thin but really wanted somethin different. It doesn)t matter what the reasonDthe earlier we discover we)ve made a mista5e, the less e8pen- sive it will be to fi8 that mista5e. $ll in all, with this style of development there)s a lot of chane as we o alon. !repared e8clusively for Rida $l &ara'i 4 =$( ? >!O( ? O>S 66 &ecause of this, we need to use a toolset that doesn)t penali'e us for chan- in our mind. If we decide we need to add a new column to a database table, or chane the naviation between paes, we need to be able to et in there and do it without a bunch of codin or confiuration hassle. $s you)ll see, Ruby on Rails shines when it comes to dealin with chaneDit)s an ideal aile prorammin environment. $nyway, on with the application. 3.A 4hat ?epot ?oes #et)s start by jottin down an outline specification for the ?epot applica- tion. 4e)ll loo5 at the hih-level use cases and s5etch out the flow throuh the web paes. 4e)ll also try wor5in out what data the application needs /ac5nowledin that our initial uesses will li5ely be wron1. Ise Cases $ use case is simply a statement about how some entity uses a system. Consultants invent these 5inds of phrases when they want to chare more moneyDit)s a perversion of business life that fancy words always cost more than plain ones, even thouh the plain ones are more valuable. ?epot)s use cases are simple /some would say traically so1. 4e start off by identifyin two different roles or actors% the buyer and the seller. (he buyer uses ?epot to browse the products we have to sell, select some to purchase, and supply the information needed to create an order. (he seller uses ?epot to maintain a list of products to sell, to determine the orders that are awaitin shippin, and to mar5 orders as shipped. /(he seller also uses ?epot to ma5e scads of money and retire to a tropical island, but that)s the subject of another boo5.1 Kor now, that)s all the detail we need. 4e could o into e8cruciatin detail about Lwhat it means to maintain productsM and Lwhat constitutes an order ready to ship,M but why botherW If there are details that aren)t obvi- ous, we)ll discover them soon enouh as we reveal successive iterations of our wor5 to the customer. (al5in of ettin feedbac5, let)s not foret to et some riht nowDlet)s ma5e sure our initial /admittedly s5etchy1 use cases are on the mar5 by as5in our user. $ssumin the use cases pass muster, let)s wor5 out how the application will wor5 from the perspectives of its various users. Report erratum !repared e8clusively for Rida $l &ara'i 4 =$( ? >!O( ? O>S 63 !ae Klow ?ave spea5in% I always li5e to have an idea of the main paes in my appli- cations, and to understand rouhly how users naviate between them. (his early in the development, these pae flows are li5ely to be incomplete, but they still help me focus on what needs doin and 5now how thins are se*uenced. Some fol5s li5e to moc5 up web application pae flows usin !hotoshop, or 4ord, or /shudder1 =(M#. I li5e usin a pencil and paper. It)s *uic5er, and the customer ets to play too, rabbin the pencil and scribblin alter- ations riht on the paper. Kiure 3.0% Klow of &uyer !aes My first s5etch of the buyer flow, shown Kiure 3.0 , is pretty traditional. (he buyer sees a catalo pae, from which she selects one product at a time. >ach product selected ets added to the cart, and the cart is displayed after each selection. (he buyer can continue shoppin usin the catalo paes, or she can chec5 out and buy the contents of the cart. Report erratum !repared e8clusively for Rida $l &ara'i 4 =$( ? >!O( ? O>S 6S ?urin chec5out we capture contact and payment details and then display a receipt pae. 4e don)t yet 5now how we)re oin to handle payment, so those details are fairly vaue in the flow. (he seller flow, shown in Kiure 3.A , is also fairly simple. $fter loin in, the seller sees a menu lettin her create or view a product, or ship e8istin orders. Once viewin a product, the seller may optionally edit the product information or delete the product entirely. Kiure 3.A% Klow of Seller !aes (he shippin option is very simplistic. It displays each order that has not yet been shipped, one order per pae. (he seller may choose to s5ip to the ne8t, or may ship the order, usin the information from the pae as appropriate. (he shippin function is clearly not oin to survive lon in the real world, but shippin is also one of those areas where reality is often straner than you miht thin5. Overspecify it upfront, and we)re li5ely to et it wron. Kor now let)s leave it as it is, confident that we can chane it as the user ains e8perience usin our application. Report erratum !repared e8clusively for Rida $l &ara'i 4 =$( ? >!O( ? O>S 6@ ?ata (he last thin we need to thin5 about before plowin into the first round of codin is the data we)re oin to be wor5in with. ;otice that we)re not usin words such as schema or classes here. 4e)re also not tal5in about databases, tables, 5eys, and the li5e. 4e)re simply tal5in about data. $t this stae in the development, we don)t 5now if we)ll even be usin a databaseDsometimes a flat file beats a database table hands down. &ased on the use cases and the flows, it seems li5ely that we)ll be wor5in with the data shown in Kiure 3.7 . $ain, pencil and paper seems a whole lot easier than some fancy tool, but use whatever wor5s for you. Kiure 3.7% Initial Juess at $pplication ?ata 4or5in on the data diaram raised a couple of *uestions. $s the user builds their shoppin cart, we)ll need somewhere to 5eep the list of prod- ucts she)s added to it, so I added a cart. &ut apart from its use as a tran- sient place to 5eep this list, the cart seems to be somethin of a hostDI couldn)t find anythin meaninful to store in it. (o reflect this uncertainty, I put a *uestion mar5 inside the cart)s bo8 in the diaram. I)m assumin this uncertainty will et resolved as we implement ?epot. Comin up with the hih-level data also raised the *uestion of what infor- mation should o into an order. $ain, I chose to leave this fairly open for Report erratum !repared e8clusively for Rida $l &ara'i # >( ) S C O?> 6G nowDwe)ll refine this further as we start showin the customer our early iterations. Kinally, you miht have noticed that I)ve duplicated the product)s price in the line item data. =ere I)m brea5in the Linitially, 5eep it simpleM rule slihtly, but it)s a transression based on e8perience. If the price of a product chanes, that price chane should not be reflected in the line item price of currently open orders, so each line item needs to reflect the price of the product at the time the order was made. $ain, at this point I)ll double chec5 with my customer that we)re still on the riht trac5. /=opefully, my customer was sittin in the room with me while I drew these three diarams.1 3.7 #et)s Code So, after sittin down with the customer and doin some preliminary anal- ysis, we)re ready to start usin a computer for developmentE 4e)ll be wor5- in from our oriinal three diarams, but the chances are pretty ood that we)ll be throwin them away fairly *uic5lyDthey)ll become outdated as we ather feedbac5. Interestinly, that)s why we didn)t spend too lon on themDit)s easier to throw somethin away if you didn)t spend a lon time creatin it. In the chapters that follow, we)ll start developin the application based on our current understandin. =owever, before we turn that pae, we have to answer just one more *uestion. 4hat should we do firstW I li5e to wor5 with the customer so we can jointly aree on priorities. In this case, I)d point out to her that it)s hard to develop anythin else until we have some basic products defined in the system, so I)d suest spendin a couple of hours ettin the initial version of the product maintenance functionality up and runnin. $nd, of course, she)d aree. Report erratum !repared e8clusively for Rida $l &ara'i Chapter S (as5 $% !roduct Maintenance Our first development tas5 is to create the web interface that lets us main- tain our product informationDcreate new products, edit e8istin products, delete unwanted ones, and so on. 4e)ll develop this application in small iterations, where small means Lmeasured in minutes.M #et)s et started.... S.0 Iteration $0% Jet Somethin Runnin !erhaps surprisinly, we should et the first iteration of this wor5in in almost no time. 4e)ll start off by creatin a new Rails application. (his is where we)ll be doin all our wor5. ;e8t, we)ll create a database to hold our information /in fact we)ll create three databases1. Once that roundwor5 is in place, we)ll H create the table to hold the product information, H confiure our Rails application to point to our database/s1, and H have Rails enerate the initial version of our product maintenance application for us. Create a Rails $pplication &ac5 on pae A3 we saw how to create a new Rails application. Jo to a command prompt, and type rails followed by the name of our project. In this case, our project is called depot , so type wor5N rails depot 4e see a bunch of output scroll by. 4hen it has finished, we find that a new directory, depot , has been created. (hat)s where we)ll be doin our wor5. !repared e8clusively for Rida $l &ara'i I (>R$(IO; $0% J >( S OM>(=I;J R I;;I;J 32 wor5N cd depot wor5N ls C=$;J>#OJ app db lo test R>$?M> components doc public vendor Ra5efile confi lib script Create the ?atabases Kor this application, we)ll use the open-source MySB# database server /which you)ll need too if you)re followin alon with the code1. Kor reasons that will become clear later, we)re actually oin to create three databases. H depot,development will be our development database. $ll of our pro- rammin wor5 will be done here. H depot,test is a test database. It is considered to be transient, so it)s perfectly acceptable for us to empty it out to ive our tests a fresh place to start each time they run. H depot,production is the production database. Our application will use this when we put it online. 4e)ll use the mys*l command-line client to create our databases, but if you)re more comfortable with tools such as phpmyadmin or CocoaMySB# , o for it. /In the session that follows, we)ve stripped out MySB#)s somewhat useless responses to each command.1 depotN mys*l -u root -p >nter password% ^^^^^^^ 4elcome to the MySB# monitor. Commands end with C or Q. mys*lN create database depot,developmentC mys*lN create database depot,testC mys*lN create database depot,productionC mys*lN rant all on depot,development.^ to + dave + ] + localhost + C mys*lN rant all on depot,test.^ to + dave + ] + localhost + C mys*lN rant all on depot,production.^ to + prod + ] + localhost + identified by + wibble + C mys*lN e8it Create the !roducts (able &ac5 in Kiure 3.7, on pae 6@, we s5etched out the basic content of the products table. ;ow let)s turn that into reality. =ere)s the ?ata ?efinition #anuae /??#1 for creatin the products table in MySB#. Kile AA drop table if e8ists productsC create table products / id int not null auto,increment, title varchar/0221 not null, description te8t not null, imae,url varchar/A221 not null, price decimal/02,A1 not null, primary 5ey /id1 1C Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; $0% J >( S OM>(=I;J R I;;I;J 30 Our table includes the product title, description, imae, and price, just as we s5etched out. 4e)ve also added somethin new% a column called id . (his is used to ive each row in the table a uni*ue 5ey, allowin other tables to reference products. &ut there)s more to this id column. &y default, Rails assumes that every table it handles has as its primary 5ey an inteer column called id . 0 Internally, Rails uses the value in this column to 5eep trac5 of the data it has loaded from the database and to lin5 between data in different tables. Pou can override this namin sys- tem, but unless you)re usin Rails to wor5 with leacy schemas that you can)t chane, we recommend you just stic5 with usin the name id . It)s all very well comin up with the ??# for the products table, but where should we store itW I)m a stron believer in 5eepin the ??# for my appli- cation databases under version control, so I always create it in a flat file. Kor a Rails application, I call the file create.s*l and put it in my applica- tion)s db subdirectory. (his lets me use the mys*l client to e8ecute the ??# and create the table in my development database. $ain, you)re free to do this usin JII or web-based tools if you prefer. depotN mys*l depot,development -dbOcreate.s*l Confiure the $pplication In many simple scriptin-lanuae web applications, the information on how to connect to the database is embedded directly into the codeDyou miht find a call to some connect /1 method, passin in host and database names, alon with a user name and password. (his is danerous, because password information sits in a file in a web-accessible directory. $ small server confiuration error could e8pose your password to the world. (he approach of embeddin connection information into code is also infle8- ible. One minute you miht be usin the development database as you hac5 away. ;e8t you miht need to run the same code aainst the test database. >ventually, you)ll want to deploy it into production. >very time you switch taret databases, you have to edit the connection call. (here)s a rule of prorammin that says you)ll mistype the password only when switchin the application into production. Smart developers 5eep the connection information out of the code. Some- times you miht want to use some 5ind of repository to store it all /Fava developers often use F;?I to loo5 up connection parameters1. (hat)s a bit 0 ;ote that the case is sinificant. If you use a nannyish JII tool that insists on chanin the column name to Id , you miht have problems. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; $0% J >( S OM>(=I;J R I;;I;J 3A development% adapter% mys*l database% rails,development host% localhost username% root password% test% adapter% mys*l database% rails,test host% localhost username% root password% production% adapter% mys*l database% rails,production host% localhost username% root password% development% adapter% mys*l database% depot,development host% localhost username% -blan5N password% test% adapter% mys*l database% depot,test host% localhost username% -blan5N password% production% adapter% mys*l database% depot,production host% localhost username% prod password% wibble confiOdatabase.yml >dit the file Oriinal Kile ;ew Kile Kiure S.0% Confiure the database.yml Kile heavy for the averae web application that we)ll write, so Rails simply uses a flat file. Pou)ll find it in confiOdatabase.yml . A $s Kiure S.0 shows, database.yml contains three sections, one each for the development, test, and production databases. Isin your favorite edi- tor, chane the fields in each to match the databases we created. ;ote that in the diaram we)ve left the username fields blan5 for the develop- ment and test environments in the new database.yml file. (his is con- venient, as it means that different developers will each use their own usernames when connectin. =owever, we)ve had reports that with some combinations of MySB#, database drivers, and operatin systems, leav- in these fields blan5 ma5es Rails attempt to connect to the database as the root user. Should you et an error such as $ccess denied for user )root)])localhost.localdomain), put an e8plicit username in these two fields. Create the Maintenance $pplication O<. $ll the round wor5 has been done. 4e set up our ?epot application as a Rails project. 4e)ve created the databases and the products table. $nd A (he . yml part of the name stands for P$M#, or P$M# $in)t a Mar5up #anuae. It)s a simple way of storin structured information in flat files /and it isn)t "M#1. Recent Ruby releases include built-in P$M# support. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; $0% J >( S OM>(=I;J R I;;I;J 37 we confiured our application to be able to connect to the databases. (ime to write the maintenance app. depotN ruby scriptOenerate scaffold !roduct $dmin dependency model e8ists appOmodelsO e8ists testOunitO e8ists testOfi8turesO create appOmodelsOproduct.rb create testOunitOproduct,test.rb % % create appOviewsOadminOshow.rhtml create appOviewsOadminOnew.rhtml create appOviewsOadminOedit.rhtml create appOviewsOadminO,form.rhtml (hat wasn)t hard now, was itW 7,6 (hat sinle command has written a basic maintenance application. (he !roduct parameter told the command the name of the model we want, and the $dmin parameter specifies the name of the controller. &efore we worry about just what happened behind the scenes here, let)s try our shiny new application. Kirst, we)ll start a local 4>&ric5-based web server, supplied with Rails. depotN ruby scriptOserver .N Rails application started on http%OO2.2.2.2%7222 XA223-2A-2G 0A%2G%62Y I;KO 4>&ric5 0.7.0 XA223-2A-2G 0A%2G%62Y I;KO ruby 0.G.A /A226-0A-721 Xpowerpc-darwin@.@.2Y XA223-2A-2G 0A%2G%62Y I;KO 4>&ric5%%=((!ServerUstart% pid.A2AS0 port.7222 Fust as it did with our demo application in Chapter 6, Instant Jratification, this command starts a web server on our local host, port 7222. 3 #et)s connect to it. Remember, the IR# we ive to our browser contains both the port number /72221 and the name of the controller in lowercase /admin1. 7 Inless, perhaps, you)re runnin OS " 02.6. It seems as if (ier has bro5en Ruby)s standard MySB# library. If you see the error &efore updatin scaffoldin from new ?& schema, try creatin a table for your model /!roduct1, it may well be because Ruby /and hence Rails1 can)t et to the database. (o fi8 $pple)s bad install, you)re oin to need to reinstall Ruby)s MySB# library, which means oin bac5 to on pae A0, runnin the script to repair the Ruby installation, and then reinstallin the mys*l em. 6 Some readers also report ettin the error Client does not support authentication protocol re*uested by serverC consider upradin MySB# client. (his incompatibility between the ver- sion of MySB# installed and the libraries used to access it can be resolved by followin the instructions at http%OOdev.mys*l.comOdocOmys*lOenOold-client.html and issuin a MySB# command such as set password for )some,user)])some,host) . O#?,!$SS4OR?/)newpwd)1C . 3 Pou miht et an error sayin $ddress already in use when you try to run 4>&ric5. (hat simply means that you already have a Rails 4>&ric5 server runnin on your machine. If you)ve been followin alon with the e8amples in the boo5, that miht well be the =ello 4orldE application from Chapter 6. Kind its console, and 5ill the server usin control-C. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; $0% J >( S OM>(=I;J R I;;I;J 36 !ort% 7222 Controller% admin (hat)s pretty borin. It)s showin us a list of products, and there aren)t any products. #et)s remedy that. Clic5 the ;ew product lin5, and a form should appear. Kiure S.A, on the followin pae shows the form after it is filled in. Clic5 the Create button, and you should see the new product in the list /Kiure S.7, on the ne8t pae1. !erhaps it isn)t the prettiest interface, but it wor5s, and we can show it to our client for approval. (hey can play with the other lin5s /showin details, editin e8istin products, as shown in Kiure S.6, on pae 3S1. 4e e8plain to them that this is only a first stepDwe 5now it)s rouh, but we wanted to et their feedbac5 early. /$nd A3 minutes into the start of codin probably counts as early in anyone)s boo5.1 Rails Scaffolds 4e covered a lot of round in a very short initial implementation, so let)s ta5e a minute to loo5 at that last step in a bit more detail. $ Rails scaffold is an autoenerated framewor5 for manipulatin a model. 4hen we run the enerator, we tell it that we want a scaffold for a particu- lar model /which it creates1 and that we want to access it throuh a iven controller /which it also creates1. In Rails, a model is automatically mapped to a database table whose name name mappin W R pae 0G2 is the plural form of the model)s class. In our case, we as5ed for a model called !roduct , so Rails associated it with the table called products . $nd how did it find that tableW 4e told it where to loo5 when we set up the devel- opment entry in confiOdatabase.yml . 4hen we started the application, the model e8amined the table in the database, wor5ed out what columns it had, and created mappins between the database data and Ruby objects. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; $0% J >( S OM>(=I;J R I;;I;J 33 Kiure S.A% $ddin a ;ew !roduct Kiure S.7% 4e Fust $dded Our Kirst !roduct Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; $0% J >( S OM>(=I;J R I;;I;J 3S Kiure S.6% Showin ?etails and >ditin (hat)s why the ;ew products form came up already 5nowin about the title, description, imae, and price fieldsDbecause they are in the database table, they are added to the model. (he form enerator used by the scaf- fold can as5 the model for information on these fields and uses what it discovers to create an appropriate =(M# form. Controllers handle incomin re*uests from the browser. $ sinle applica- tion can have multiple controllers. Kor our ?epot application, it)s li5ely that we)ll end up with two of them, one handlin the seller)s administra- tion of the site and the other handlin the buyer)s e8perience. 4e created the product maintenance scaffoldin in the $dmin controller, which is why the IR# that accesses it has admin at the start of its path. (he utility that enerates a Rails scaffold populates your application)s directory tree with wor5in Ruby code. If you e8amine it, you)ll find that what you have is the bare bones of a full applicationDthe Ruby code has been placed inlineC it)s all in the source, rather than simply bein a sin- le call into some standard library. (his is ood news for us, because it Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; $A% $ ?? $ M ISSI;J C O#IM; 3@ ?avid Says... 4on)t 4e >nd Ip Replacin $ll the ScaffoldsW Most of the time, yes. Scaffoldin is not intended to be the sha5e )n) ba5e of application development. It)s there as support while you build out the application. $s you)re desinin how the list of products should wor5, you rely on the scaffold-enerated create, update, and delete actions. (hen you replace the enerated creation functionality while relyin on the remainin actions. $nd so on and so forth. Sometimes scaffoldin will be enouh, thouh. If you)re merely interested in ettin a *uic5 interface to a model online as part of a bac5end inter- face, you may not care that the loo5s are bare. &ut this is the e8ception. ?on)t e8pect scaffoldin to replace the need for you as a prorammer just yet /or ever1. means that we can modify the code produced in the scaffold. (he scaffold is the startin point of an application, not a finished application in its own riht. $nd we)re about to ma5e use of that fact as we move on to the ne8t iteration in our project. S.A Iteration $A% $dd a Missin Column So, we show our scaffold-based code to our customer, e8plainin that it)s still pretty rouh-and-ready. She)s delihted to see somethin wor5in so *uic5ly. Once she plays with it for a while, she notices that somethin was missed in our initial discussions. #oo5in at the product information displayed in a browser window, it becomes apparent that we need to add an availability date columnDthe product will be offered to customers only once that date has passed. (his means we)ll need to add a column to the database table, and we)ll need to ma5e sure that the various maintenance paes are updated to add support for this new column. Some developers /and ?&$s1 would add the column by firin up a utility proram and issuin the e*uivalent of the command alter table products add column date,available datetimeC Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; $A% $ ?? $ M ISSI;J C O#IM; 3G Instead, I tend to maintain the flat file containin the ??# I oriinally used to create the schema. (hat way I have a version-controlled history of the schema and a sinle file containin all the commands needed to re-create it. So let)s alter the file dbOcreate.s*l , addin the date,available column. Kile S6 drop table if e8ists productsC create table products / id int not null auto,increment, title varchar/0221 not null, description te8t not null, imae,url varchar/A221 not null, price decimal/02,A1 not null, date,available datetime not null, primary 5ey /id1 1C 4hen I first created this file, I added a drop table command at the top of it. (his now allows us to create a new /empty1 schema instance with the commands depotN mys*l depot,development -dbOcreate.s*l Obviously, this approach only wor5s if there isn)t important data already in the database table /as droppin the table wipes out the data it contains1. (hat)s fine durin development, but in production we)d need to step more carefully. Once an application is in production, I tend to produce version- controlled miration scripts to uprade my database schemas. >ven in development, this can be a pain, as we)d need to reload our test data. I normally dump out the database contents /usin mys*ldump 1 when I have a set of data I can use for development, then reload this database each time I blow away the schema. (he schema has chaned, so our scaffold code is now out-of-date. $s we)ve made no chanes to the code, it)s safe to reenerate it. ;otice that the enerate script prompts us when it)s about to overwrite a file. 4e type a to indicate that it can overwrite all files. depotN ruby scriptOenerate scaffold !roduct $dmin dependency model e8ists appOmodelsO e8ists testOunitO e8ists testOfi8turesO s5ip appOmodelsOproduct.rb s5ip testOunitOproduct,test.rb s5ip testOfi8turesOproducts.yml e8ists appOcontrollersO e8ists appOhelpersO e8ists appOviewsOadmin e8ists testOfunctionalO overwrite appOcontrollersOadmin,controller.rbW XPna*Y a forcin scaffold force appOcontrollersOadmin,controller.rb Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; $A% $ ?? $ M ISSI;J C O#IM; 3: Kiure S.3% ;ew !roduct !ae $fter $ddin ?ate Column force testOfunctionalOadmin,controller,test.rb force appOhelpersOadmin,helper.rb force appOviewsOlayoutsOadmin.rhtml force publicOstylesheetsOscaffold.css force appOviewsOadminOlist.rhtml force appOviewsOadminOshow.rhtml force appOviewsOadminOnew.rhtml force appOviewsOadminOedit.rhtml create appOviewsOadminO,form.rhtml Refresh the browser, and create a new product, and you)ll see somethin li5e Kiure S.3 . /If it doesn)t loo5 any different, perhaps the enerator is still waitin for you to type a .1 4e now have our date field /and with no e8plicit codin1. Imaine doin this with the client sittin ne8t to you. (hat)s rapid feedbac5E Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; $7% 9 $#I?$(> E S2 S.7 Iteration $7% 9alidateE 4hile playin with the results of iteration two, our client noticed some- thin. If she entered an invalid price, or forot to set up a product descrip- tion, the application happily accepted the form and added a line to the database. 4hile a missin description is embarrassin, a price of _2.22 actually costs her money, so she as5ed that we add validation to the appli- cation. ;o product should be allowed in the database if it has an empty te8t field, an invalid IR# for the imae, or an invalid price. So, where do we put the validationW (he model layer is the ate5eeper between the world of code and the database. ;othin to do with our application comes out of the database or ets stored bac5 into the database that doesn)t first o throuh the model. (his ma5es it an ideal place to put all validationC it doesn)t matter whether the data comes from a form or from some prorammatic manipulation in our application. If the model chec5s it before writin to the database, then the database will be protected from bad data. #et)s loo5 at the source code of the model class /in appOmodelsOproduct.rb 1. Kile S7 class !roduct - $ctiveRecord%%&ase end ;ot much to it, is thereW $ll of the heavy liftin /database mappin, creatin, updatin, searchin, and so on1 is done in the parent class / $ctiveRecord%%&ase , a part of Rails1. &ecause of the joys of inheritance, our !roduct class ets all of that functionality automatically. $ddin our validation should be fairly clean. #et)s start by validatin that the te8t fields all contain somethin before a row is written to the database. 4e do this by addin some code to the e8istin model. Kile S3 class !roduct - $ctiveRecord%%&ase validates,presence,of %title, %description, %imae,url end (he validates,presence,of /1 method is a standard Rails validator. It chec5s that a iven field, or set of fields, is present and its contents are not empty. Kiure S.S, on the followin pae, shows what happens if we try to submit a new product with none of the fields filled in. It)s pretty impressive% the fields with errors are hihlihted, and the errors are summari'ed in a nice list at the top of the form. ;ot bad for one line of code. Pou miht also have noticed that after editin the product.rb file you didn)t have to restart the application to test your chanesDin development mode, Rails notices Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; $7% 9 $#I?$(> E S0 Kiure S.S% 9alidatin (hat Kields $re !resent that the files have been chaned and reloads them into the application. (his is a tremendous productivity boost when developin. ;ow we)d li5e to validate that the price is a valid, positive number. 4e)ll attac5 this problem in two staes. Kirst, we)ll use the delihtfully named validates,numericality,of /1 method to verify that the price is a valid number. Kile S3 validates,numericality,of %price ;ow, if we add a product with an invalid price, the appropriate messae will appear. S S MySB# ives Rails enouh metadata to 5now that price contains a number, so Rails converts it to a floatin-point value. 4ith other databases, the value miht come bac5 as a strin, so you)d need to convert it usin Kloat/price1 before usin it in a comparison Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; $7% 9 $#I?$(> E SA ;e8t we need to chec5 that it is reater than 'ero. 4e do that by writin a method named validate /1 in our model class. Rails automatically calls this method before savin away instances of our product, so we can use it to chec5 the validity of fields. 4e ma5e it a protected method, because it protected W R pae 6@7 shouldn)t be called from outside the conte8t of the model. Kile S3 protected def validate errors.add/%price, Vshould be positiveV1 unless price.nilW [[ price N 2.2 end If the price is less than or e*ual to 'ero, the validation method uses errors.add/...1 to record the error. ?oin this prevents Rails from writin the row to the database. It also ives our forms a nice messae to display to the user. (he first parameter to errors.add /1 is the name of the field, and the second is the te8t of the messae. ;ote that we only do the chec5 if the price has been set. 4ithout that e8tra test we)ll compare nil aainst 2.2 , and that will raise an e8ception. (wo more thins to validate. Kirst, we want to ma5e sure that each product has a uni*ue title. One more line in the !roduct model will do this. (he uni*ueness validation will perform a simple chec5 to ensure that no other row in the products table has the same title as the row we)re about to save. Kile S3 validates,uni*ueness,of %title #astly, we need to validate that the IR# entered for the imae is valid. 4e)ll do this usin the validates,format,of /1 method, which matches a field aainst a reular e8pression. Kor now we)ll just chec5 that the IR# starts reular e8pression W R pae 6@S with http% and ends with one of . if , . jp , or . pn . @ Kile S3 validates,format,of %imae,url, %with .N Zr`ahttp%.TQ./if[jp[pn1_bi, %messae .N Vmust be a IR# for a JIK, F!J, or !;J imaeV @ #ater on, we)d probably want to chane this form to let the user select from a list of available imaes, but we)d still want to 5eep the validation to prevent malicious fol5s from submittin bad data directly. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; $6% ! R>((I>R # IS(I;JS S7 So, in a couple of minutes we)ve added validations that chec5 H (he field)s title, description, and imae IR# are not empty. H (he price is a valid number reater than 'ero. H (he title is uni*ue amon all products. H (he imae IR# loo5s reasonable. (his is the full listin of the updated !roduct model. Kile S3 class !roduct - $ctiveRecord%%&ase validates,presence,of %title, %description, %imae,url validates,numericality,of %price validates,uni*ueness,of %title validates,format,of %imae,url, %with .N Zr`ahttp%.TQ./if[jp[pn1_bi, %messae .N Vmust be a IR# for a JIK, F!J, or !;J imaeV protected def validate errors.add/%price, Vshould be positiveV1 unless price.nilW [[ price N 2.2 end end ;earin the end of this cycle, we as5 our customer to play with the appli- cation, and she)s a lot happier. It too5 only a few minutes, but the simple act of addin validation has made the product maintenance paes feel a lot more solid. S.6 Iteration $6% !rettier #istins Our customer has one last re*uest /customers always seem to have one last re*uest1. (he listin of all the products is uly. Can we Lpretty it upM a bitW $nd, while we)re in there, can we also display the product imae alon with the imae IR#W 4e)re faced with a dilemma here. $s developers, we)re trained to respond to these 5inds of re*uest with a sharp inta5e of breath, a 5nowin sha5e of the head, and a murmured Lyou want whatWM $t the same time, we also li5e to show off a bit. In the end, the fact that it)s fun to ma5e these 5inds of chanes usin Rails wins out, and we fire up our trusty editor. (he Rails view in the file appOviewsOadminOlist.rhtml produces the current list of products. (he source code, which was produced by the scaffold enerator, loo5s somethin li5e the followin. Kile SS -h0N#istin products-Oh0N -tableN -trN -Z for column in !roduct.content,columns ZN -thN-Z. column.human,name ZN-OthN Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; $6% ! R>((I>R # IS(I;JS S6 -Z end ZN -OtrN -Z for product in ]products ZN -trN -Z for column in !roduct.content,columns ZN -tdN-Z.h product.send/column.name1 ZN-OtdN -Z end ZN -tdN-Z. lin5,to + Show + , %action .N + show + , %id .N product ZN-OtdN -tdN-Z. lin5,to + >dit + , %action .N + edit + , %id .N product ZN-OtdN -tdN-Z. lin5,to + ?estroy + , `%action .N + destroy + , %id .N productb, %confirm .N V$re you sureWV ZN-OtdN -OtrN -Z end ZN -OtableN -Z. if ]product,paes.current.previous lin5,to V!revious paeV, ` %pae .N ]product,paes.current.previous b end ZN -Z. if ]product,paes.current.ne8t lin5,to V;e8t paeV, ` %pae .N ]product,paes.current.ne8t b end ZN -br ON -Z. lin5,to + ;ew product + , %action .N + new + ZN (he view uses >Rb to iterate over the columns in the !roduct model. It >Rb W R pae 70 creates a table row for each product in the ]products array. /(his array is set up by the list action method in the controller.1 (he row contains an entry for each column in the result set. (he dynamic nature of this code is neat, as it means that the display will automatically update to accommodate new columns. =owever, it also ma5es the display somewhat eneric. So, let)s ta5e this code and modify it to produce nicer-loo5in output. Kile S@ -h0N!roduct #istin-Oh0N -table cellpaddin.V3V cellspacin.V2VN -Z odd,or,even . 2 for product in ]products odd,or,even . 0 - odd,or,even ZN -tr valin.VtopV class.V#ist#ine-Z. odd,or,even ZNVN -tdN -im width.VS2V heiht.V@2V src.V-Z. product.imae,url ZNVON -OtdN -td width.VS2ZVN -span class.V#ist(itleVN-Z. h/product.title1 ZN-OspanN-br ON -Z. h/truncate/product.description, G211 ZN -OtdN -td alin.VrihtVN -Z. product.date,available.strftime/VZy-Zm-ZdV1 ZN-brON -stronN_-Z. sprintf/VZ2.AfV, product.price1 ZN-OstronN -OtdN -td class.V#ist$ctionsVN -Z. lin5,to + Show + , %action .N + show + , %id .N product ZN-brON -Z. lin5,to + >dit + , %action .N + edit + , %id .N product ZN-brON Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; $6% ! R>((I>R # IS(I;JS S3 -Z. lin5,to + ?estroy + , ` %action .N + destroy + , %id .N product b, %confirm .N V$re you sureWV ZN -OtdN -OtrN -Z end ZN -OtableN -Z. if ]product,paes.current.previous lin5,to/V!revious paeV, ` %pae .N ]product,paes.current.previous b1 end ZN -Z. if ]product,paes.current.ne8t lin5,to/V;e8t paeV, ` %pae .N ]product,paes.current.ne8t b1 end ZN -br ON -Z. lin5,to + ;ew product + , %action .N + new + ZN ;otice how we used the odd,or,even variable to tole the name of the CSS class applied to alternatin rows of the table. (his will result in alternatin pastel-shaded lines for each product. /If you)re readin this on paper, you)ll have to ta5e our word for it about the pastels.1 4e also used Ruby)s sprintf /1 method to convert the floatin-point price to a nicely formatted strin. $ll scaffold-enerated applications use the stylesheet scaffold.css in the directory publicOstylesheets . 4e added our own styles to this file. Kile SG .#ist(itle ` color% UA66C font-weiht% boldC font-si'e% larerC b .#ist$ctions ` font-si'e% 8-smallC te8t-alin% rihtC paddin-left% 0emC b .#ist#ine2 ` bac5round% Ue2fGfGC b .#ist#ine0 ` bac5round% UfGb2fGC b !ut some imaes in the publicOimaes directory and enter some product descriptions, and the resultin product listin miht loo5 somethin li5e Kiure S.@, on the ne8t pae. $ Rails scaffold provides real source code, files that we can modify and immediately see results. (his approach ives us the fle8ibility we need to develop in an aile way. 4e can customi'e a particular source file and leave the rest aloneDchanes are both possible and locali'ed. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; $6% ! R>((I>R # IS(I;JS SS Kiure S.@% (idied-up !roduct #istin So, we proudly show our customer her new product listin, and she)s pleased. >nd of tas5. (ime for lunch. 4hat 4e Fust ?id In this chapter we laid the roundwor5 for our store application. H 4e created three databases /development, test, and production1 and confiured our Rails application to access them. H 4e created the products table and used the scaffold enerator to write an application to maintain it. H 4e aumented that enerated code with validation. H 4e rewrote the eneric view code with somethin prettier. One thin that we didn)t do was discuss the paination of the product listin. (he scaffold enerator automatically made use of Rails) built-in paination helper. (his brea5s the lists of products into paes of 02 entries each and automatically handles naviation between paes. 4e discuss this in more depth startin on pae 762. Report erratum !repared e8clusively for Rida $l &ara'i Chapter @ (as5 &% Catalo ?isplay $ll in all, it)s been a successful day so far. 4e athered the initial re*uire- ments from our customer, documented a basic flow, wor5ed out a first pass at the data we)ll need, and put toether the maintenance pae for the ?epot application)s products. 4e even manaed to cap off the mornin with a decent lunch. (hus fortified, it)s on to our second tas5. 4e chatted throuh priorities with our customer, and she said she)d li5e to start seein what thins loo5 li5e from the buyer)s point of view. Our ne8t tas5 is to create a simple catalo display. (his also ma5es a lot of sense from our point of view. Once we have the products safely tuc5ed into the database, it should be fairly simple to display them. It also ives us a basis from which to develop the shoppin cart portion of the code later. 4e should also be able to draw on the wor5 we did in the product main- tenance tas5Dthe catalo display is really just a lorified product listin. So, let)s et started. @.0 Iteration &0% Create the Catalo #istin &ac5 on pae 3S, we said that we)d be usin two controller classes for this application. 4e)ve already created the $dmin controller, used by the seller to administer the ?epot application. ;ow it)s time to create the second controller, the one that interacts with the payin customers. #et)s call it Store . depotN ruby scriptOenerate controller Store inde8 !repared e8clusively for Rida $l &ara'i I (>R$(IO; &0% C R>$(> (=> C $($#OJ # IS(I;J SG In the previous chapter, we used the enerate utility to create a scaffold for the products table. (his time, we)ve as5ed it to create a new controller /called StoreController 1 containin a sinle action method, inde8 /1. So why did we choose to call our first method inde8 W &ecause, just li5e most web servers, if you invo5e a Rails controller and don)t specify an e8plicit action, Rails automatically invo5es the inde8 action. In fact, let)s try it. !oint a browser at http%OOlocalhost%7222Ostore and up pops our web pae. It miht not ma5e us rich, but at least we 5now thins are all wired toether correctly. (he pae even tells us where to find the proram file that draws this pae. #et)s start by displayin a simple list of all the salable products in our database. 4e 5now that eventually we)ll have to be more sophisticated, brea5in them into cateories, but this will et us oin. 4hat constitutes a salable productW Our customer told us that we only display ones with an available date on or before today. 4e need to et the list of products out of the database and ma5e it available to the code in the view that will display the table. (his means we have to chane the inde8 /1 method in store,controller.rb . 4e want to proram at a decent level of abstraction, so let)s just assume we can as5 the model for a list of the products we can sell. Kile S: def inde8 ]products . !roduct.salable,items end Obviously, this code won)t run as it stands. 4e need to define the method salable,items /1 in the product.rb model. (he code that follows uses the Rails find /1 method. (he %all parameter tells Rails that we want all rows that match the iven condition. /(he condition chec5s that the item)s availabil- ity date is not in the future. It uses the MySB# now /1 function to et the current date and time.1 4e as5ed our customer if she had a preference Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; &0% C R>$(> (=> C $($#OJ # IS(I;J S: reardin the order thins should be listed, and we jointly decided to see what happened if we displayed the newest products first, so the code does a descendin sort on date,available . def self.888 W R pae 6@0 Kile @2 U Return a list of products we can sell /which means they have to be U available1. Show the most recently available first. def self.salable,items find/%all, %conditions .N Vdate,available -. now/1V, %order .N Vdate,available descV1 end (he find /1 method returns an array containin a !roduct object for each row returned from the database. (he salable,items /1 method simply passes this array bac5 to the controller. ;ow we need to write our view template. Kor now we)ll display the prod- ucts in a simple table. (o do this, edit the file appOviewsOstoreOinde8.rhtml . /Remember that the path name to the view is built from the name of the controller / store 1 and the name of the action / inde8 1. (he . rhtml part sinifies an >Rb template.1 Kile @0 -table cellpaddin.V3V cellspacin.V2VN -Z for product in ]products ZN -tr valin.VtopVN -tdN -im src.V-Z. product.imae,url ZNVON -OtdN -td width.V632VN -h7N-Z.h product.title ZN-Oh7N -smallN -Z. product.description ZN -OsmallN -brON -stronN_-Z. sprintf/VZ2.AfV, product.price1 ZN-OstronN -Z. lin5,to + $dd to Cart + , %action .N + add,to,cart + , %id .N product ZN -brON -OtdN -OtrN -trN-td colspan.VAVN-hrON-OtdN-OtrN -Z end ZN -OtableN =ittin Refresh brins up the display in Kiure @.0, on the followin pae. 4e call the customer over, and she)s pretty pleased. $fter all, we have the ma5ins of a catalo and it)s ta5en only a few minutes. &ut before we et too full of ourselves, she points out that she)d really li5e a proper-loo5in web pae here. She needs at least a title at the top and a sidebar with lin5s and news. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; &A% $ ?? ! $J> ? >COR$(IO;S @2 Kiure @.0% Our Kirst Catalo !ae $t this point in the real world we)d probably want to call in the desin fol5sDwe)ve all seen too many prorammer-desined web sites to feel com- fortable inflictin another on the world. &ut the !ramatic 4eb ?esiner is off ettin inspiration somewhere and won)t be bac5 until later in the year, so let)s put a placeholder in for now. It)s time for an iteration. @.A Iteration &A% $dd !ae ?ecorations (he paes in a particular web site typically share a similar layoutDthe desiner will have created a standard template that is used when placin content. Our job is to add this pae decoration to each of the store paes. Kortunately, in Rails we can define layouts. $ layout is a template into layout which we can flow additional content. In our case, we can define a sinle layout for all the store paes and insert the catalo pae into that layout. #ater we can do the same with the shoppin cart and chec5out paes. &ecause there)s only one layout, we can chane the loo5 and feel of this entire section of our site by editin just one thin. (his ma5es us feel better about puttin a placeholder in for nowC we can update it when the desiner eventually returns from the mountaintop. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; &A% $ ?? ! $J> ? >COR$(IO;S @0 (here are many ways of specifyin and usin layouts in Rails. 4e)ll choose the simplest for now. If you create a template file in the appOviewsOlayouts directory with the same name as a controller, all views rendered by that controller will use that layout by default. So let)s create one now. Our controller is called store , so we)ll name the layout store.rhtml . Kile @A #ine 0 -htmlN - -headN - -titleN!rapro &oo5s Online Store-OtitleN - -Z. stylesheet,lin5,ta VdepotV, %media .N VallV ZN 3 -OheadN - -bodyN - -div id.VbannerVN - -im src.VOimaesOloo.pnVON [[ W R pae 6@: - -Z. ]pae,title [[ V!ramatic &oo5shelfV ZN 02 -OdivN - -div id.VcolumnsVN - -div id.VsideVN - -a href.Vhttp%OOwww....VN=ome-OaN-br ON - -a href.Vhttp%OOwww....Ofa*VNBuestions-OaN-br ON 03 -a href.Vhttp%OOwww....OnewsVN;ews-OaN-br ON - -a href.Vhttp%OOwww....OcontactVNContact-OaN-br ON - -OdivN - -div id.VmainVN - -Z. ]content,for,layout ZN A2 -OdivN - -OdivN - -ObodyN - -OhtmlN $part from the usual =(M# ubbins, this layout has three Rails-specific items. #ine 6 uses a Rails helper method to enerate a - lin5 N ta to our depot.css stylesheet. On line : we set the pae title to a value in the variable ]pae,title . (he real maic, however, ta5es place on line 0:. Rails automatically sets the variable ]content,for,layout to the pae-specific contentDthe stuff enerated by the view invo5ed by this re*uest. In our case, this will be the catalo pae enerated by inde8.rhtml . 4e)ll also ta5e this opportunity to tidy up the inde8.rhtml view in appOviews . Kile @7 -Z for product in ]products ZN -div class.VcataloentryVN -im src.V-Z. product.imae,url ZNVON -h7N-Z. h/product.title1 ZN-Oh7N -Z. product.description ZN -span class.VcatalopriceVN-Z. sprintf/V_Z2.AfV, product.price1 ZN-OspanN -Z. lin5,to + $dd to Cart + , `%action .N + add,to,cart + , %id .N product b, %class .N + addtocart + ZN-brON -OdivN -div class.VseparatorVN\nbspC-OdivN -Z end ZN -Z. lin5,to VShow my cartV, %action .N Vdisplay,cartV ZN ;otice how we)ve switched to usin - div N tas and added CSS class names to tas to assist with layin out the pae. (o ive the $dd to Cart lin5 a Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; &A% $ ?? ! $J> ? >COR$(IO;S @A Kiure @.A% Catalo with #ayout $dded class, we had to use the optional third parameter of the lin5,to /1 method, which lets us specify =(M# attributes for the enerated ta. (o ma5e this all wor5, we need to hac5 toether a *uic5 stylesheet /or, more li5ely, rab an e8istin stylesheet and bend it to fit1. (he file depot.css oes into the directory publicOstylesheets . /#istins of the stylesheets start on pae 32G.1 =it Refresh, and the browser window loo5s somethin li5e Kiure @.A . It won)t win any desin awards, but it)ll show our customer rouhly what the final pae will loo5 li5e. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; &A% $ ?? ! $J> ? >COR$(IO;S @7 4hat 4e Fust ?id 4e)ve put toether the basis of the store)s catalo display. (he steps were as follows. H Create a new controller to handle user-centric interactions. H Implement the default inde8 /1 action. H $dd a class method to the !roduct model to return salable items. H Implement a viewer /an . rhtml file1 and a layout to contain it /another . rhtml file1. H Create a simple stylesheet. (ime to chec5 it all in and move on to the ne8t tas5. Report erratum !repared e8clusively for Rida $l &ara'i Chapter G (as5 C% Cart Creation ;ow that we have the ability to display a catalo containin all our won- derful products, it would be nice to be able to sell them. Our customer arees, so we)ve jointly decided to implement the shoppin cart function- ality ne8t. (his is oin to involve a number of new concepts, includin sessions and parent-child relationships between database tables, so let)s et started. G.0 Sessions &efore we launch into our ne8t wildly successful iteration, we need to spend just a little while loo5in at sessions, web applications, and Rails. $s a user browses our online catalo, she)ll /we hope1 select products to buy. (he convention is that each item selected will be added to a virtual shoppin cart, held in our store. $t some point, our buyer will have every- thin she needs, and she)ll proceed to our site)s chec5out, where she)ll pay for the stuff in the cart. (his means that our application will need to 5eep trac5 of all the items added to the cart by the buyer. (his sounds simple, e8cept for one minor detail. (he protocol used to tal5 between browsers and application pro- rams is statelessDthere)s no memory built-in to it. >ach time your appli- cation receives a re*uest from the browser is li5e the first time they)ve tal5ed to each other. (hat)s cool for romantics but not so ood when you)re tryin to remember what products your user has already selected. (he most popular solution to this problem is to fa5e out the idea of state- ful transactions on top of =((!, which is stateless. $ layer within the application tries to match up an incomin re*uest to a locally held piece !repared e8clusively for Rida $l &ara'i S >SSIO;S @3 of session data. If a particular piece of session data can be matched to all the re*uests that come from a particular browser, we can 5eep trac5 of all the stuff done by the user of that browser usin that session data. (he underlyin mechanisms for doin this session trac5in are varied. Sometimes an application encodes the session information in the form data on each pae. Sometimes the encoded session identifier is added to the end of each IR# /the so-called IR# Rewritin option1. $nd sometimes the application uses coo5ies. Rails uses the coo5ie-based approach. $ coo5ie is simply a chun5 of named data that a web application passes coo5ie to a web browser. (he browser stores the coo5ie locally on the user)s com- puter. Subse*uently, when the browser sends a re*uest to the application, the coo5ie data tas alon. (he application uses information in the coo5ie to match the re*uest with session information stored in the server. It)s an uly solution to a messy problem. Kortunately, as a Rails prorammer you don)t have to worry about all these low-level details. /In fact, the only reason to o into them at all is to e8plain why users of Rails applications must have coo5ies enabled in their browsers.1 Rather than have developers worry about protocols and coo5ies, Rails pro- vides a simple abstraction. 4ithin the controller, Rails maintains a special hash-li5e collection called session . $ny 5eyOvalue pairs you store into this hash W R pae 6@6 hash durin the processin of a re*uest will be available durin subse- *uent re*uests from the same browser. In the ?epot application we want to use the session facility to store the information about what)s in each buyer)s cart. &ut we have to be slihtly careful hereDthe issue is deeper than it miht appear. (here are problems of resilience and scalability. &y default, Rails stores session information in a file on the server. If you have a sinle Rails server runnin, there)s no problem with this. &ut ima- ine that your store application ets so wildly popular that you run out of capacity on a sinle server machine and need to run multiple bo8es. (he first re*uest from a particular user miht be routed to one bac5end machine, but the second re*uest miht o to another. (he session data stored on the first server isn)t available on the secondC the user will et very confused as items appear and disappear in their cart across re*uests. So, it)s a ood idea to ma5e sure that session information is stored some- where e8ternal to the application where it can be shared between multiple application processes if needed. $nd if this e8ternal store is persistent, we can even bounce a server and not lose any session information. 4e tal5 Report erratum !repared e8clusively for Rida $l &ara'i M OR> ( $&#>S , M OR> M O?>#S @S all about settin up session information in Chapter AA, ?eployment and Scalin, on pae 662. Kor now, let)s assume that Rails handles all this. So, havin just plowed throuh all that theory, where does that leave us in practiceW 4e need to be able to assin a new cart object to a session the first time it)s needed and find that cart object aain every time it)s needed in the same session. 4e can achieve that by creatin a helper method, find,cart /1, in the store controller. (he implementation is as follows. [[. W R pae 6@: Kile @3 private def find,cart sessionX%cartY [[. Cart.new end (his method is fairly tric5y. It uses Ruby)s conditional assinment opera- tor, [[. . If the session hash has a value correspondin to the 5ey %cart , that value is returned immediately. Otherwise a new cart object is created and assined to the session. (his new cart is then returned. 4e ma5e the find,cart /1 method private. (his prevents Rails from ma5in it available as an action on the controller. G.A More (ables, More Models So, we 5now we need to create some 5ind of cart that holds the thins that our customers have selected for purchase, and we 5now that we)ll 5eep that cart around between re*uests by associatin it with a session. (he cart will hold line items, where a line item is basically a combination of a product and a *uantity. If, for e8ample, the end user buys a unit testin boo5, the cart will hold a line item with a *uantity of 0 referencin the unit testin product in the database. 4e chatted with our customer, who reminded us that if a product)s price chanes, e8istin orders should honor the old price, so we)ll also 5eep a unit price field in the line item. &ased on what we 5now, we can o ahead and create our line,items table by addin a new table definition to our create.s*l script. ;otice the forein 5ey reference that lin5s the line item to the appropriate product. Kile G2 drop table if e8ists line,itemsC create table line,items / id int not null auto,increment, product,id int not null, *uantity int not null default 2, unit,price decimal/02,A1 not null, constraint f5,items,product forein 5ey /product,id1 references products/id1, primary 5ey /id1 1C Report erratum !repared e8clusively for Rida $l &ara'i M OR> ( $&#>S , M OR> M O?>#S @@ If you)re followin alon at home, remember to load this new definition into your MySB# schema. depotN mys*l depot,development -dbOcreate.s*l 4e need to remember to create a Rails model for this new table. ;ote how the name mappin wor5s here% a class name of #ineItem will be mapped to the underlyin table line,items . Class names are mi8ed case /each word starts with a capital letter, and there are no brea5s1. (able names /and, as we)ll see later, variable names and symbols1 are lowercase, with an under- score between words. /$lthouh that)s all we have to say about namin for now, you miht want to loo5 at Section 06.0, (ables and Classes, on pae 0:0, for more information on how class and table names are related.1 depotN ruby scriptOenerate model #ineItem Kinally, we have to tell Rails about the reference between the line items and the product tables. Pou miht thin5 that Rails could po5e around in the database schema definition to discover these relationships, but not all database enines support forein 5eys. 0 Instead, we have to e8plicitly tell Rails what relationships e8ist between tables. In this particular case, we represent the relationship between a line item and a product by tellin Rails that the line item belons,to /1 a product. 4e specify that directly in the line item model class, defined in appOmodelsOline,item.rb . Kile @@ class #ineItem - $ctiveRecord%%&ase belons,to %product end Rails uses a namin convention that lets it ma5e assumptions about how the forein 5eys wor5 in the underlyin database. #et)s loo5 at the loical model, schema, and resultin Rails classes shown in Kiure G.0, on the ne8t pae. If the model called Child belons to the model !arent, Rails assumes that the table children has a column parent,id referencin the col- umn id in the parents table. A In our line item model, the belons to rela- tionship means that a line item has a column product,id that references the id column in the products table. $s with most thins in Rails, if the assumptions it ma5es about column names don)t wor5 for your particular schema, you can always override it. 0 Kor e8ample, the popular MySB# database does not implement forein 5eys internally unless you specify a particular underlyin implementation for the correspondin tables. A Pes, Rails is smart enouh to 5now that a model called Child should map to a table named children . Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; C0% C R>$(I;J $ C $R( @G !arent $ttributes . . . Child $ttributes . . . class !arent - . . . . . . end class Child - . . . belons,to %parent end create table parents / id int primary 5ey, . . . 1C create table children / id int primary 5ey, parent,id int references parents/id1, . . . 1C 0 #oical Model ?atabase Schema Rails Classes Kiure G.0% Model, Schema, and Classes for belons,to (hen there)s the cart itself. 4e)ll need a class for it, but do we also need a cart database tableW ;ot necessarily. (he cart is tied to the buyer)s session, and as lon as that session data is available across all our servers /when we finally deploy in a multiserver environment1, that)s probably ood enouh. So for now we)ll assume the cart is a reular class and see what happens. Inside the cart we)ll hold line items /which correspond to rows in the line,items table1, but we won)t save those line items into the database until we create the order at chec5out. G.7 Iteration C0% Creatin a Cart Observant readers /yes, that)s all of you1 will have noticed that our catalo listin view already includes an $dd to Cart lin5 in each product listin. Kile @: -Z. lin5,to + $dd to Cart + , `%action .N + add,to,cart + , %id .N product b, %class .N + addtocart + ZN-brON (his lin5 points bac5 to an add,to,cart / 1 action in the store controller and Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; C0% C R>$(I;J $ C $R( @: will pass in the product id as a form parameter. 7 =ere)s where we start to see how important the id field is in our models. Rails identifies model objects /and the correspondin database rows1 by their id fields. If we pass an id to add,to,cart / 1, we)re uni*uely identifyin the product to add. #et)s implement the add,to,cart /1 method now. It needs to find the shop- pin cart for the current session /creatin one if there isn)t one there already1, add the selected product to that cart, and display the cart con- tents. So, rather than worry too much about the details, let)s just write the code at this level of abstraction. 4e)ll create an add,to,cart / 1 method in appOcontrollersOstore,controller.rb . It uses the params object to et the id parameter from the re*uest and then finds the correspondin product, and it uses the find,cart /1 method we created earlier to find the cart in the ses- sion and add the product to it. &e careful when you add the add,to,cart / 1 method to the controller. &ecause it is called as an action, it must be pub- lic and so must be added above the private directive we put in to hide the find,cart /1 method. Kile @3 def add,to,cart product . !roduct.find/paramsX%idY1 ]cart . find,cart ]cart.add,product/product1 redirect,to/%action .N + display,cart + 1 end Clearly, this code isn)t oin to run yet% we haven)t yet created a Cart class, and we don)t have any implementation of the display,cart /1 functionality. #et)s start off with the Cart class and its add,product /1 method. $s it stores application data, it is loically part of our model, so we)ll create the file cart.rb in the directory appOmodels . =owever, it isn)t tied to a database table, so it)s not a subclass of $ctiveRecord%%&ase . Kile @S class Cart attr,reader %items attr,reader %total,price def initiali'e ]items . XY ]total,price . 2.2 end def add,product/product1 -- W R pae 6@6 ]items -- #ineItem.for,product/product1 ]total,price T. product.price end end 7 Sayin %id.Nproduct is idiomatic shorthand for %id.Nproduct.id . &oth pass the product)s id bac5 to the controller. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; C0% C R>$(I;J $ C $R( G2 (hat was pretty straihtforward. 4e create a new line item based on the product and add it to the list. Of course, we haven)t yet ot a method that creates a line item based on information in a product, so let)s rectify that now. 4e)ll open up appOmodelsOline,item.rb and add a class method for,product /1. Creatin these class-level helper methods is a neat tric5 for 5eepin your code readable. Kile @@ class #ineItem - $ctiveRecord%%&ase belons,to %product def self.for,product/product1 self.new W R pae 6@: item . self.new item.*uantity . 0 item.product . product item.unit,price . product.price item end end So, let)s see where we are. 4e created a Cart class to hold our line items, and we implemented the add,to,cart / 1 method in the controller. (hat in turn calls the new find,cart /1 method, which ma5es sure that we 5eep the cart object in the session. 4e still need to implement the display,cart /1 method and the correspondin view. $t the same time, we)ve written a whole lot of code without tryin it out, so let)s just slap in a couple of stubs and see what happens. In the store controller, we)ll implement an action method to handle the incomin re*uest. Kile @3 def display,cart ]cart . find,cart ]items . ]cart.items end Over in the appOviewsOstore directory we)ll create a stub for the correspond- in view, display,cart.rhtml . Kile @G -h0N?isplay Cart-Oh0N -pN Pour cart contains -Z. ]items.si'e ZN items. -OpN So, with everythin plumbed toether, let)s have a loo5 at our store in a browser. ;aviatin to http%OOlocalhost%7222Ostore brins up our catalo pae. Clic5 on the $dd to Cart lin5s for one of the products. 6 4e e8pect to see the stub cart display, but instead we are faced with a somewhat brutal 6 If you don)t see a list of products, you)ll need to o bac5 to the administration section of the application and add some. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; C0% C R>$(I;J $ C $R( G0 pae. /$lthouh not in Rails 2.07.0 or later, where the steps on this pae are no loner necessary.1 $t first, we miht be tempted to thin5 that we)d mispelled the name of the action method or the name of the view, but that)s not the case. (his isn)t a Rails error messaeDit comes straiht from 4>&ric5. (o find out what)s oin on, we need to loo5 at the 4>&ric5 console output. Kind the window where 4>&ric5 is runnin, and you)ll see it is full of loin and trace messaes. (he trace indicates that somethin has one wron in the application. /(echnically, this is a stac5 bac5trace, which shows the chain of method calls that ot us to the point where the application cho5ed.1 (he easy way to find out what went wron is to scroll bac5 throuh the trace. Fust before it starts, you)ll see an error messae. U-$ctionController%%SessionRestore>rror% Session contained objects where the class definition wasn + t available. Remember to re*uire classes for all objects 5ept in the session. (he session has been deleted.N 4hen Rails tried to load the session information from the coo5ie that came from the browser, it came across some classes that it didn)t 5now about. 4e)ll have to tell Rails about our Cart and #ineItem classes. /(he sidebar on pae G7 e8plains why.1 Over in appOcontrollers you)ll find a file called appli- cation.rb . (his file is used to establish a conte8t for the entire application. &y default, it contains an empty definition of class $pplicationController . 4e)ll need to add two lines to this class to declare our new model files. Kile @6 class $pplicationController - $ctionController%%&ase model %cart model %line,item end ;ow if we hit Refresh in our browser, we should see our stub view displayed /see Kiure G.A, on the ne8t pae1. If we use the &ac5 button to return to the catalo display and add another product to the cart, you)ll see the Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; C0% C R>$(I;J $ C $R( GA Kiure G.A% Our Cart ;ow =as an Item in It count update when the display cart pae is displayed. It loo5s li5e we have sessions wor5in. !hewE (hat was probably the hardest thin we)ve done so far. It was definitely the lonest time we)ve spent without havin anythin to show our customer /or ourselves1. &ut now that we have everythin lin5ed toether correctly, let)s *uic5ly implement a simple cart display so we can et some customer feedbac5. 4e)ll replace the stub code in display,cart.rhtml with code that displays the items in the cart. Kile GA -h0N?isplay Cart-Oh0N -tableN -Z for item in ]items product . item.product -ZN -trN -tdN-Z. item.*uantity ZN-OtdN -tdN-Z. h/product.title1 ZN-OtdN -td alin.VrihtVN-Z. item.unit,price ZN-OtdN -td alin.VrihtVN-Z. item.unit,price ^ item.*uantity ZN-OtdN -OtrN -Z end -ZN -OtableN (his template illustrates one more feature of >Rb. If we end a piece of embedded Ruby with -ZN /note the e8tra minus sin1, >Rb will suppress the newline that follows. (hat means embedded Ruby that doesn)t enerate any output won)t end up leavin e8traneous blan5 lines in the output. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; C0% C R>$(I;J $ C $R( G7 Session Information, Seriali'ation, and Classes (he session construct stores the objects that you want to 5eep around between browser re*uests. (o ma5e this wor5, Rails has to ta5e these objects and store them at the end of one re*uest, loadin them bac5 in when a subse*uent re*uest comes in from the same browser. (o store objects outside the runnin application, Rails uses Ruby)s seriali'ation mechanism, which converts objects to data that can be loaded bac5 in. =owever, that loadin wor5s only if Ruby 5nows about the classes of the objects in the seriali'ed file. 4henwe store a cart in a session, we)restorin an object of class Cart . &ut when it comes time to load that data bac5 in, there)s no uarantee that Rails will have loaded up the Cart model at that point /because Rails loads thins only when it thin5s it needs them1. Isin the model declaration forces Rails to load the user model class early, so Ruby 5nows what to do with it when it loads the seriali'ed session. =it Refresh in our browser, and /assumin we pic5ed a product from the catalo1 we)ll see it displayed. 0 !ramatic !roject $utomation A:.:3 A:.:3 =it the &ac5 button /we)ll wor5 on naviation shortly1, and add another. 0 !ramatic !roject $utomation A:.:3 A:.:3 0 !ramatic 9ersion Control A:.:3 A:.:3 #oo5in ood. Jo bac5 and add a second copy of the oriinal product. 0 !ramatic !roject $utomation A:.:3 A:.:3 0 !ramatic 9ersion Control A:.:3 A:.:3 0 !ramatic !roject $utomation A:.:3 A:.:3 (hat)s not so ood. $lthouh the cart is loically correct, it)s not what our users would e8pect. Instead, we should probably have mered both of those automation boo5s into a sinle line item with a *uantity of A. Kortunately, this is a fairly straihtforward chane to the add,product /1 method in the Cart model. 4hen addin a new product, we)ll loo5 to see if that product is already in the cart. If so, we)ll just increment its *uantity rather than addin a new line item. Remember that the cart is not a database objectDthis is just straiht Ruby code. Kile G0 def add,product/product1 item . ]items.find `[i[ i.product,id .. product.idb if item item.*uantity T. 0 Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; C0% C R>$(I;J $ C $R( G6 else item . #ineItem.for,product/product1 ]items -- item end ]total,price T. product.price end (he problem we now face is that we already have a session with duplicate products in the cart, and this session is associated with a coo5ie stored in our browser. It won)t o away unless we delete that coo5ie. 3 Kortunately, there)s an alternative to punchin buttons in a browser window when we want to try out our code. (he Rails way is to write tests. &ut that)s such a bi topic, we)re puttin it in its own chapter, Chapter 0A, (as5 (% (estin, on pae 07A. (idyin Ip the Cart &efore we declare this iteration complete and we show our customer our application so far, let)s tidy up the cart display pae. Rather than simply dumpin out the products, let)s add some formattin. $t the same time, we can add the much-needed Continue shoppin lin5 so we don)t have to 5eep hittin the &ac5 button. 4hile we)re addin lin5s, let)s anticipate a little and add lin5s to empty the cart and to chec5 out. Our new display,cart.rhtml file loo5s li5e this. Kile A3 -div id.VcartmenuVN -ulN -liN-Z. lin5,to + Continue shoppin + , %action .N Vinde8V ZN-OliN -liN-Z. lin5,to + >mpty cart + , %action .N Vempty,cartV ZN-OliN -liN-Z. lin5,to + Chec5out + , %action .N Vchec5outV ZN-OliN -OulN -OdivN -table cellpaddin.V02V cellspacin.V2VN -tr class.VcarttitleVN -td rowspan.VAVNBty-OtdN -td rowspan.VAVN?escription-OtdN -td colspan.VAVN!rice-OtdN -OtrN -tr class.VcarttitleVN -tdN>ach-OtdN -tdN(otal-OtdN -OtrN -Z for item in ]items product . item.product -ZN -trN -tdN-Z. item.*uantity ZN-OtdN 3 4hich you can do if you want. Pou can delete the coo5ie file /on a Ini8 bo8 it will be in a file whose name starts ruby,sess in your Otmp directory. $lternatively, loo5 in your browser for a coo5ie from localhost called ,session,id , and delete it. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; C0% C R>$(I;J $ C $R( G3 Kiure G.7% On Our 4ay to a !resentable Cart -tdN-Z. h/product.title1 ZN-OtdN -td alin.VrihtVN-Z. item.unit,price ZN-OtdN -td alin.VrihtVN-Z. item.unit,price ^ item.*uantity ZN-OtdN -OtrN -Z end ZN -trN -td colspan.V7V alin.VrihtVN-stronN(otal%-OstronN-OtdN -td id.VtotalcellVN-Z. ]cart.total,price ZN-OtdN -OtrN -OtableN $fter a bit of CSS maic, our cart loo5s li5e Kiure G.7 . =appy that we have somethin presentable, we call our customer over and show her the result of our mornin)s wor5. She)s pleasedDshe can see the site startin to come toether. =owever, she)s also troubled, havin just read an article in the trade press on the way e-commerce sites are bein attac5ed and compromised daily. She read that one 5ind of attac5 involves feedin re*uests with bad parameters into web applications, hopin to e8pose bus and security flaws. She noticed that the lin5 to add an item to our cart loo5s li5e storeOadd,to,cartO nnn, where nnn is our internal product id. Keelin malicious, she manually types this re*uest into a browser, ivin it a product id of Vwibble.V She)s not impressed when our application displays the pae in Kiure G.6, on the followin pae. So it loo5s as if our ne8t iteration will be spent ma5in the application more resilient. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; CA% = $;?#I;J > RRORS GS Kiure G.6% Our $pplication Spills Its Juts G.6 Iteration CA% =andlin >rrors #oo5in at the pae displayed in Kiure G.6, it)s apparent that our appli- cation threw an e8ception at line G of the store controller. (hat turns out to be the line product . !roduct.find/paramsX%idY1 If the product cannot be found, $ctive Record throws a Record;otKound e8ception, which we clearly need to handle. (he *uestion arisesDhowW 4e could just silently inore it. Krom a security standpoint, this is proba- bly the best move, as it ives no information to a potential attac5er. =ow- ever, it also means that should we ever have a bu in our code that en- erates bad product ids, our application will appear to the outside world to be unresponsiveDno one will 5now there)s been an error. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; CA% = $;?#I;J > RRORS G@ Instead, we)ll ta5e three actions when an e8ception is thrown. Kirst, we)ll lo the fact to an internal lo file usin Rails) loer facility /described on pae 0GS1. Second, we)ll output a short messae to the user /somethin alon the lines of LInvalid productM1. $nd third, we)ll redisplay the catalo pae so they can continue to use our site. (he KlashE $s you may have uessed, Rails has a convenient way of dealin with errors and error reportin. It defines a structure called a flash. $ flash is a buc5et /actually closer to a =ash 1, into which you can store stuff as you pro- cess a re*uest. (he contents of the flash are available to the ne8t re*uest in this session before bein deleted automatically. (ypically the flash is used to collect error messaes. Kor e8ample, when our add,to,cart /1 action detects that it was passed an invalid product id, it can store that error messae in the flash area and redirect to the inde8 /1 action to redisplay the catalo. (he view for the inde8 action can e8tract the error and display it at the top of the catalo pae. (he flash information is accessible within the views by usin the ]flash instance variable. 4hy couldn)t we just store the error in any old instance variableW Remem- ber that a redirect is sent by our application to the browser, which then sends a new re*uest bac5 to our application. &y the time we receive that re*uest, our application has moved onDall the instance variables from previous re*uests are lon one. (he flash data is stored in the session in order to ma5e it available between re*uests. $rmed with all this bac5round about flash data, we can now chane our add,to,cart / 1 method to intercept bad product ids and report on the problem. Kile A7 def add,to,cart product . !roduct.find/paramsX%idY1 ]cart . find,cart ]cart.add,product/product1 redirect,to/%action .N + display,cart + 1 rescue loer.error/V$ttempt to access invalid product U`paramsX%idYbV1 flashX%noticeY . + Invalid product + redirect,to/%action .N + inde8 + 1 end (he rescue clause intercepts the e8ception thrown by !roduct.find /1. In the handler we use the Rails loer to record the error, create a flash notice with an e8planation, and redirect bac5 to the catalo display. /4hy redi- rect, rather than just display the catalo hereW If we redirect, the user)s Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; CA% = $;?#I;J > RRORS GG browser will end up displayin a IR# of http%OO...OstoreOinde8 , rather than http%OO...OstoreOadd,to,cartOwibble . 4e e8pose less of the application this way. 4e also prevent the user from retrierin the error by hittin the Reload button.1 4ith this code in place, we can rerun our customer)s problematic *uery. (his time, when we e8plicitly enter http%OOlocalhost%7222OstoreOadd,to,cartOwibble we don)t see a bunch of errors in the browser. Instead, the catalo pae is displayed. If we loo5 at the end of the lo file / development.lo in the lo directory1, we)ll see our messae. S !arameters% `VactionV.NVadd,to,cartV, VidV.NVwibbleV, VcontrollerV.NVstoreVb !roduct #oad /2.22267:1 S>#>C( ^ KROM products 4=>R> id . + wibble + #IMI( 0 $ttempt to access invalid product wibble Redirected to http%OOlocalhost%7222OstoreO % % % Renderin storeOinde8 within layoutsOstore Renderin layoutsOstore /A22 O<1 Completed in 2.22S6A2 /033 re*sOsec1 [ Renderin% 2.227@A2 /3@Z1 [ ?&% 2.220S32 /A3Z1 So, the loin wor5ed. &ut the flash messae didn)t appear on the user)s browser. (hat)s because we didn)t display it. 4e)ll need to add somethin to the layout to tell it to display flash messaes if they e8ist. (he followin rhtml code chec5s for a notice-level flash messae and creates a new - div N containin it if necessary. Kile A6 -Z if ]flashX%noticeY -ZN -div id.VnoticeVN-Z. ]flashX%noticeY ZN-OdivN -Z end -ZN So, where do we put this codeW 4e could put it at the top of the catalo display templateDthe code in inde8.rhtml . $fter all, that)s where we)d li5e it to appear riht now. &ut as we continue to develop the application, it would be nice if all paes had a standardi'ed way of displayin errors. 4e)re already usin a Rails layout to ive all the store paes a consistent loo5, so let)s add the flash-handlin code into that layout. (hat way if our customer suddenly decides that errors would loo5 better in the sidebar, we can ma5e just one chane and all our store paes will be updated. So, our new store layout code now loo5s as follows. S On Ini8 machines, we)d probably use a command such as tail or less to view this file. On 4indows, you could use your favorite editor. It)s often a ood idea to 5eep a window open showin new lines as they are added to this file. In Ini8 you)d use tail -f . Pou can download a tail command for 4indows from http%OOun8utils.sourcefore.netO or et a JII-based tool from http%OOtailforwin7A.sourcefore.netO . Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; CA% = $;?#I;J > RRORS G: Kile A6 -htmlN -headN -titleN!rapro &oo5s Online Store-OtitleN -Z. stylesheet,lin5,ta VdepotV, %media .N VallV ZN -OheadN -bodyN -div id.VbannerVN -im src.VOimaesOloo.pnVON -Z. ]pae,title [[ V!ramatic &oo5shelfV ZN -OdivN -div id.VcolumnsVN -div id.VsideVN -a href.Vhttp%OOwww....VN=ome-OaN-br ON -a href.Vhttp%OOwww....Ofa*VNBuestions-OaN-br ON -a href.Vhttp%OOwww....OnewsVN;ews-OaN-br ON -a href.Vhttp%OOwww....OcontactVNContact-OaN-br ON -OdivN -div id.VmainVN -Z if ]flashX%noticeY -ZN -div id.VnoticeVN-Z. ]flashX%noticeY ZN-OdivN -Z end -ZN -Z. ]content,for,layout ZN -OdivN -OdivN -ObodyN -OhtmlN (his time, when we manually enter the invalid product code, we see the error reported at the top of the catalo pae. 4hile we)re loo5in for ways in which a malicious user could confuse our application, we notice that the display,cart /1 action could be called directly from a browser when the cart is empty. (his isn)t a bi problemDit would just display an empty list and a 'ero total, but we could do better than that. 4e can use our flash facility followed by a redirect to display a nice notice at the top of the catalo pae if the user tries to display an empty cart. 4e)ll modify the display,cart /1 method in the store controller. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; C7% K I;IS=I;J (=> C $R( :2 ?avid Says... =ow Much Inline >rror =andlin Is ;eededW (he add,to,cart /1 method shows the delu8e version of error handlin in Rails where the particular error is iven e8clusive attention and code. ;ot every conceivable error is worth spendin that much time catchin. #ots of input errors that will cause the application to raise an e8ception occur so rarely that we)d rather just treat them to a uniform catchall error pae. Such an error pae can be implemented in the $pplicationController where the rescue,action,in,public/e8ception1 method will be called when an e8ception bubbles up without bein cauht at the lower levels. More on this techni*ue in Chapter AA, ?eployment and Scalin, on pae 662. Kile A7 def display,cart ]cart . find,cart ]items . ]cart.items if ]items.emptyW flashX%noticeY . VPour cart is currently emptyV redirect,to/%action .N + inde8 + 1 end end Remember that on pae @0 we set up the store layout to use the value in ]pae,title if it was defined. #et)s use that facility now. >dit the template display,cart.rhtml to override the pae title whenever it)s used. (his is a nice feature% instance variables set in the template are available in the layout. Kile A: -Z ]pae,title . VPour !ramatic CartV -ZN Sensin the end of an iteration, we call our customer over and show her that the error is now properly handled. She)s delihted and continues to play with the application. She notices two thins on our new cart display. Kirst, the >mpty cart button isn)t connected to anythin /we 5new that1. Second, if she adds the same boo5 twice to the cart, it shows the total price as 3:.: /two times _A:.:31, rather than _3:.:2. (hese two minor chanes will be our ne8t iteration. 4e should ma5e it before headin home. G.3 Iteration C7% Kinishin the Cart #et)s start by implementin the >mpty cart lin5 on the cart display. 4e 5now by now that we have to implement an empty,cart /1 method in the store controller. #et)s have it deleate the responsibility to the Cart class. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; C7% K I;IS=I;J (=> C $R( :0 def empty,cart find,cart.emptyE flashX%noticeY . + Pour cart is now empty + redirect,to/%action .N + inde8 + 1 end Over in the cart, we)ll implement the emptyE /1 method. /Ruby method names can end with e8lamation mar5s and *uestion mar5s. 4e use a method name endin in an e8clamation mar5 as a hint to future develop- ers that this method does somethin destructive.1 Kile AG def emptyE ]items . XY ]total,price . 2.2 end ;ow when we clic5 the >mpty cart lin5, we et ta5en bac5 to the catalo pae, and a nice little messae says =owever, before we brea5 an arm tryin to pat ourselves on the bac5, let)s loo5 bac5 at our code. 4e)ve just introduced two pieces of duplication. Kirst, in the store controller, we now have three places that put a messae into the flash and redirect to the inde8 pae. Sounds li5e we should e8tract that common code into a method, so let)s implement redirect,to,inde8 /1 and chane the add,to,cart / 1, display,cart /1, and empty,cart /1 methods to use it. Kile AS def add,to,cart product . !roduct.find/paramsX%idY1 ]cart . find,cart ]cart.add,product/product1 redirect,to/%action .N + display,cart + 1 rescue loer.error/V$ttempt to access invalid product U`paramsX%idYbV1 redirect,to,inde8/ + Invalid product + 1 end def display,cart ]cart . find,cart ]items . ]cart.items if ]items.emptyW redirect,to,inde8/VPour cart is currently emptyV1 end end Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; C7% K I;IS=I;J (=> C $R( :A def empty,cart ]cart . find,cart emptyE W R pae 6@: ]cart.emptyE redirect,to,inde8/ + Pour cart is now empty + 1 end private W R pae 6@7 private def redirect,to,inde8/ms . nil1 flashX%noticeY . ms if ms redirect,to/%action .N + inde8 + 1 end Our second piece of duplication is in the cart model, where both the constructor and the emptyE method do the same thin. (hat)s easily remediedDwe)ll have the constructor call the emptyE /1 method. Kile AG def initiali'e emptyE end def emptyE ]items . XY ]total,price . 2.2 end =elpers Our second tas5 in this iteration is to tidy up the dollar amounts displayed in the cart. Rather than 3:.:, we should be displayin _3:.:2. ;ow we all 5now how to do this. 4e can just slap a call to the sprintf /1 method @ into the view. -td alin.VrihtVN -Z. sprintf/V_Z2.AfV, item.unit,price1 ZN -OtdN &ut before we do that, let)s thin5 for a second. 4e)re oin to have to do this for every dollar amount we display. (here)s some duplication there. 4hat happens if our customer says later that we need to insert commas between sets of three diits, or represent neative numbers in parenthe- sesW It would be better to e8tract monetary formattin out into a sinle method so we have a sinle point to chane. $nd before we write that method, we have to decide where it oes. Kortunately, Rails has an answerDit lets you define helpers. $ helper is simply code in a module that is automatically included into your views. Pou define helper files in appOhelpers . $ helper named 8y' ,helper.rb defines methods that will be available to views invo5ed by the 8y' controller. If you define helper methods in the file appOhelpersOapplication,helper.rb , those @ Old C hippies will reconi'e sprintf /1 as the method that ta5es a strin containin Z8 se*uences. It substitutes its additional parameters for these se*uences. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; C7% K I;IS=I;J (=> C $R( :7 methods will be available in all views. $s displayin dollar amounts seems to be a fairly universal thin, let)s add our method there. module W R pae 6@7 Kile A@ U (he methods added to this helper will be available U to all templates in the application. module $pplication=elper def fmt,dollars/amt1 sprintf/V_Z2.AfV, amt1 end end (hen we)ll update our cart display view to use this new method. Kile A: -Z for item in ]items product . item.product -ZN -trN -tdN-Z. item.*uantity ZN-OtdN -tdN-Z. h/product.title1 ZN-OtdN -td alin.VrihtVN-Z. fmt,dollars/item.unit,price1 ZN-OtdN -td alin.VrihtVN-Z. fmt,dollars/item.unit,price ^ item.*uantity1 ZN-OtdN -OtrN -Z end ZN -trN -td colspan.V7V alin.VrihtVN-stronN(otal%-OstronN-OtdN -td id.VtotalcellVN-Z. fmt,dollars/]cart.total,price1 ZN-OtdN -OtrN ;ow when we display the cart, the dollar amounts all loo5 nicely formatted. ;ow it)s time for a confession. $t the time we wrote this sample appli- cation, Rails was a few releases away from version 0.2. Since then, a number of built-in helper methods have been added. One of these is the method number,to,currency /1, which would be a nice replacement for the fmt,dollars /1 method we just wrote. =owever, if we chaned the boo5 to use Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; C7% K I;IS=I;J (=> C $R( :6 the new method, we wouldn)t be able to show you how to write your own helpers, would weW One last thin. On the catalo pae /created by the inde8.rhtml template1 we use sprintf /1 to format the product prices. ;ow that we have a handy-dandy currency formatter, we)ll use it there too. 4e won)t bother to show the template aain hereDthe chane is trivial. 4hat 4e Fust ?id It)s been a busy day, but a productive one. 4e)ve added a shoppin cart to our store, and alon the way we)ve dipped our toes into some neat Rails features. H Isin sessions to store state H $ssociatin Rails models usin belons,to H Creatin and interatin nondatabase models H Isin the flash to pass errors between actions H Isin the loer to lo events H Removin duplication with helpers So now the customer wants to see some chec5out functionality. (ime for a new chapter. Report erratum !repared e8clusively for Rida $l &ara'i Chapter : (as5 ?% Chec5outE #et)s ta5e stoc5. So far, we)ve put toether a basic product administration system, we)ve implemented a catalo, and we have a pretty spiffy-loo5in shoppin cart. So now we need to let the buyer actually purchase the contents of that cart. #et)s o ahead and implement the chec5out function. 4e)re not oin to o overboard here. Kor now, all we)ll do is capture the customer)s contact details and payment option. Isin these we)ll con- struct an order in the database. $lon the way we)ll be loo5in a bit more at models, validation, form handlin, and components. Foe $s5s... 4here)s the Credit-Card !rocessinW $t this point, our tutorial application is oin to divere slihtly from real- ity. In the real world, we)d probably want our application to handle the commercial side of chec5out. 4e miht even want to interate credit- card processin /possibly usin the !ayment module1. =owever, inte- ratin with bac5end payment processin systems re*uires a fair amount of paperwor5 and jumpin throuh hoops. $nd this would distract from loo5in at Rails, so we)re oin to punt on this particular detail. http%OOrubyfore.orOprojectsOpayment !repared e8clusively for Rida $l &ara'i I (>R$(IO; ?0% C $!(IRI;J $; O R?>R :S :.0 Iteration ?0% Capturin an Order $n order is a set of line items, alon with details of the purchase transac- tion. 4e already have the line itemsDwe defined them when we created the shoppin cart in the previous chapter. 4e don)t yet have a table to contain orders. &ased on the diaram on pae 6@, combined with a brief chat with our customer, we can create the orders table. Kile 7@ create table orders / id int not null auto,increment, name varchar/0221 not null, email varchar/A331 not null, address te8t not null, pay,type char/021 not null, primary 5ey /id1 1C 4e 5now that when we create a new order, it will be associated with one or more line items. In database terms, this means that we)ll need to add a forein 5ey reference from the line,items table to the orders table, so we)ll ta5e this opportunity to update the ??# for line items too. /=ave a loo5 at the listin of create.s*l on pae 6G@ to see how the drop table statements should be added.1 Kile 7@ create table line,items / id int not null auto,increment, product,id int not null, order,id int not null, *uantity int not null default 2, unit,price decimal/02,A1 not null, constraint f5,items,product forein 5ey /product,id1 references products/id1, constraint f5,items,order forein 5ey /order,id1 references orders/id1, primary 5ey /id1 1C Remember to update the schema /which will empty your database of any data it contains1 and create the Order model usin the Rails enerator. 4e don)t reenerate the model for line items, as the one that)s there is fine. depotN mys*l depot,development -dbOcreate.s*l depotN ruby scriptOenerate model Order (hat told the database about the forein 5eys. (his is a ood thin, as many databases will chec5 forein 5ey constraints, 5eepin our code hon- est. &ut we also need to tell Rails that an order has many line items and that a line item belons to an order. Kirst, we open up the newly created order.rb file in appOmodels and add a call to has,many /1. Kile 7A class Order - $ctiveRecord%%&ase has,many %line,items end Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; ?0% C $!(IRI;J $; O R?>R :@ ;e8t, we)ll specify a lin5 in the opposite direction, addin a call to the method belons,to /1 in the line,item.rb file. /Remember that a line item was already declared to belon to a product when we set up the cart.1 Kile 70 class #ineItem - $ctiveRecord%%&ase belons,to %product belons,to %order U . . . 4e)ll need an action to orchestrate capturin order details. In the previous chapter we set up a lin5 in the cart view to an action called chec5out , so now we have to implement a chec5out /1 method in the store controller. Kile 72 def chec5out ]cart . find,cart ]items . ]cart.items emptyW W R pae 6@: if ]items.emptyW redirect,to,inde8/V(here + s nothin in your cartEV1 else ]order . Order.new end end ;otice how we first chec5 to ma5e sure that there)s somethin in the cart. (his prevents people from naviatin directly to the chec5out option and creatin empty orders. $ssumin we have a valid cart, we create a new Order object for the view to fill in. ;ote that this order isn)t yet saved in the databaseDit)s just used by the view to populate the chec5out form. (he chec5out view will be in the file chec5out.rhtml in the appOviewsOstore directory. #et)s build somethin simple that will show us how to marry form data to Rails model objects. (hen we)ll add validation and error han- dlin. $s always with Rails, it)s easiest to start with the basics and iterate towards nirvana /the state of bein, not the band1. Rails and Korms Rails has reat support for ettin data out of relational databases and into Ruby objects. So you)d e8pect to find that it has correspondin support for transferrin that data bac5 and forth between those model objects and users on browsers. 4e)ve already seen one e8ample of this. 4hen we created our product administration controller, we used the scaffold enerator to create a form that captures all the data for a new product. If you loo5 at the code for that view /in appOviewsOadminOnew.rhtml 1, you)ll see the followin. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; ?0% C $!(IRI;J $; O R?>R :G Kile 76 -h0N;ew product-Oh0N -Z. start,form,ta %action .N + create + ZN -Z. render,partial VformV ZN -Z. submit,ta VCreateV ZN -Z. end,form,ta ZN -Z. lin5,to + &ac5 + , %action .N + list + ZN (his references a subform usin render,partial/+form+1 . 0 (hat subform, in the file ,form.rhtml , captures the information about a product. Kile 77 -Z. error,messaes,for + product + ZN -E--Xform%productY--N -pN-label for.Vproduct,titleVN(itle-OlabelN-brON -Z. te8t,field + product + , + title + ZN-OpN -pN-label for.Vproduct,descriptionVN?escription-OlabelN-brON -Z. te8t,area + product + , + description + , %rows .N 3 ZN-OpN -pN-label for.Vproduct,imae,urlVNImae url-OlabelN-brON -Z. te8t,field + product + , + imae,url + ZN-OpN -pN-label for.Vproduct,priceVN!rice-OlabelN-brON -Z. te8t,field + product + , + price + ZN-OpN -pN-label for.Vproduct,date,availableVN?ate available-OlabelN-brON -Z. datetime,select + product + , + date,available + ZN-OpN -E--Xeoform%productY--N 4e could use the scaffold enerator to create a form for the orders table too, but the Rails-enerated form is not all that pretty. 4e)d li5e to produce somethin nicer. #et)s di further into all those methods in the autoener- ated form before creatin the data entry form for ourselves. Rails has model-aware helper methods for all the standard =(M# input tas. Kor e8ample, say we need to create an =(M# - input N ta to allow the buyer to enter their name. In Rails, we could write somethin li5e the followin in the view. -Z. te8t,field/VorderV, VnameV, %si'e .N 62 1 ZN =ere, te8t,field /1 will create an =(M# - input N ta with type.Vte8tV . (he neat thin is that it would populate that field with the contents of the name field in the ]order model. 4hat)s more, when the end user eventually submits the form, the model will be able to capture the new value of this field from the browser)s response and store it, ready to be written to the database as re*uired. (here are a number of these form helper methods /we)ll loo5 at them in more detail startin on pae 77A1. In addition to te8t,field /1, we)ll be usin 0 render,partial /1 is a deprecated form of render/%partial.N...1 . (he scaffold enerators had not been updated to create code usin the newer form at the time this boo5 was written. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; ?0% C $!(IRI;J $; O R?>R :: te8t,area /1 to capture the buyer)s address and select /1 to create a selection list for the payment options. Of course, for Rails to et a response from the browser, we need to lin5 the form to a Rails action. 4e could do that by specifyin a lin5 to our application, controller, and action in the action. attribute of a - form N ta, but it)s easier to use form,ta /1, another Rails helper method that does the wor5 for us. So, with all that bac5round out of the way, we)re ready to create the view to capture the order information. =ere)s our first attempt at the chec5- out.rhtml file in appOviewsOstore directory. Kile 7S -Z ]pae,title . VChec5outV -ZN -Z. start,form,ta/%action .N Vsave,orderV1 ZN -tableN -trN -tdN;ame%-OtdN -tdN-Z. te8t,field/VorderV, VnameV, Vsi'eV .N 62 1 ZN-OtdN -OtrN -trN -tdN>Mail%-OtdN -tdN-Z. te8t,field/VorderV, VemailV, Vsi'eV .N 62 1 ZN-OtdN -OtrN -tr valin.VtopVN -tdN$ddress%-OtdN -tdN-Z. te8t,area/VorderV, VaddressV, VcolsV .N 62, VrowsV .N 31 ZN-OtdN -OtrN -trN -tdN!ay usin%-OtdN -tdN-Z. options . XXVSelect a payment optionV, VVYY T Order%%!$PM>;(,(P!>S select/VorderV, Vpay,typeV, options1 ZN-OtdN -OtrN -trN -tdN-OtdN -tdN-Z. submit,ta/V C=>C<OI( V1 ZN-OtdN -OtrN -OtableN -Z. end,form,ta ZN (he only tric5y thin in there is the code associated with the selection list. 4e)ve assumed that the list of available payment options is an attribute of the Order modelDit will be an array of arrays in the model file. (he first element of each subarray is the strin to be displayed as option in the selection and the second value ets stored in the database. A 4e)d better define the option array in the model order.rb before we foret. A If we anticipate that other non-Rails applications will update the orders table, we miht want to move the list of payment types into a separate loo5up table and ma5e the orders column a forein 5ey referencin that new table. Rails provides ood support for eneratin selection lists in this conte8t too. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; ?0% C $!(IRI;J $; O R?>R 022 Kiure :.0% Our Kirst Chec5out !ae Kile 7A !$PM>;(,(P!>S . X X VChec5V, Vchec5V Y, X VCredit CardV, VccV Y, X V!urchase OrderV, VpoV Y Y.free'e U free'e to ma5e this array constant If there)s no current selection in the model, we)d li5e to display some prompt te8t in the browser field. 4e do this by merin a new option at the start of the selection options returned by the model. (his new option has an appropriate display strin and a blan5 value. So, with all that in place, we can fire up our trusty browser. $dd some items to the cart, and clic5 the chec5out lin5. Pou)ll see a shiny new chec5out pae li5e the one in Kiure :.0 . #oo5in oodE Of course, if you clic5 the Chec5out button, you)ll be reeted with In5nown action ;o action responded to save,order So let)s et on and implement the save,order /1 action in our store controller. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; ?0% C $!(IRI;J $; O R?>R 020 (his method has to 0. Capture the values from the form to populate a new Order model object. A. $dd the line items from our cart to that order. 7. 9alidate and save the order. If this fails, display the appropriate mes- saes and let the user correct any problems. 6. Once the order is successfully saved, redisplay the catalo pae, includin a messae confirmin that the order has been placed. (he method ends up loo5in somethin li5e this. Kile 72 #ine 0 def save,order - ]cart . find,cart - ]order . Order.new/paramsX%orderY1 - ]order.line,items -- ]cart.items 3 if ]order.save - ]cart.emptyE - redirect,to,inde8/ + (han5 you for your order. + 1 - else - render/%action .N + chec5out + 1 02 end - end On line 7, we create a new Order object and initiali'e it from the form data. In this case we want all the form data related to order objects, so we select the %order hash from the parameters /we)ll tal5 about how forms are lin5ed to models on pae 7601. (he ne8t line adds into this order the line items that are already stored in the cartDthe session data is still there throuhout this latest action. ;otice that we didn)t have to do anythin special with the various forein 5ey fields, such as settin the order,id column in the line item rows to reference the newly created order row. Rails does that 5nittin for us usin the has,many /1 and belons,to /1 declarations we added to the Order and #ineItem models. ;e8t, on line 3, we tell the order object to save itself /and its children, the line items1 to the database. $lon the way, the order object will perform validation /but we)ll et to that in a minute1. If the save succeeds, we empty out the cart ready for the ne8t order and redisplay the catalo, usin our redirect,to,inde8 /1 method to display a cheerful messae. If instead the save fails, we redisplay the chec5out form. One last thin before we call our customer over. Remember when we showed her the first product maintenance paeW She as5ed us to add validation. 4e should probably do that for our chec5out pae too. Kor now we)ll just chec5 that each of the fields in the order has been iven a Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; ?0% C $!(IRI;J $; O R?>R 02A Foe $s5s... $ren)t Pou Creatin ?uplicate OrdersW Foe)s concerned to see our controller creatin Order model objects in two actions, chec5out and save,order . =e)s wonderin why this doesn)t lead to duplicate orders in the database. (he answer is simple% the chec5out action creates an Order object in mem- ory simply to ive the template code somethin to wor5 with. Once the response is sent to the browser, that particular object ets abandoned, and it will eventually be reaped by Ruby)s arbae collector. It never ets close to the database. (he save,order action also creates an Order object, populatin it from the form fields. (his object does et saved in the database. So, model objects perform two roles% they map data into and out of the database, but they are also just reular objects that hold business data. (hey affect the database only when you tell them to, typically by callin save /1. value. 4e 5now how to do thisDwe add a validates,presence,of /1 call to the Order model. Kile 7A validates,presence,of %name, %email, %address, %pay,type So, as a first test of all of this, hit the Chec5out button on the chec5out pae without fillin in any of the form fields. 4e e8pect to see the chec5out pae redisplayed alon with some error messaes complainin about the empty fields. Instead, we simply see the chec5out paeDno error mes- saes. 4e forot to tell Rails to write them out. 7 $ny errors associated with validatin or savin a model are stored with that model. (here)s another helper method, error,messaes,for /1, that e8tracts and formats these in a view. 4e just need to add a sinle line to the start of our chec5out.rhtml file. Kile 7S -Z. error,messaes,for/VorderV1 ZN 7 If you)re followin alon at home and you et the messae ;o action responded to save,order, it)s possible that you added the save,order /1 method after the private declaration in the controller. !rivate methods cannot be called as actions. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; ?0% C $!(IRI;J $; O R?>R 027 Kiure :.A% Kull =ouseE >very Kield Kails 9alidation Fust as with the administration validation, we need to add the scaffold.css stylesheet to our store layout file to et decent formattin for these errors. Kile 73 -Z. stylesheet,lin5,ta VscaffoldV, VdepotV, %media .N VallV ZN Once we do that, submittin an empty chec5out pae shows us a lot of hihlihted errors, as shown in Kiure :.A . If we fill in some data as shown at the top of Kiure :.7, on pae 023, and clic5 Chec5out , we should et ta5en bac5 to the catalo, as shown at the bottom of the fiure. &ut did it wor5W #et)s loo5 in the database. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; ?A% S =O4 C $R( C O;(>;(S O; C =>C<OI( 026 depotN mys*l depot,development 4elcome to the MySB# monitor. Commands end with C or Q. mys*lN select ^ from ordersC T----T-------------T-----------------------T-------------T----------T [ id [ name [ email [ address [ pay,type [ T----T-------------T-----------------------T-------------T----------T [ 7 [ ?ave (homas [ customer]prapro.com [ 0A7 Main St [ chec5 [ T----T-------------T-----------------------T-------------T----------T 0 row in set /2.22 sec1 mys*lN select ^ from line,itemsC T----T------------T----------T----------T------------T [ id [ product,id [ order,id [ *uantity [ unit,price [ T----T------------T----------T----------T------------T [ 6 [ 6 [ 7 [ 0 [ A:.:3 [ T----T------------T----------T----------T------------T 0 row in set /2.22 sec1 Ship itE Or, at least, let)s show it to our customer. She li5es it. >8cept.... ?o you suppose we could add a summary of the cart contents to the chec5- out paeW Sounds li5e we need a new iteration. :.A Iteration ?A% Show Cart Contents on Chec5out In this iteration we)re oin to add a summary of the cart contents to the chec5out pae. (his is pretty easy. 4e already have a layout that shows the items in a cart. $ll we have to do is cut and paste the code across into the chec5out view and...ummm...oh, yeah, you)re watchin what I)m doin. O<, so cut-and-paste codin is out, because we don)t want to add dupli- cation to our code. 4hat else can we doW It turns out that we can use Rails components to allow us to write the cart display code just once and invo5e it from two places. /(his is actually a very simple use of the compo- nent functionalityC we)ll see it in more detail in Section 0@.:, #ayouts and Components, on pae 73S.1 $s a first pass, let)s edit the view code in chec5out.rhtml to include a call to render the cart at the top of the pae, before the form. Kile 7G -Z. error,messaes,for/VorderV1 ZN -Z. render,component/%action .N Vdisplay,cartV1 ZN -Z. start,form,ta/%action .N Vsave,orderV1 ZN -tableN -trN -tdN;ame%-OtdN (he render,component /1 method invo5es the iven action and substitutes the output it renders into the current view. 4hat happens when we run this codeW =ave a loo5 at Kiure :.6, on pae 02S. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; ?A% S =O4 C $R( C O;(>;(S O; C =>C<OI( 023 Kiure :.7% Our Kirst Chec5out Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; ?A% S =O4 C $R( C O;(>;(S O; C =>C<OI( 02S Kiure :.6% Methin5s (he Component Renders (oo Much OopsE Invo5in the display,cart action has substituted in the entire ren- dered pae, includin the layout. 4hile this is interestin in a post- modern, self-referential 5ind of way, it)s probably not what our buyers were e8pectin to see. 4e)ll need to tell the controller not to use our fancy layout when it)s ren- derin the cart as a component. Kortunately, that)s not too difficult. 4e can set parameters in the render,component /1 call that are accessible in the action that)s invo5ed. 4e can use a parameter to tell our display,cart /1 action not to invo5e the full layout when it)s bein invo5ed as a compo- nent. It can override Rails) default renderin in that case. (he first step is to add a parameter to the render,component /1 call. Kile 62 -Z. render,component/%action .N Vdisplay,cartV, %params .N ` %conte8t .N %chec5out b1 ZN 4e)ll alter the display,cart /1 method in the controller to call different render methods dependin on whether this parameter is set. !reviously we didn)t have to render our layout e8plicitlyC if an action method e8its without callin a render method, Rails will call render /1 automatically. ;ow we need to override this, callin render/%layout.Nfalse1 in a chec5out conte8t. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; ?A% S =O4 C $R( C O;(>;(S O; C =>C<OI( 02@ Kile 7: def display,cart ]cart . find,cart ]items . ]cart.items if ]items.emptyW redirect,to,inde8/VPour cart is currently emptyV1 end if paramsX%conte8tY .. %chec5out render/%layout .N false1 end end 4hen we hit Refresh in the browser, we see a much better result. 4e call our customer over, and she)s delihted. One small re*uest% can we remove the >mpty cart and Chec5out options from the menu at the rihtW $t the ris5 of ettin thrown out of the prorammers union, we say, L(hat)s not a problem.M $fter all, we just have to add some conditional code to the display,cart.rhtml view. Kile 6A -ulN -liN-Z. lin5,to + Continue shoppin + , %action .N Vinde8V ZN-OliN -Z unless paramsX%conte8tY .. %chec5out -ZN -liN-Z. lin5,to + >mpty cart + , %action .N Vempty,cartV ZN-OliN -liN-Z. lin5,to + Chec5out + , %action .N Vchec5outV ZN-OliN -Z end -ZN -OulN 4hile we)re at it, we)ll add a nice little headin just before the start of the form in the template chec5out.rhtml in appOviewsOstore . Kile 60 -Z. error,messaes,for/VorderV1 ZN -Z. render,component/%action .N Vdisplay,cartV, %params .N ` %conte8t .N %chec5out b1 ZN -h7N!lease enter your details below-Oh7N Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; ?A% S =O4 C $R( C O;(>;(S O; C =>C<OI( 02G $ *uic5 refresh in the browser, and we have a nice loo5in chec5out pae. Our customer is happy, our code is neatly tuc5ed into our repository, and it)s time to move on. ;e8t we)ll be loo5in at addin shippin functionality to ?epot. 4hat 4e Fust ?id In a fairly short amount of time, we did the followin. H $dded an orders table /with correspondin model1 and lin5ed its rows to the line items we)d defined previously H Created a form to capture details for the order and lin5ed it to the Order model H $dded validation and used helper methods to display errors bac5 to the user H Ised the component system to include the cart summary on the chec5out pae Report erratum !repared e8clusively for Rida $l &ara'i Chapter 02 (as5 >% Shippin 4e)re now at the point where buyers can use our application to place orders. Our customer would li5e to see what it)s li5e to fulfill these orders. ;ow, in a fully fleded store application, fulfillment would be a lare, com- ple8 deal. 4e miht need to interate with various bac5end shippin aen- cies, we miht need to enerate feeds for customs information, and we)d probably need to lin5 into some 5ind of accountin bac5end. 4e)re not oin to do that here. &ut even thouh we)re oin to 5eep it simple, we)ll still have the opportunity to e8periment with partial templates, collections, and a slihtly different interaction style to the one we)ve been usin so far. 02.0 Iteration >0% &asic Shippin 4e chat for a while with our customer about the shippin function. She says that she wants to see a list of the orders that haven)t yet been shipped. $ shippin person will loo5 throuh this list and fulfill one or more orders manually. Once the order had been shipped, the person would mar5 them as shipped in the system, and they)d no loner appear on the shippin pae. Our first tas5 is to find some way of indicatin whether an order has shipped. Clearly we need a new column in the orders table. 4e could ma5e it a simple character column /perhaps with LPM meanin shipped and L;M not shipped1, but I prefer usin timestamps for this 5ind of thin. If the column has a null value, the order has not been shipped. Otherwise, the value of the column is the date and time of the shipment. (his way the column both tells us whether an order has shipped and, if so, when it shipped. !repared e8clusively for Rida $l &ara'i I (>R$(IO; >0% & $SIC S =I!!I;J 002 ?avid Says... ?ate and (imestamp Column ;ames (here)s a Rails column-namin convention that says datetime fields should end in ,at and date fields should end in ,on . (his results in natural names for columns, such as last,edited,on and sent,at . (his is the convention that)s pic5ed up by auto-timestampin, described on pae AS@, where columns with names such as created,at are automat- ically filled in by Rails. So, let)s modify our create.s*l file in the db directory, addin the shipped,at column to the orders table. Kile 6@ create table orders / id int not null auto,increment, name varchar/0221 not null, email varchar/A331 not null, address te8t not null, pay,type char/021 not null, shipped,at datetime null, primary 5ey /id1 1C 4e load up the new schema. depotN mys*l depot,development -dbOcreate.s*l (o save myself havin to enter product data throuh the administration paes each time I reload the schema, I also too5 this opportunity to write a simple set of SB# statements that loads up the product table. It could be somethin as simple as loc5 tables products writeC insert into products values/null, + !ramatic !roject $utomation + , Utitle + $ really reat readE + , Udescription + OimaesOpic0.jp + , Uimae,url + A:.:3 + , Uprice + A226-0A-A3 23%22%22 + 1C Udate,available insert into products values/ ++ , + !ramatic 9ersion Control + , + $ really controlled readE + , + OimaesOpicA.jp + , + A:.:3 + , + A226-0A-A3 23%22%22 + 1C unloc5 tablesC Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; >0% & $SIC S =I!!I;J 000 (hen load up the database. depotN mys*l depot,development -dbOproduct,data.s*l 4e)re bac5 wor5in on the administration side of our application, so we)ll need to create a new action in the admin,controller.rb file. #et)s call it ship /1. 4e 5now its purpose is to et a list of orders awaitin shippin for the view to display, so let)s just code it that way and see what happens. Kile 67 def ship ]pendin,orders . Order.pendin,shippin end 4e now need to implement the pendin,shippin /1 class method in the Order model. (his returns all the orders with null in the shipped,at column. Kile 66 def self.pendin,shippin find/%all, %conditions .N Vshipped,at is nullV1 end Kinally, we need a view that will display these orders. (he view has to con- tain a form, because there will be a chec5bo8 associated with each order /the one the shippin person will set once that order has been dispatched1. Inside that form we)ll have an entry for each order. 4e could include all the layout code for that entry within the view, but in the same way that we brea5 comple8 code into methods, let)s split this view into two parts% the overall form and the part that renders the individual orders in that form. (his is somewhat analoous to havin a loop in code call a separate method to do some processin for each iteration. 4e)ve already seen one way of handlin these 5inds of subroutines at the view level when we used components to show the cart contents on the chec5out pae. $ lihter-weiht way of doin the same thin is usin a partial template. Inli5e the component-based approach, a partial tem- plate has no correspondin actionC it)s simply a chun5 of template code that has been factored into a separate file. #et)s create the overall ship.rhtml view in the directory appOviewsOadmin . Kile 6S #ine 0 -h0NOrders (o &e Shipped-Oh0N - - -Z. form,ta/%action .N VshipV1 ZN - 3 -table cellpaddin.V3V cellspacin.V2VN - -Z. render/%partial .N Vorder,lineV, %collection .N ]pendin,orders1 ZN - -OtableN - - -br ON 02 -input type.VsubmitV value.V S=I! C=>C<>? I(>MS V ON - - -Z. end,form,ta ZN - -br ON Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; >0% & $SIC S =I!!I;J 00A ;ote the call to render /1 on line S. (he %collection parameter is the list of orders that we created in the action method. (he %partial parameter performs double duty. (he first use of Vorder,lineV is to identify the name of the partial template to render. (his is a view, and so it oes into an . rhtml file just li5e other views. =owever, because partials are special, you have to name them with a leadin underscore in the filename. In this case, Rails will loo5 for the partial in the file appOviewsOadminO,order,line.rhtml . (he Vorder,lineV parameter also tells Rails to set a local variable called order,line to the value of the order currently bein rendered. (his variable is available only inside the partial template. Kor each iteration over the collection of orders, order,line will be updated to reference the ne8t order in the collection. 4ith all that e8planation under our belts, we can now write the partial template, ,order,line.rhtml . Kile 63 -tr valin.VtopVN -td class.Volnamebo8VN -div class.VolnameVN-Z. h/order,line.name1 ZN-OdivN -div class.VoladdressVN-Z. h/order,line.address1 ZN-OdivN -OtdN -td class.Volitembo8VN -Z order,line.line,items.each do [li[ ZN -div class.VolitemVN -span class.Volitem*tyVN-Z. li.*uantity ZN-OspanN -span class.VolitemtitleVN-Z. li.product.title ZN-OspanN -OdivN -Z end ZN -OtdN -tdN -Z. chec5,bo8/Vto,be,shippedV, order,line.id, `b, VyesV, VnoV1 ZN -OtdN -OtrN So, usin the store part of the application, create a couple of orders. (hen switch across to localhost%7222OadminOship . Pou)ll see somethin li5e Ki- ure 02.0, on the followin pae. It wor5ed, but it doesn)t loo5 very pretty. On the store side of the application, we used a layout to frame all the paes and apply a common stylesheet. &efore we o any further, let)s do the same here. In fact, Rails has already created the layout /when we first enerated the admin scaffold1. #et)s just ma5e it prettier. >dit the file admin.rhtml in the appOviewsOlayouts directory. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; >0% & $SIC S =I!!I;J 007 Kiure 02.0% It)s a Shippin !ae, &ut It)s Ily Kile 32 -htmlN -headN -titleN$?MI;IS(>R !rapro &oo5s Online Store-OtitleN -Z. stylesheet,lin5,ta VscaffoldV, VdepotV, VadminV, %media .N VallV ZN -OheadN -bodyN -div id.VbannerVN -Z. ]pae,title [[ V$dminister &oo5shelfV ZN -OdivN -div id.VcolumnsVN -div id.VsideVN -Z. lin5,to/V!roductsV, %action .N VlistV1 ZN -Z. lin5,to/VShippinV, %action .N VshipV1 ZN -OdivN -div id.VmainVN -Z if ]flashX%noticeY -ZN -div id.VnoticeVN-Z. ]flashX%noticeY ZN-OdivN -Z end -ZN -Z. ]content,for,layout ZN -OdivN -OdivN -ObodyN -OhtmlN =ere we)ve used the stylesheet,lin5,ta /1 helper method to create lin5s to scaffold.css , depot.css , and a new admin.css stylesheet. /I li5e to set different color schemes in the administration side of a site so that it)s immediately obvious that you)re wor5in there.1 $nd now we have a dedicated CSS file for the administration side of the application, we)ll move the list-related styles we added to scaffold.css bac5 on pae S3 into it. (he admin.css file is listed Section C.0, CSS Kiles, on pae 32G. 4hen we refresh our browser, we see the prettier display that follows. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; >0% & $SIC S =I!!I;J 006 ;ow we have to fiure out how to mar5 orders in the database as shipped when the person doin the shippin chec5s the correspondin bo8 on the form. ;otice how we declared the chec5bo8 in the partial template, ,order,line.rhtml . -Z. chec5,bo8/Vto,be,shippedV, order,line.id, `b, VyesV, VnoV1 ZN (he first parameter is the name to be used for this field. (he second parameter is also used as part of the name, but in an interestin way. If you loo5 at the =(M# produced by the chec5,bo8 /1 method, you)ll see somethin li5e -input name.Vto,be,shippedX0YV type.Vchec5bo8V value.VyesV ON In this e8ample, the order id was 0, so Rails used the name to,be,shippedX0Y for the chec5bo8. (he last three parameters to chec5,bo8 /1 are an /empty1 set of options, and the values to use for the chec5ed and unchec5ed states. 4hen the user submits this form bac5 to our application, Rails parses the form data and detects these fields with inde8-li5e names. It splits them out, so that the parameter to,be,shipped will point to a =ash, where the 5eys are the inde8 values in the name and the value is the value of the correspondin form ta. /(his process is e8plained in more detail on pae 760.1 In the case of our e8ample, if just the sinle chec5bo8 for Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; >0% & $SIC S =I!!I;J 003 the order with an id of 0 was chec5ed, the parameters returned to our controller would include ]params . ` Vto,be,shippedV .N ` V0V .N VyesV b b &ecause of this special handlin of forms, we can iterate over all the chec5- bo8es in the response from the browser and loo5 for those that the ship- pin person has chec5ed. to,ship . paramsX%to,be,shippedY if to,ship to,ship.each do [order,id, do,it[ if do,it .. VyesV U mar5 order as shipped... end end end 4e have to wor5 out where to put this code. (he answer depends on the wor5flow we want the shippin person to see, so we wander over and chat with our customer. She e8plains that there are multiple wor5flows when shippin. Sometimes you miht run out of a particular item in the shippin area, so you)d li5e to s5ip them for a while until you et a chance to restoc5 from the warehouse. Sometimes the shipper will try to ship thins with the same style pac5ain and then move on to items with different pac5ain. So, our application shouldn)t enforce just one way of wor5in. $fter chattin for a while, we come up with a simple desin for the ship- pin function. 4hen a shippin person selects the shippin function, the function displays all orders that are pendin shippin. (he ship- pin person can wor5 throuh the list any way they want, clic5in the chec5bo8 when they ship a particular order. 4hen they eventually hit the Ship Chec5ed Items button, the system will update the orders in the database and redisplay the items still remainin to be shipped. Obviously this scheme wor5s only if shippin is handled by just one person at a time /because two people usin the system concurrently could both choose to ship the same orders1. Kortunately, our customer)s company has just one shippin person. Jiven that information, we can now implement the complete ship /1 action in the admin,controller.rb controller. 4hile we)re at it, we)ll 5eep trac5 of how many orders et mar5ed as shipped each time the form is submittedDthis lets us write a nice flash notice. ;ote that the ship /1 method does not redirect at the endDit simply redis- plays the ship view, updated to reflect the items we just shipped. &ecause Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; >0% & $SIC S =I!!I;J 00S of this, we use the flash in a new way. (he flash.now facility adds a mes- sae to the flash for just the current re*uest. It will be available when we render the ship template, but the messae will not be stored in the session and made available to the ne8t re*uest. Kile 6G def ship count . 2 if thins,to,ship . paramsX%to,be,shippedY count . do,shippin/thins,to,ship1 if count N 2 count,te8t . plurali'e/count, VorderV1 flash.nowX%noticeY . VU`count,te8tb mar5ed as shippedV end end ]pendin,orders . Order.pendin,shippin end private def do,shippin/thins,to,ship1 count . 2 thins,to,ship.each do [order,id, do,it[ if do,it .. VyesV order . Order.find/order,id1 order.mar5,as,shipped order.save count T. 0 end end count end def plurali'e/count, noun1 case count plurali'e W R pae 0GS when 2% V;o U`noun.plurali'ebV when 0% VOne U`nounbV else VU`countb U`noun.plurali'ebV end end 4e also need to add the mar5,as,shipped /1 method to the Order model. Kile 6: def mar5,as,shipped self.shipped,at . (ime.now end ;ow when we mar5 somethin as shipped and clic5 the button, we et the nice messae shown in Kiure 02.A, on the followin pae. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; >0% & $SIC S =I!!I;J 00@ Kiure 02.A% Status Messaes ?urin Shippin 4hat 4e Fust ?id (his was a fairly small tas5. 4e saw how to do the followin. H 4e can use partial templates to render sections of a template and helpers such as render /1 with the %collection parameter to invo5e a partial template for each member of a collection. H 4e can represent arrays of values on forms /althouh there)s more to learn on this subject1. H 4e can cause an action to loop bac5 to itself to enerate the effect of a dynamically updatin display. Report erratum !repared e8clusively for Rida $l &ara'i Chapter 00 (as5 K% $dministrivia 4e have a happy customerDin a very short time we)ve jointly put toether a basic shoppin cart that she can start showin to customers. (here)s just one more chane that she)d li5e to see. Riht now, anyone can access the administrative functions. She)d li5e us to add a basic user administration system that would force you to lo in to et into the administration parts of the site. 4e)re happy to do that, as it ives us a chance to play with callbac5 hoo5s and filters, and it lets us tidy up the application somewhat. Chattin with our customer, it seems as if we don)t need a particularly sophisticated security system for our application. 4e just need to rec- oni'e a number of people based on user names and passwords. Once reconi'ed, these fol5s can use all of the administration functions. 00.0 Iteration K0% $ddin Isers #et)s start by creatin a simple database table to hold the user names and hashed passwords for our administrators. 0 Kile 3@ create table users / id int not null auto,increment, name varchar/0221 not null, hashed,password char/621 null, primary 5ey /id1 1C 4e)ll create the Rails model too. 0 Rather than store passwords in plain te8t, we)ll feed them throuh an S=$0 diest, resultin in a 0S2-bit hash. 4e chec5 a user)s password by diestin the value they ive us and comparin that hashed value with the one in the database. !repared e8clusively for Rida $l &ara'i I (>R$(IO; K0% $ ??I;J I S>RS 00: depotN ruby scriptOenerate model Iser e8ists appOmodelsO e8ists testOunitO e8ists testOfi8turesO create appOmodelsOuser.rb create testOunitOuser,test.rb create testOfi8turesOusers.yml ;ow we need some way to create the users in this table. In fact, it)s li5ely that we)ll be addin a number of functions related to users% loin, list, delete, add, and so on. #et)s 5eep thins tidy by puttin them into their own controller. $t this point, we could invo5e the Rails scaffoldin ener- ator that we used when we wor5 on product maintenance, but this time let)s do it by hand. A (hat way, we)ll et to try out some new techni*ues. So, we)ll enerate our controller / #oin 1 alon with a method for each of the actions we want. depotN ruby scriptOenerate controller #oin add,user loin loout Q delete,user list,users e8ists appOcontrollersO e8ists appOhelpersO create appOviewsOloin e8ists testOfunctionalO create appOcontrollersOloin,controller.rb create testOfunctionalOloin,controller,test.rb create appOhelpersOloin,helper.rb create appOviewsOloinOloin.rhtml create appOviewsOloinOadd,user.rhtml create appOviewsOloinOdelete,user.rhtml create appOviewsOloinOlist,users.rhtml 4e 5now how to create new rows in a database tableC we create an action, put a form into a view, and invo5e the model to save data away. &ut to ma5e this chapter just a tad more interestin, let)s create users usin a slihtly different style in the controller. In the automatically enerated scaffold code that we used to maintain the products table, the edit action set up a form to edit product data. 4hen that form was completed by the user, it was routed bac5 to a separate save action in the controller. (wo separate methods cooperated to et the job done. In contrast, our user creation code will use just one action, add,user /1. Inside this method we)ll detect whether we)re bein called to display the initial /empty1 form or whether we)re bein called to save away the data in a completed form. 4e)ll do this by loo5in at the =((! method of the A In fact, we probably wouldn)t use scaffolds at all. Pou can download Rails code enera- tors which will write user manaement code for you. Search the Rails wi5i / wi5i.rubyonrails.com 1 for loin enerator. (he Salted =ash version is the most secure from brute-force attac5s. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; K0% $ ??I;J I S>RS 0A2 incomin re*uest. If it has no associated data, it will come in as a J>( re*uest. If instead it contains form data, we)ll see a !OS(. Inside a Rails controller, the re*uest information is available in the attribute re*uest . 4e can chec5 the re*uest type usin the methods etW /1 and postW /1. =ere)s the code for the add,user /1 action in the file loin,controller.rb . /;ote that we added the admin layout to this new controllerDlet)s ma5e the screen layouts consistent across all administration functions.1 Kile 3A class #oinController - $pplicationController layout VadminV def add,user if re*uest.etW ]user . Iser.new else ]user . Iser.new/paramsX%userY1 if ]user.save redirect,to,inde8/VIser U`]user.nameb createdV1 end end end U . . . If the incomin re*uest is a J>(, the add,user /1 method 5nows that there is no e8istin form data, so it creates a new Iser object for the view to use. If the re*uest is not a J>(, the method assumes that !OS( data is present. It loads up a Iser object with data from the form and attempts to save it away. If the save is successful, it redirects to the inde8 paeC otherwise it displays its own view aain, allowin the user to correct any errors. (o et this action to do anythin useful, we)ll need to create a view for it. (his is the template add,user.rhtml in appOviewsOloin . ;ote that the form,ta needs no parameters, as it defaults to submittin the form bac5 to the action and controller that rendered the template. Kile 33 -Z ]pae,title . V$dd a IserV -ZN -Z. error,messaes,for + user + ZN -Z. form,ta ZN -tableN -trN -tdNIser name%-OtdN -tdN-Z. te8t,field/VuserV, VnameV1 ZN-OtdN -OtrN -trN -tdN!assword%-OtdN -tdN-Z. password,field/VuserV, VpasswordV1 ZN-OtdN -OtrN -trN -tdN-OtdN -tdN-input type.VsubmitV value.V $?? IS>R V ON-OtdN -OtrN -OtableN -Z. end,form,ta ZN Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; K0% $ ??I;J I S>RS 0A0 4hat)s less straihtforward is our user model. In the database, the user)s password is stored as a 62-character hashed strin, but on the form the user types it in plain te8t. (he user model needs to have a split personal- ity, maintainin the plain-te8t password when dealin with form data but switchin to deal with a hashed password when writin to the database. &ecause the Iser class is an $ctive Record model, it 5nows about the columns in the users tableDit will have a hashed,password attribute auto- matically. &ut there)s no plain-te8t password in the database, so we)ll use Ruby)s attr,accessor to create a readOwrite attribute in the model. attr,accessor W R pae 6@A Kile 36 class Iser - $ctiveRecord%%&ase attr,accessor %password 4e need to ensure that the hashed password ets set from the value in the plain-te8t attribute before the model data ets written to the database. 4e can use the hoo5 facility built into $ctive Record to do just that. $ctive Record defines a lare number of callbac5 hoo5s that are invo5ed at various points in the life of a model object. Callbac5s run, for e8ample, before a model is validated, before a row is saved, after a new row has been created, and so on. In our case, we can use the before and after creation callbac5s to manae the password. &efore the user row is saved, we use the before,create /1 hoo5 to ta5e a plain-te8t password and apply the S=$0 hash function to it, storin the result in the hashed,password attribute. (hat way, the hashed,password column in the database will be set to the hashed value of the plain-te8t password just before the model is written out. $fter the row is saved, we use the after,create /1 hoo5 to clear out the plain- te8t password field. (his is because the user object will eventually et stored in session data, and we don)t want these passwords to be lyin around on dis5 for fol5s to see. (here are a number of ways of definin hoo5 methods. =ere, we)ll simply define methods with the same name as the callbac5s / before,create /1 and after,create /11. #ater, on pae 0AS, we)ll see how we can do it declaratively. =ere)s the code for this password manipulation. Kile 36 re*uire VdiestOsha0V class Iser - $ctiveRecord%%&ase attr,accessor %password attr,accessible %name, %password def before,create self.hashed,password . Iser.hash,password/self.password1 end Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; K0% $ ??I;J I S>RS 0AA def after,create ]password . nil end private def self.hash,password/password1 ?iest%%S=$0.he8diest/password1 end end $dd a couple of validations, and the wor5 on the user model is done /for now1. Kile 36 class Iser - $ctiveRecord%%&ase attr,accessor %password attr,accessible %name, %password validates,uni*ueness,of %name validates,presence,of %name, %password (he add,user /1 method in the loin controller calls the redirect,to,inde8 /1 method. 4e)d previously defined this in the store controller on pae :0, so it isn)t accessible in the loin controller. (o ma5e the redirection method accessible across multiple controllers we need to move it out of the store controller and into the file application.rb in the appOcontrollers directory. (his file defines class $pplicationController , which is the parent of all the controller classes in our application. Methods defined here are available in all these controllers. Kile 30 class $pplicationController - $ctionController%%&ase model %cart model %line,item private def redirect,to,inde8/ms . nil1 flashX%noticeY . ms if ms redirect,to/%action .N + inde8 + 1 end end (hat)s it% we can now add users to our database. #et)s try it. ;aviate to http%OOlocalhost%7222OloinOadd,user , and you should see this stunnin e8am- ple of pae desin. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; KA% # OJJI;J I ; 0A7 4hen we hit the $dd Iser button, the application blows up, as we don)t yet have an inde8 action defined. &ut we can chec5 that the user data was created by loo5in in the database. depotN mys*l depot,development mys*lN select ^ from usersC T----T------T------------------------------------------T [ id [ name [ hashed,password [ T----T------T------------------------------------------T [ 0 [ dave [ e3e:fa0ba70ecd0aeG6f@3caaa6@6f7aSS7f23f6 [ T----T------T------------------------------------------T 0 row in set /2.22 sec1 00.A Iteration KA% #oin In 4hat does it mean to add loin support for administrators of our storeW H 4e need to provide a form that allows them to enter their user name and password. H Once they are loed in, we need to record the fact somehow for the rest of their session /or until they lo out1. H 4e need to restrict access to the administrative parts of the applica- tion, allowin only people who are loed in to administer the store. 4e)ll need a loin /1 action in the loin controller, and it will need to record somethin in session to say that an administrator is loed in. #et)s have it store the id of their Iser object usin the 5ey %user,id . (he loin code loo5s li5e this. Kile 3A def loin if re*uest.etW sessionX%user,idY . nil ]user . Iser.new else ]user . Iser.new/paramsX%userY1 loed,in,user . ]user.try,to,loin if loed,in,user sessionX%user,idY . loed,in,user.id redirect,to/%action .N Vinde8V1 else flashX%noticeY . VInvalid userOpassword combinationV end end end (his uses the same tric5 that we used with the add,user /1 method, han- dlin both the initial re*uest and the response in the same method. On the initial J>( we allocate a new Iser object to provide default data to the form. 4e also clear out the user part of the session dataC when you)ve reached the loin action, you)re loed out until you successfully lo in. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; KA% # OJJI;J I ; 0A6 If the loin action receives !OS( data, it e8tracts it into a Iser object. It invo5es that object)s try,to,loin /1 method. (his returns a fresh Iser object correspondin to the user)s row in the database, but only if the name and hashed password match. (he implementation, in the model file user.rb , is straihtforward. Kile 36 def self.loin/name, password1 hashed,password . hash,password/password [[ VV1 find/%first, %conditions .N XVname . W and hashed,password . WV, name, hashed,passwordY1 end def try,to,loin Iser.loin/self.name, self.password1 end 4e also need a loin view, loin.rhtml . (his is pretty much identical to the add,user view, so let)s not clutter up the boo5 by showin it here. /Remember, a complete listin of the ?epot application starts on pae 6GS.1 Kinally, it)s about time to add the inde8 pae, the first thin that admin- istrators see when they lo in. #et)s ma5e it usefulDwe)ll have it display the total number of orders in our store, alon with the number pendin shippin. (he view is in the file inde8.rhtml in the directory appOviewsOloin . Kile 3S -Z ]pae,title . V$dminister your StoreV -ZN -h0N?epot Store Status-Oh0N -pN (otal orders in system% -Z. ]total,orders ZN -OpN -pN Orders pendin shippin% -Z. ]pendin,orders ZN -OpN (he inde8 /1 action sets up the statistics. Kile 3A def inde8 ]total,orders . Order.count ]pendin,orders . Order.count,pendin end $nd we need to add a class method to the Order model to return the count of pendin orders. Kile 37 def self.count,pendin count/Vshipped,at is nullV1 end ;ow we can e8perience the joy of loin in as an administrator. Report erratum !repared e8clusively for Rida $l &ara'i I (>R$(IO; K7% # IMI(I;J $ CC>SS 0A3 4e show our customer where we are, but she points out that we still haven)t controlled access to the administrative paes /which was, after all, the point of this e8ercise1. 00.7 Iteration K7% #imitin $ccess 4e want to prevent people without an administrative loin from accessin our site)s admin paes. It turns out that it)s easy to implement usin the Rails filter facility. Rails filters allow you to intercept calls to action methods, addin your own processin before they are invo5ed, after they return, or both. In our case, we)ll use a before filter to intercept all calls to the actions in our admin controller. (he interceptor can chec5 session X %user,id Y. If set, the application 5nows an administrator is loed in and the call can proceed. If it)s not set, the interceptor can issue a redirect, in this case to our loin pae. 4here should we put this methodW It could sit directly in the admin controller, but, for reasons that will become apparent shortly, let)s put it instead in the $pplicationController , the parent class of all our controllers. (his is in the file application.rb in the directory appOcontrollers . Kile 3: def authori'e unless sessionX%user,idY flashX%noticeY . V!lease lo inV redirect,to/%controller .N VloinV, %action .N VloinV1 end end (his authori'ation method can be invo5ed before any actions in the admin- istration controller by addin just one line. Report erratum