You are on page 1of 85

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

You might also like