Вы находитесь на странице: 1из 448

82

004.738.5

., ., .

82

Rails 4. -. .: , 2014. 448 .: .


( ).

ISBN 978-5-496-00898-3

32.988.02-018

Agile web development with Rails,


Apache Software Foundation Atom, Programming Ruby, Rails.
Rails , , . , , Rails , ,
Web 2.0.
, Ruby on Rails, , -.
Ruby Rails.
- , Rails.
Rails: ,
. Rails 4 Ruby 1.9 2.0.

32.988.02-018
004.738.5

Pragmatic Bookshelf. .

.
, , ,
. , ,

, .
ISBN 978-1937785567 (.)
ISBN 978-5-496-00898-3

2011 P
 ragmatic Bookshelf, LLC.
 , 2014
 , , 2014


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Rails . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

14
15
16
18

I. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1. Rails . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.1. Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.2. Mac OS X . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.3. Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
1.4. Rails . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
1.5. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
1.6. Rails . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.1. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.2. , Rails! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
2.3. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3. Rails- . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.1. , . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.2. Rails . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.3. Action Pack: . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4. Ruby . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
4.1. Ruby - . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
4.2. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.3. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
4.4. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.5. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

6
4.6. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
4.7. , Ruby . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69

II. . . . . . . . . . . . . . . . . . . . . . . . . . 71
5. - . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
5.1. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
5.2. Depot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
5.3. . . . . . . . . . . . . . . . . . . . . . . . . . . 77
6. : . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
6.1. A1: . . . . . . . . . . . . . . . . . . . . . 79
6.2. 2: . . . . . . . . . . . . . . . . . 87
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
7. :
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
7.1. 1: . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
7.2. 2: . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
8. : . . . . . . . . . . . . . . . . . . . 110
8.1. 1: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
8.2. 2: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
8.3. 3: . . . . . 119
8.4. 4: . . . . . . . . . . . . . . 120
8.5. 5: . . . . . . . . . . . . . . . . . . . . . . 122
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
9. : . . . . . . . . . . . . . . . . . . . . 126
9.1. 1: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
9.2. 2: . . . . . . . . . . . . . . . . . . . . . . . . . . 127
9.3. 3: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
10. : . . . . . . . . . . . . . . . . . . 137
10.1. 1: . . . . . . . . . . . . . . . . . 137
10.2. 2: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
10.3. 3: . . . . . . . . . . . . . . . . . . . . . . . . . 147
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
11. : AJAX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
11.1. 1: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
11.2. 2: AJAX- . . . . . . . . . . . 159

11.3. 3: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
11.4. 4: . . . . . . . . . . . 166
11.5. 5: . . . . 169
11.6. , AJAX . . . . . . . 171
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
12. : . . . . . . . . . . . . . . . . . . . . . . . . . 176
12.1. 1: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
12.2. 2: Atom- . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
13. : . . . . . . . . . . . . . . . . . . . . 194
13.1. 1: . . . . . . 194
13.2. 32: . . . . . . . . . . . . . . . . . . 202
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
14. : . . . . . . . . . . . . . . . 208
14.1. 1: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
14.2. 2: . . . . . . . . . . . . . . . . . . . . . . . . . 214
14.3. 3: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
14.4. 4:
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
15. : . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
15.1. 1: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
15.2. 2: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
15.3. 3: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
15.4. 4: . . . . . . . . . . . . . . . . . 245
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
16. : . . . . . . . . . . . . . . . . . . 249
16.1. 1: Phusion Passenger
MySQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
16.2. 2: Capistrano . . . . . . . . . 258
16.3. 3: . . . . . . . . . . . . . . 264
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
17. Depot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
17.1. Rails . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
17.2. . . . . . . . . . . . . . . . . . . . . . . . . . . 272

III. Rails . . . . . . . . . . . . . . . . . . . 275


18. Rails . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276
18.1. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276
18.2. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288
19. Active Record . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
19.1. . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
19.2.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294
19.3. , , , (CRUD Create, Read,
Update,Delete) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298
19.4. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
19.5. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
20. Action Dispatch Action Controller . . . . . . . . . . . . . . . . . . . . . . . 324
20.1. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325
20.2. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334
20.3. ,
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355
21. Action View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356
21.1. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357
21.2. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358
21.3. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 361
21.4. Rails- . . . . . . . . . . . . . . . . . . . . 363
21.5. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367
21.6.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382
22. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
22.1. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384
22.2. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386
22.3. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 391
22.4. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395
22.5. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398
22.6. . . . . . . . . . . . . . . . . . . . 399
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 400

23. , . . . . . . . . . . . . . . . . . . . 401
23.1. , Active Record . . . . . . . . . . . . 402
23.2. , Active Support . . . . . . . . . . . . 403
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407
24. Rails . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 408
24.1. XML Builder . . . . . . . . . . . . . . . . . . . . . . . . . . 409
24.2. HTML ERB . . . . . . . . . . . . . . . . . . . . . . . . . . . 410
24.3. Bundler . . . . . . . . . . . . . . . . . . 412
24.4. - Rack . . . . . . . . . . . . . . . . . . 415
24.5. Rake . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418
24.6. Rails- . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423
25. Rails . . . . . . . . . . . . . . . . . . . . . . . . . . . 424
25.1. Active Merchant . . . . . . . . . . . 424
25.2. Haml . . . . . . . . . . . . . . . . . . . . . 426
25.3 429
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431
25.4. RailsPlugins.org . . . . . . . . . 432
26. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 436


Rails , . Depot ,
. ,
, .
Ruby Rails.
:
(Jeremy Anderson), Andrea Barisone ( ), (Ken Coar), (Jeff Cohen), (Joel
Clermont), (Geoff Drake), Jeremy Frens ( ),
(Pavan Gorakavi), (Michael Jurewitz), (Mickel Lindsaar), Nigel Lowry ( ), Stephen Orr (
), Aaron Patterson ( ), (Paul Rayner), (Martin Reuvers), (Doug Rhoten), (Gary
Sherman), Tibor Simic ( ), Gianluigi Spagnuolo (
), (Davanum Srinivas), Charley Stran ( ), Federico Tomassetti ( ),
(Stefan Turalski) (Jos Valim).
, -:
PDF-, .
1000
.
,
. ,
-
, ,
:
. (Manuel E. Vidaurre Arenas), (Seth
Arnold), (Will Bowlin), (Andy Brice), (Jason Catena), (Victor Marius Costan),
(David Hadley), (Jason Holloway),

11

(David Kapp), Trung LE, (Kristian Riiber Mandrup),


mltsy, (Steve Nicholson), (Jim Puls),
(Johnathan Ritzi), Leonel S, (Kim Shrier), (Don
Smith), (Joe Straitiff) (Martin Zoller).
,
Rails, , ,
, ,

Rails. :
Rafael Frana ( , rafaelfranca), Guillermo Iguaran (
, guilleiguaran), (Jeremy Kemper, bitsweat),
(Yehuda Katz, wycats), (Michael Koziarski), Santiago
Pastorino ( , spastorino), Aaron Patterson ( ) (Jos Valim, josevalim).
(Sam Ruby)
mailto: rubys@intertwingly.net


Ruby on Rails , , -. , , Rails
,
, , , Web2.0.
?
Rails . , -. , , ,
Java, PHP .NET,
. Rails,
.
. , -
-.
, .
Rails , -.
, Rails-
-- (Model-View-Controller, MVC). Java- , Tapestry Struts,
MVC. Rails MVC :
Rails ,
,
.
. Rails
. Rails- .
- Rails , .
, .

13

Rails- Ruby -
. Ruby
.
( )
.
Rails Ruby, , . ,
. ,
.
. .
,
- .
class Project < ActiveRecord::Base
belongs_to :portfolio
has_one :project_manager
has_many :milestones
has_many :deliverables, through: milestones
validates :name, :description, presence: true
validates :non_disclosure_agreement, acceptance: true
validates :short_name, uniqueness: true
end

Rails : DRY . DRY dont repeat yourself, :


.
, Rails
Ruby. Rails- , ,
, , MVC-, .
, -,
,
, .
. , Rails ,
, .
, Rails-,
, -, Java XML. , Rails .
, Rails, .
Rails - : ,
. Rails , AJAX RESTful,
Rails. ( AJAX
REST, , , .)
. , Rails

14
(
, ).
Rails . ,

, .
Rails-
- .
Rails - , . . ,
Rails - ( 45), .
.

Rails
- Rails 4. , , , Rails-.
. ,
Rails.
,
(Agile Manifesto), :1
;
;
;
.
Rails .
, ,
. Ruby-.
, , ,
, . ,
.
Rails . HTML-
.
Rails - .
Rails- 500- .
, . ,
, , ,
http://agilemanifesto.org/. 17 .

15

. ,
.
, , .
, Rails . ,
Rails- ,
, , , ,
. :
, ?.
. , Rails DRY, ,
Rails- ,
. Rails- Ruby,
, . ,
,
,
, .
.
, Rails, ,
. ,
-,
. ,
, , , Rails,
.
, Rails: , ,
!


,
-. , Rails (, , Ruby),
, ,
Rails.
HTML, Cascading Style Sheets (CSS)
JavaScript, ,
-. , ,
, , .

16


. Ruby
Rails, Ruby Rails,
. ( -) ,
Rails.
Rails ( , ..). ,
,
.
, , . , ,
( tar- zip-1). Rails 3.0, Rails 3.1, Rails 3.2 Rails 4.0. Rails4.0, rails40.
README-FIRST.

, ,
, . Mac OS X
Linux touch
. , Rails-.
, Rails,
Rails. Rails,
. ,
Rails, ,
. ,
, Rails .

.

,
, .
, ,
( , ).

http://pragprog.com/titles/rails4/source_code

17
rails40/work/demo1/app/controllers/say_controller.rb

class SayController < ApplicationController


def hello
end

end

def goodbye
end

. ,
, , .
.

. (David Heinemeier Hansson)


Rails , .. Rails, ,
.


,
, .

Rails. ,
.
,
.
API-.
Rails, , , ,
. Rails,
RubyGems ( ),
gem- ( gem_server), API- Rails,
http://localhost:8808.
18, .
, , Rails , ,
, , , , .
.10.3. ,
10.2 2: ,
.

18
- ,
-. , 1,
, 2,
, wiki3, ,
.
, wiki , ,
, .
, ! Ruby Rails .


, ,
comp@piter.com ( , ).
!
, , http://
pragprog.com/titles/rails4/source_code.
- http://www.piter.com .

1
2
3

http://forums.pragprog.com/forums/148
http://www.pragprog.com/titles/rails4/errata
http://www.pragprog.com/wikis/wiki/RailsPlayTime

Rails

:
Ruby, RubyGems, SQLite3 Rails;
.

Ruby Rails.
.
Rails , :
Ruby. Rails Ruby,
Ruby. Rails 4.0
Ruby 2.0.0, 1.9.3. Ruby 1.8.7
Ruby 1.9.2 ;
Ruby on Rails. Rails
4.0 ( Rails 4.0.0);
JavaScript. Microsoft Windows, Mac OS X, Rails
, .
JavaScript;
, ;
. SQLite 3, MySQL5.5.
, , ,
( , ).
,
- ( ),
, Rails . , .

1 Rails 21

?
...

1.1. Windows
Rails Windows RailsInstaller1.
RailsInstaller 2.2.1, Ruby 1.9.3 Rails 3.2.
, ,
Rails 4.0.0 Ruby 2.0, RailsInstaller
2.1.
.
Run, Next. I accept
the License (, ,
) NextInstallFinish.

.
Git. ssh- .
.
Windows 8 (Start screen)
cmd Enter. Windows, Windows8, (Start) ...
(Run...), cmd OK.
Windows 8 node.js2. ,
, ,
%PATH%, . , , node -v.
RailsInstaller Ruby 1.9.3 , Ruby .
, , 1.4, Rails,
, Rails ,
. .

1.2. Mac OS X
Mac OS X Ruby 1.8.7, Ruby, Rails 4.0.
http://railsinstaller.org/
http://nodejs.org/download/

1
2

22 I
RailsInstaller,
Ruby1.9.3.
RVM ,
Ruby 2.0.0. Ruby 2.0 Rails Ruby 1.9.3,
.
, .
, Utilities ()
Terminal. ,
,
Rails.

RailsInstaller
RailsInstaller Download
the Kit ( ).
, ,
. ,
Control. Open ().
, (app store).
( , git), .
Terminal :
$ ruby -v

:
ruby 1.9.3p392 (2013-02-22 revision 39386) [x86_64-darwin11.4.0]

Rails , ,
:
$ gem install rails --version 4.0.0 --no-ri --no-rdoc

!
Windows 1.4, Rails.

RVM
( 2013) Xcode(Command Line Tools for Xcode)
(OS X Lion OS X Mountain Lion), XCode
Downloads ().
Terminal , RVM:

1 Rails 23
$ curl -L https://get.rvm.io | bash -s stable

,
.
,
Ruby:
$ rvm install 2.0.0 --autolibs=enable

, . ,
Rails:
$ rvm use 2.0.0
$ gem install rails --version 4.0.0 --no-ri --no-rdoc

rvm use, . rvm use . use , rvm 2.0.0.


Ruby
, :
$ rvm --default 1.9.2

:
$ rails -v

, ,
- rvm Troubleshooting Your Install1.
OS X 1.4,
Rails, Windows. .

1.3. Linux
, : apt-get, dpkg, portage, rpm, rug, synaptic, up2date yum.
. Ubuntu 13.04 (Raring Ringtail);


git curl, .
$ sudo apt-get install apache2 curl git libmysqlclient-dev mysql-server nodejs

https://rvm.beginrescueend.com/rvm/install

24 I
MySQL. , .
, , 16, MySQL
.
Rails Rails 4.0 Ruby 2.0,
Ruby1.9.3,
.
Ubuntu 12.04 Ruby 1.9.3 Rails 4.0
:
$ sudo apt-get install ruby1.9.3
$ sudo gem install rails --version 4.0.0 --no-ri --no-rdoc

, 1.4, Rails.
Ruby ,
, Ruby.
RVM. RVM -
RVM1. .
RVM :
$ curl -L https://get.rvm.io | bash -s stable

Gnome Terminal Profile Preference Run command as login shell. Integrating


RVM with gnome-terminal2.
Terminal
.
.bash_profile.
, ,
:
$ rvm requirements --autolibs=enable

,
Ruby.
$ rvm install 2.0.0

, . , Rails:
$ rvm use 2.0.0
$ gem install rails --version 4.0.0 --no-ri --no-rdoc

https://rvm.io/rvm/install
https://rvm.io/integration/gnome-terminal/

1
2

1 Rails 25

rvm use,
. rvm use
. use , rvm 2.0.0.
Ruby , :
$ rvm --default 2.0.0

:
$ rails -v

, ,
- rvm Troubleshooting Your Install1.
, Windows, Mac OS X Linux,
.

1.4. Rails
Rails,
. .
, Rails
. , , ,
, Rails.
- ,
. gem
Rails:
$ gem list --local rails

rails --version , Rails . 4.0.0.


,
rails Rails, . :
$ rails _4.0.0_ --version

, Rails,
,
, .
Gemfile,
,
https://rvm.beginrescueend.com/rvm/install

26 I
bundle install. 24, ,
Bundler.

1.5.
Rails-
, -. .


.
(GUI),
Rails- , ,
- . . , ,

.
Unix Bash zsh ( tab-).
Tab ,
.


( Git).
Rails- Git ,
.
.
IDE?
Ruby Rails , , C# Java,
IDE ( ).
, 100
IDE, . , ,
1000
.
Ruby Rails IDE (
). Rails-
. , . ,

1 Rails 27
, IDE ,
: , ,
.
Ruby . , TextMate, 90% , IDE,
.
, IDE
.

Rails- , continuous integration


system (CI). - , CI-
. .
CI-
. ,
.

Rails-, .
, . , ,
Emacs, Filladapt ,
XML-.
, Vim. , Emacs, Vim
Rails-. , , , Rails
.
Ruby HTML.
.erb ( Rails,
Ruby HTML-).

Ruby. ;

.
. (
TextMate .)
Ruby Rails. , , IDE
, ,
.

28 I
. , Rails : , ,
, .
, :
,
, ,
, ,
. , ,
FileOpen,
.
,
,
( ), , , , ,
.
. Rails
. ,
.
- ,
-
- . ,
- , ,
.
TextMate1, Ruby on Rails
Mac OS X.
Sublime Text2, - ,
TextMate.
Aptana Studio 33. Rails-
Eclipse. Windows, Mac OS X Linux.
RadRails, 2006 Eclipse,
2007 Aptana.
jEdit4. Ruby. .


3

4

1
2

http://macromates.com/
http://www.sublimetext.com/
http://www.aptana.com/products/studio3
http://www.jedit.org/

1 Rails 29

Komodo1. IDE, ActiveState , Ruby.


RubyMine2. IDE- Ruby,
. Windows, Mac OS X Linux.
NetBeans Ruby and Rails3, NetBeans IDE.
,
, .
, .


,
Rails, , .
, .

. ,
, . tail -f.
, ,
- , ,
.
Rails API,
. gem_server -, Rails-. , ,
, , , Rails-
. ,
http://api.rubyonrails.org, Rails .

1.6. Rails
SQLite 3
( 3.7.4 ). , , , SQLite 3.
- ,
http://www.activestate.com/komodo-ide
http://www.jetbrains.com/ruby/features/index.html
3
http://plugins.netbeans.org/plugin/38549
1
2

30 I
. ,
- SQL- , Rails

SQL-.
, SQLite 3, Rails
DB2, MySQL, Oracle, Postgres, Firebird SQL Server.
, SQLite 3, , ,
Rails
. , .
C
.
, - .
, .
RAILS API
Rails API.
:
rails_apps> rails new dummy_app
rails_apps> cd dummy_app
dummy_app> rake doc:rails
. , Rails API , doc/api.
, dummy_app.
Rails API doc/api/index.
html.


, , . Windows ,
Visual C++. Linux
gcc (, , ).
OS X ( ,
).
Ruby. Ruby
, ,
Ruby .
, Ruby /usr/bin,
which ruby.
-, .
DB2

http://raa.ruby-lang.org/project/ruby-db2 http://rubyforge.org/projects/rubyibm

Firebird

http://rubyforge.org/projects/fireruby/

1 Rails 31

MySQL

http://www.tmtm.org/en/mysql/ruby/

Oracle

http://rubyforge.org/projects/ruby-oci8

Postgres

https://bitbucket.org/ged/ruby-pg/wiki/Home

Server

https://github.com/rails-sqlserver SQL

SQLite

https://github.com/luislavena/sqlite3-ruby

MySQL SQLite RubyGems (, mysql2 sqlite3).


;; ( ) Ruby.
;; ( ) Rails.
;; ( ) SQLite3 MySQL.
;; .
, Rails, .
,
.

;
;
;
;
;
.

, ,
Rails ,
Rails.

2.1.
Rails
, rails, Rails-.
- ?

? , , . , Rails-
Ruby. Rails, , ,
. Rails

2 33

, . ( 18.1 ) ,
,
. rails Rails-.
Rails-
,
.
work. , rails demo. :
, ,
.

, 1.4 Rails, ,
Rails , .

rubys> cd work
work> rails new demo
create
create README.rdoc
create Rakefile
create config.ru
:
:
:
create vendor/assets/stylesheets
create vendor/assets/stylesheets/.keep
run bundle install
Fetching gem metadata from https://rubygems.org/...........
:
:
:
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.
work>

demo. ( ls Unix dir Windows).


:
work> cd demo
demo> dir /w
[.]
config.ru
[log]
[tmp]

[..]
[db]
[public]
[vendor]

[app]
Gemfile
Rakefile.rdoc

[bin]
Gemfile.lock
README

[config]
[lib]
[test]

( , ) ,
.
: app, .

34 I
, -, Rails.
demo:
demo> rails server
=> Booting WEBrick
=> Rails 4.0.0 application starting on http://0.0.0.0:3000
=> Run 'rails server -h' for more startup options
=> Ctrl-C to shutdown server
[2013-04-18 20:22:16] INFO WEBrick 1.3.1
[2013-04-18 20:22:16] INFO ruby 2.0.0 (2013-02-24) [x86_64-linux]
[2013-04-18 20:22:16] INFO WEBrick::HTTPServer#start: pid=25170 port=3000

- , , . WEBrick -, Ruby, Ruby .


- ( Rails ),
rails server WEBrick. Rails WEBrick, rails :
demo> rails server webrick

, - 3000. 0.0.0.0 ,
WEBrick .
OSX
(127.0.0.1 ::1) .
, - URL- http://localhost:3000. .2.1.
, , , , .
. ,
,
.
, ,
, Ctrl+C, WEBrick ( ,
).
, ,
. .

2.2. , Rails!
, Hello, World! ,
. ,
.

2 35

3 Rails- , Rails
(ModelViewController). Rails
, , .
,
. Rails ,
, .
Hello, World!,
. , . .
,
.

. 2.1. Rails-

, Rails- rails,

36 I
. rails generate. , say
, demo, ,
,
:
demo> rails generate controller Say hello goodbye
create app/controllers/say_controller.rb
route get "say/goodbye"
route get "say/hello"
invoke erb
create
app/views/say
create
app/views/say/hello.html.erb
create
app/views/say/goodbye.html.erb
invoke test_unit
create
test/controllers/say_controller_test.rb
invoke helper
create
app/helpers/say_helper.rb
invoke
test_unit
create
test/unit/helpers/say_helper_test.rb
invoke assets
invoke
coffee
create
app/assets/javascripts/say.js.coffee
invoke
scss
create
app/assets/stylesheets/say.css.scss

rails generate ,
Ruby- .
, .html.erb.
. app/
controllers/say_controller.rb. :
rails40/demo1/app/controllers/say_controller.rb
class SayController < ApplicationController
def hello
end

end

def goodbye
end

, ? SayController ,
ApplicationController,
. ? ,
hello() goodbye().
, , , Rails .

2 37

Rails URL-
Rails-, , ,
-, URL-.
URL-, ,
.
. URL-
http://localhost:3000/say/hello. , ,
.2.2.

. 2.2. ,


,
URL- , , Rails
, Rails , .
. :
,
.
. ,
say, , app/views/say.
Rails , ,
. , hello.html.erb app/views/say. ( .html.erb?
.)
HTML:
rails40/demo1/app/views/say/hello.html.erb
<h1> Rails!</h1>

hello.html.erb .
(.2.3).

38 I

. 2.3.

Rails-. , .
Rails:
app/controllers, , app/views.
.2.4.

. 2.4.


Rails-
. .

2 39

: . .
-, ? -,
?


Rails . , ,
Ruby . hello.html.erb .html.erb Rails
, ERB.
ERB , Rails, .erb
. Rails HTML, - .
. ,
<%= %>, Ruby-, . ,
<%=%>. ,
hello.html.erb , :
rails40/demo2/app/views/say/hello.html.erb
<h1> Rails!</h1>
<p>

<%= Time.now %>


</p>

,
Ruby (.2.5).

. 2.5. Ruby

: (Refresh)
.
.

40 I



. ,
. : ?
, Ruby- Time.now()
hello.html.erb . , . . -
-. ,
. ,
@time:
rails40/demo3/app/controllers/say_controller.rb
class SayController < ApplicationController
def hello

@time = Time.now
end

end

def goodbye
end

.html.erb , :
rails40/demo3/app/views/say/hello.html.erb
<h1> Rails!</h1>
<p>

<%= @time %>


</p>

, .

, , -
. - , . .
,
. ?
, Rails. ( )
. ,
. .
URL- , ,

2 41
. ,
. (. 16 :
).


? . ,
, , . , ,
. ,
, .
, ,,
. , ,
,
. ,
.
.

,
.
1. , URL- http://localhost:3000/say/hello.
2. Rails , .
say , Rails
Ruby- SayController (
app/controllers/say_controller.rb).
3. URL, hello ,
. Rails .
, , Time,
, @time.
4. Rails , .
app/views, ,
(say), ,
(hello.html.erb).
5. Rails ERB, Ruby- ,
.
6. , Rails .

42 I
. Rails
( ).
, Rails. , Rails-
,
URL- ,
, , Rails , ,
,
.

2.3.
-, , .
,
-.
.

( , ).
. -,
, - ,
, .
goodbye, app/views/say.
goodbye.html.erb, ,
.
rails40/demo4/app/views/say/goodbye.html.erb
<h1> !</h1>
<p>
, .
</p>

,
, URL http://localhost:3000/say/goodbye.
, .2.6.
. hello
, goodbye, . , , ,
.
, Rails
URL-
. URL .

2 43

. 2.6.

hello.html.erb :
...
<p>

<a href="/say/goodbye"> </a>!

</p>
...

goodbye.html.erb :
...
<p>

<a href="/say/hello"></a>!

</p>
...

, , .
-, URL .
Rails URL-,
Rails .
, . Rails
,
.
link_to(), . ( link_to()
, .)
link_to() hello.html.erb :
rails40/demo5/app/views/say/hello.html.erb
<h1> Rails!</h1>
<p>
<%= @time %>
</p>
<p>

<%= link_to " ", say_goodbye_path %>!


</p>

44 I
link_to() ERB-
<%=%>. URL-,
goodbye(). link_to() ,
, Rails goodbye().
. :
link_to " ", say_goodbye_path

, link_to() . ( Rails , , .)
, Java,
. ,
.
say_goodbye_path ,
Rails .
/say/goodbye. , Rails
,
.
.
hello, goodbye (.2.7).

. 2.7.

goodbye.html.erb,
hello:
rails40/demo5/app/views/say/goodbye.html.erb
<h1> !</h1>
<p>
, .
</p>
<p>

<%= link_to "", say_hello_path %> .


</p>

2 45

,
Rails.
.


, :
;; Rails-
;
;; ;
;; .
, . , .


:
: <%= 1+2 %>
: <%= "cow" + "boy" %>
: <%= 1.hour.from_now %>
Ruby-
:
@files = Dir.glob('*')

, ,
.
:
:
<% for file in @files %>
file name is: <%= file %>
<% end %>

<ul>.
( http://www.pragprog.com/wikis/wiki/RailsPlay
Time.)

46 I


, , , , ,
. 6, : , ,
,
3000.
, Ctrl+C , .
Microsoft Windows
Ctrl+Pause/Break.
Rails.


Rails-

:
;
;
.

Rails ,
-.
, , .
, .

3.1. ,
1979 (Trygve Reenskaug) .
: , .
.
, . , .
; -, . ,
20, . . - , ,

48 I
. ,
.
,
. , - .
,
. , . ,
. ,
, . -
,
, ,
.
.
( ),
.
, , MVC (ModelViewController ).
.3.1.

. 3.1.

MVC GUI-,
,
, , , .
, . MVC :
, . Rails (scaffold).

3 Rails- 49

Ruby on Rails MVC. Rails


,
, Rails
. Rails ,
, , ,
- ,
.
Rails.
Rails- ,
,
.
-
( Rails ). , .
,
.
Rails , .3.2. ,
.
http://localhost:3000/line_items?product_id=2, line_items
, 2 .

. 3.2. Rails MVC

, ,
. (/line_items?product_id=2)
( POST; GET, PUT, PATCH DELETE).
Rails , line_items, ,
product_id . POST-

50 I
create(). ,
create() LineItemsController
( 18.2).
create() .
( , ). 2. . (,
-?
, , , .)
, , . , , cart . Rails
,
.
, -, MVC. , ,
, .
.
MVC
, , Ruby
on Rails? : Rails ,
, ,
. ,
.

3.2. Rails
, - , .
, .
, , ,
.
SQL1,
, ,
. , ,
- . ,
. , ,
SQL, Structured Query Language, ,
.

3 Rails- 51

-
. .
- .
,
Rails.

-
ORM.
orders (), Order.
Order.
.
Order ,
..
Rails-, , ,
. , . , Order. Ruby :
order = Order.find(1)
puts " #{order.customer_id}, =$#{order.amount}"

:
Order.where(name: 'dave').each do |order|
puts order.amount
end

, , , , , . ,
save(), :
Order.where(name: 'dave').each do |order|
order.pay_type = " "
order.save
end

, ORM , ,
. ,
.

ORM- . , ORM-,
, XML.

52 I

Active Record
Active Record ORM-, Rails.
ORM-: , ,
. ORM- .
, Active Record , .
, , Active Record :
require 'active_record'
class Order < ActiveRecord::Base
end
order = Order.find(1)
order.pay_type = " "
order.save

, 1,
pay_type Order. ( , .) Active Record
, .
Active Record . 5, , , , Active Record
Rails. -
, -, Active Record
. Active Record
, , Rails
.
Active Record MVC-,
Rails.

3.3. Action Pack:

, MVC
. , , . -
Rails
Action Pack.
, , Action Pack

3 Rails- 53

. , Rails ,
.


Rails , ,
.
HTML-, - .
,
.
Rails . , c
Ruby Embedded Ruby (ERb), Ruby , , -,
PHP JSP. , MVC. , ,
. , ,
. . (HTML-
25.2 HTML ERb.)
ERb JavaScript, .
AJAX-.
11.2.
Rails , XML Builder,
XML-, Ruby, XML . xml.
builder , 24.1.

, !
Rails . , .
Rails , .
Rails-.

.
.
URL.

54 I
,
.
,
.
,
.
2.2, ,
8.1 1: .
Rails . , , Ruby,
.

Ruby

: ;
: , , ;
: if, while, , ;
: ;
YAML ;
, .

, Rails, Ruby.
, Java, JavaScript, PHP, Perl Python,
Ruby .
Ruby. , (
, Ruby 1+2*3==7).
Ruby , .
Programming Ruby1.
Ruby, ,
, Ruby ,
Programming Ruby (
PickAxe). Ruby!

David Thomas, Chad Fowler, and Andrew Hunt. Programming Ruby: The Pragmatic
Programmers Guide. The Pragmatic Bookshelf, Raleigh, NC and Dallas, TX, Third,
2008.

56 I

4.1. Ruby
, Ruby, ,
.
- . , , .
-
. Ruby . , . (, ) ,
(,
). 4.4.
, , .
new().
LineItem :
line_item_one = LineItem.new
line_item_one.quantity = 1
line_item_one.sku = "AUTO_B_00"

. .
, . :
"dave".length
line_item_one.quantity()
cart.add_line_item(next_purchase)
submit_tag " "

, , .
Rails ,
, , , .
, Ruby, ,
. Ruby , , .

, Ruby
, : order,

4 Ruby 57

line_item xr2000 . at (@), @quantity @product_id.


Ruby
( line_item
, lineItem).
.
, . Object,
PurchaseOrder LineItem.
Ruby . ,

. :
redirect_to :action => "edit", :id => params[:id]

, , .
:action, :line_items :id.
, . - , , :id
- id.
,
.

, .
:
def say_goodnight(name)
result = ' , ' + name
return result
end
# ...
puts say_goodnight('-') # => ' , -'
puts say_goodnight(' ') # => ' , '

.
puts(), ,
( ).

. Ruby # . ( -
Ruby ).
(,
) Ruby .
end. return

58 I
, ,
.

4.2.
, Ruby ,
, . .

, , . ,
. ,
Ruby . Ruby
. ,
, , .
Ruby
. -, ,
, .
\n,
. ,
, \n .
-, , , Ruby .
#{} . ,
:
def say_goodnight(name)
" , #{name.capitalize}"
end
puts say_goodnight('pa')

Ruby ,
name . #{}
. capitalize(),
, .
, . Ruby
.

4 Ruby 59


Ruby . , .
, .
. ,
.
: , , ,
.
.
,
, . Ruby .
a = [ 1, 'cat', 3.14 ]
a[0]
a[2] = nil

#
#
#
#


(1)

: [ 1, 'cat', nil ]

, nil. nil ( null) .


Ruby -: nil , ,
.
<<().
:
ages = []
for person in @people
ages << person.age
end

Ruby :
a = [ '' , '' , '' , '' , '' ]
# :
a = %w{ }

Ruby . -
.
: , .
, .
inst_section = {
:
:
:
:

=>
=>
=>
=>

' ' ,
' ' ,
' ' ,
' ' ,

60 I

:
:

=> ' ' ,


=> ' '

, =>, , ,
, .
:.
,
, .. ,
Rails .
Rails ,
,
.
, , Ruby 1.9, ,
:
inst_section = {
:
:
:
:
:
:
}

' ' ,
' ' ,
' ' ,
' ' ,
' ' ,
' '

, ?
. . ,
, ,
.

, :
inst_section[:]
inst_section[:]
inst_section[:]

#=> ' '


#=> ' '
#=> nil

, , , nil. ,
nil false.
. Ruby , , . Rails .
,
redirect_to. , , , , Ruby
:
redirect_to :action => 'show' , :id => product.id

, , .

4 Ruby 61



. Ruby //
%r{}.
, /Perl|Python/, , , Perl Python.

, (|).
, , , , Perl,
Python. , , , :
/P(erl|ython)/.
=~:
if line =~ /P(erl|ython)/
puts ", "
end

. /ab+c/
, a, b, , , c.
, /ab*c/,
a, b
c.
; \d, ,
\s, , \w, - () .
Ruby ,
.
PickAxe,
.
, .

4.3.
. Ruby , ,
.


Ruby ,
if while. , Java, C Perl, ,

62 I
.
Ruby end:
if count > 10
puts " "
elsif tries == 3
puts " "
else
puts " "
end

while end:
while weight < 100 and num_pallets <= 30
pallet = next_pallet()
weight += pallet.weight
num_pallets += 1
end

Ruby : unless if,


. until
while, ,
, true.
if while , Ruby
, . , :
puts " , " if radiation > 3000
distance = distance * 1.2 while distance < 100

if Ruby- , ,
Ruby, . .


,
do...end. , ,
do/end :
{ puts "" }

do

###
#
#
###

end

club.enroll(person)
person.socialize

,
( ). ,

4 Ruby 63

, . , ,
puts "", greet():
greet { puts "" }

, :
verbose_greet("", " ") { puts "" }

Ruby- yield,
. yield
, , ,
yield. yield,
. ,
, (|).
Ruby- . , - , :
animals = %w{ } #
animals.each {|animal| puts animal }
#

N times(), , , N :
3.times { print "! " } #=> ! ! !

&
:
def wrap &b
print " : "
3.times(&b)
print "\n"
end
wrap { print "! " }

, .

( Exception ).
raise. , Ruby
,
.
rescue , ,
begin end.

64 I
begin
content = load_blog_data(file_name)
rescue BlogDataNotFound
STDERR.puts " #{file_name} "
rescue BlogDataFormatError
STDERR.puts " #{file_name} "
rescue Exception => exc
STDERR.puts " #{file_name}: #{exc.message}"
end

rescue begin-end.
,
,
.

4.4.
Ruby : .
.

Ruby :

1
class Order < ActiveRecord::Base
has_many :line_items
def self.find_all_unpaid
self.where('paid = 0')
5
end
def total
sum = 0
line_items.each {|li| sum += li.total}
sum
10
end
end

class,
( ).
Order , Base,
ActiveRecord.
Rails .
has_many , Active Record.
Order.
, .
.
self.( 3)

4 Ruby 65

: .
:
to_collect = Order.find_all_unpaid

. , @,
.
.

. , :
class Greeter
def initialize(name)
@name = name
end
def name
@name
end

end

def name=(new_name)
@name = new_name
end

g = Greeter.new("")
g.name
#=>
g.name = ""
g.name
#=>

Ruby ,
( , ):
class Greeter
attr_accessor :name
attr_reader :greeting
attr_writer :age

#
#
#

(public) . ,
, :
class MyClass
def m1
end

protected
def m2
end

66 I
private

end

def m3
end

private; .
,
.
Ruby .
.

, , ,
. ,
.
. -, ,
, , - . -,
, ,
.
,
. .
Rails
. Rails . , , , store, store_helper.rb app/helpers
:
module StoreHelper
def capitalize_words(string)
string.split(' ').map {|word| word.capitalize}.join(' ')
end
end

YAML, Ruby, , Rails.

YAML
YAML1 , YAML Aint Markup Language
(YAML ). Rails YAML
http://www.yaml.org/

4 Ruby 67

, . :
development:
adapter: sqlite3
database: db/development.sqlite3
pool: 5
timeout: 5000

YAML , ,
development () -, .
YAML ,
, Ruby
.

4.5.
Ruby - ,
. .
(
),
.
.
-, :
- ,
, IO -

, TypeError.
-, , Ruby
( ).
Rails .
Rails , ,
. model ,
.
, .
Ruby, , , .
, Rails.

4.6.
, Rails
Ruby, , ,

68 I
. 6,
.
, Ruby.
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.string :title
t.text :description
t.string :image_url
t.decimal :price, precision: 8, scale: 2

end

end

end

t.timestamps

Ruby, ,
products. title, description,
image_url price, , timestamps (
22.1).
Ruby.
CreateProducts, Migration
ActiveRecord. change().
create_table() ( ActiveRecord::Migration),
.
create_table() , .
t, . Rails
, .

.
decimal , .
, Ruby, ,
, ,
, .
Rails , Ruby,
(, )
. , ,
.
, , , ,
Ruby. .

4 Ruby 69

4.7. , Ruby
Ruby
, , .
Ruby-.
empty! empty?
Ruby () (-). . -
true false.
a || b
a || b a. false nil,
a.
b. , .
a ||= b
: a= b a = a b.
:
count += 1
price *= discount
count ||= 0

# count = count + 1
# price = price * discount
# count = count || 0

, count ||= 0 count


0, count .
obj = self.new
:
class Person < ActiveRecord::Base
def self.for_dave
Person.new(name: '')
end
end

, Person. :
class Employee < Person
# ..
end
dave = Employee.for_dave

# Person

for_dave()
Person, Employee.for_dave .
self.new
-, Employee.

70 I
lambda
lambda Proc. , Ruby 1.9, ->.
19.3.
require File.expand_path('../../config/environment',__FILE__)
Ruby- require .
,
. Ruby
, , LOAD_PATH.
, . , . ,
, ,
.
,
, , . ,
File.expand_
path(), ,
, (
__FILE__).

, Ruby .
:
http://www.ruby-lang.org/en/documentation/ruby-from-other-languages/
http://en.wikipedia.org/wiki/Ruby_programming_language
http://www.zenspider.com/Languages/Ruby/QuickRef.html
, . Rails, , , Rails,
( ) Ruby.
.

II

:
;
, , ;
.

, ,
, .
- - Depot.
, -,
.
?
,
Rails-. , , ,
.
12 , , .

5.1.
.
.
, , .

5 - 73

, ,
- .
. ,
. , , , , ,
. , ,
, .
, .
,
.
, ,
. ,
, Ruby on Rails
.
. , ,
. Rails ,

.
.

5.2. Depot
Depot. -. ,
( , ).


, -
. ,
, , , , ,
.
Depot ( ). ,
: .

74 II
Depot , , .
Depot ,
, , . (
Depot, ,
, - .)
, . , ,
, ,
, ? - , ,
,
.
, ,
, ( )
. ,
: ,
.


,
. , , ,
, , .
, - -
Photoshop, Word ( ) HTML. . ,
, .

.5.1. . , . ,
.
, ,
. , . ,
,
.
. 5.2. . , ,
.

.

5 - 75

. 5.1. ,

. 5.2. ,

76 II
.
.
, , , .
, ,
, . ,
.
, , , .

, , .
, , .
, , .
. ,
.
,
, .5.3.
, ,
- , ,
.

. 5.3. ,

, ,
. - , -
, . ,
,
- , .
,

5 - 77

, . ,
Depot.
,
.
,
.
,
.
, . ,
, , ,
.
, . ( , ,
.)

5.3.

, ,
,
. , ,
,
. ,
.
,
. ,
. ?
, . ,
- ,
, ,
,
. , , .

.
Rails SQLite3 Linux, Mac OS X
Windows .
. , , . ,
. : ,

78 II
,
. .
,
. ,
.
rake db:migrate:redo, ,
III. .
- ,
.

;
;
;
;
.

-,
, , ..
,
.
,
1, 2, 3 .. . ,
.

6.1. A1:

Depot . ,
, .
, ,

80 II
. , , Rails
.

Rails-
2 , Rails-.
. rails new,
.
depot, , , :
work> rails new depot

, . , depot.
.
work> cd depot
depot> dir /w
[.]
[..]
config.ru
[db]
[log]
[public]
[tmp]
[vendor]

[app]
Gemfile
Rakefile

[bin]
[config]
Gemfile.lock
[lib]
README.rdoc
[test]

Windows ls p dir /w.



SQLite ( , ).
SQLite 3.
SQLite 3 , Rails-
, Rails 1 Rails.
SQLite 3 , . ,

(, Rails, ).
, SQLite 3, , ,
. Getting Started Rails Guide1.

http://guides.rubyonrails.org/getting_started.html#configuring-a-database

6 : 81


, .5.3, ,
, products. .
Rails,
, , .
, products , ,
. Rails,
, Rails ,
(scaffold) . , ,
, Product. Rails
,
.
Product, Rails
products. ( ? , Rails development config/database.yml. SQLite 3
db.)
depot> rails generate scaffold Product \
title:string description:text image_url:string price:decimal
invoke active_record
create
db/migrate/ 20121130000001_create_products.rb
create
app/models/product.rb
invoke
test_unit
create
test/models/product_test.rb
create
test/unit/product_test.rbcreate
create
test/fixtures/products.yml
invoke resource_route
route
resources :products
invoke jbuilder_scaffold_controller
create
app/controllers/products_controller.rb
invoke
erb
create
app/views/products
create
app/views/products/index.html.erb
create
app/views/products/edit.html.erb
create
app/views/products/show.html.erb
create
app/views/products/new.html.erb
create
app/views/products/_form.html.erb
invoke
test_unit
create
test/ controllers/products_controller_test.rb
invoke
helper
create
app/helpers/products_helper.rb
invoke
test_unit
create
test/helpers/products_helper_test.rb
invoke jbuilder
exist
app/views/products
create
app/views/products/index.json.jbuilder
create
app/views/products/show.json.jbuilder
invoke assets

82 II
invoke
create
invoke
create
invoke
create

coffee
app/assets/javascripts/products.js.coffee
scss
app/assets/stylesheets/products.css.scss
scss
app/assets/stylesheets/scaffolds.css.scss

. , 20121130000001_create_products.rb.
, , , . , .
,
, .
22,
.
UTC
(20121130000001), (create_products) (.rb, Ruby).
, , . , ,
.
, .


Rails ,
(price),
.
rails40/depot_a/db/migrate/20121130000001_create_products.rb
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.string :title
t.text :description
t.string :image_url

t.decimal :price, precision: 8, scale: 2

end

end

end

t.timestamps

Rails
. rake.
, :
, .

6 : 83

rake ,
:
depot> rake db:migrate
== CreateProducts: migrating =================================================
-- create_table(:products)
-> 0.0027s
== CreateProducts: migrated (0.0023s) ========================================

. Rake , , . ,
development database.yml, products.
. Depot Rails.

. products
Product
products. . , .


(
, SQLite 3, -
). ,
, .
, Rails:
depot> rails server
=> Booting WEBrick
=> Rails 4.0.0 application starting in development on http://0.0.0.0:3000
=> Run 'rails server -h' for more startup options
=> Ctrl-C to shutdown server
[2013-04-18 17:45:38] INFO WEBrick 1.3.1
[2013-04-18 17:45:38] INFO ruby 2.0.0 (2013-02-24) [i386-mingw32]
[2013-04-18 17:45:43] INFO WEBrick::HTTPServer#start: pid=4908 port=3000

-
3000, , demo,
2. Address already in use ( ),
, Rails. , ,
Hello, World! 2.
Ctrl+C.
Windows, :
[Y()/N()]? (Terminate batch job (Y/N)?).
, y.

84 II
. , URL-, , (3000)
(products), . 6.1.

. 6.1.

, .
- . New product
( ) ,
(. 6.2).

. 6.2.

HTML-, ,
2.2 , Rails!. . description ():

6 : 85
rails40/depot_a/app/views/products/_form.html.erb
<%= form_for(@product) do |f| %>
<% if @product.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@product.errors.count, "error") %>
prohibited this product from being saved:</h2>
<ul>
<% @product.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :description %><br>

<%= f.text_area :description, rows: 6 %>


</div>
<div class="field">
<%= f.label :image_url %><br>
<%= f.text_field :image_url %>
</div>
<div class="field">
<%= f.label :price %><br>
<%= f.text_field :price %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>

8, :
. , , ,
. (. 6.3).
Create (), ,
. Back (),
(. 6.4).
, , ,
.
( , ..). ,
, ,
. ( -
, , .)

86 II

. 6.3.

. 6.4. ,


. ,
:
rake test

6 : 87

, : 0 failures, 0 errors (0 , 0 ).
, Rails
. , .
II ,
.
7.2 2: .
, , SQLite3,
. database.yml
23.

6.2. 2:

( -
). , .
? , , URL-
?
. ,
, ,
: ? , .
, Rails,
.
, , .

. ,
, ,
. ,
, .
,
. , . Rails
.
seeds.rb, db.
products. create() Product.
. , , 1.
http://media.pragprog.com/titles/rails4/code/rails40/depot_a/db/seeds.rb

88 II
1 app/assets/
images . , seeds.rb products
.
, , !
rails40/depot_a/db/seeds.rb
Product.delete_all
# . . .
Product.create!(title: 'Programming Ruby 1.9 & 2.0',
description:
%{<p>
Ruby is the fastest growing and most exciting dynamic language
out there. If you need to get working programs delivered fast,
you should add Ruby to your toolbox.
</p>},
image_url: 'ruby.jpg',
price: 49.95)
# . . .

, %{},
, .
.
, Rails create()!
-
.
products :
depot> rake db:seed

.
:
HTML- class.
, .
Rails, , generate scaffolding
. , ,
products.css.scss, app/assets/stylesheets.
rails40/depot_a/app/assets/stylesheets/products.css.scss
// Products.
// application.css.
// Sass (SCSS), : http://sass-lang.com/
.products {

table {

border-collapse: collapse;

http://media.pragprog.com/titles/rails4/code/rails40/depot_a/app/assets/images/

6 : 89

table tr td {

padding: 5px;

vertical-align: top;

.list_image {

width: 60px;

height: 70px;

.list_description {

width: 60%;

dl {

margin: 0;

dt {

color: #244;

font-weight: bold;

font-size: larger;

dd {

margin: 0;

.list_actions {

font-size: x-small;

text-align: right;

padding-left: 1em;

.list_line_even {

background: #e0f8f8;

.list_line_odd {

background: #f8b0f8;

}
}

,
, Rails , . ,
. Mac OS X Linux
touch.
, , CSS dl
.list_description, , ,

90 II
products. ,
, .
, ,
erb,
Ruby. , , scss, , ,
, ,
css, Sassy CSS1.
!
ERb, SCSS CSS. SCSS ,
.
SCSS CSS,
. SCSS Pragmatic Guide to Sass.
, products, . .html.erb, -
. HTML- <head>,
. Rails
,
. application.html.erb Rails
layouts:
rails40/depot_a/app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Depot</title>
<%= stylesheet_link_tag "application", media: "all",
"data-turbolinks-track" => true %>
<%= javascript_include_tag "application", "data-turbolinks-track" => true %>
<%= csrf_meta_tags %>
</head>
<body class='<%= controller.controller_name %>'>
<%= yield %>
</body>
</html>

, , ,
, . controller_name,
.
, ,
, index.html.erb app/
views/products ,
:
http://sass-lang.com/

6 : 91
rails40/depot_a/app/views/products/index.html.erb
<h1>Listing products</h1>
<table>
<% @products.each do |product| %>
<tr class="<%= cycle('list_line_odd', 'list_line_even') %>">
<td>

<%= image_tag(product.image_url, class: 'list_image') %>


</td>
<td class="list_description">
<dl>
<dt><%= product.title %></dt>
<dd><%= truncate(strip_tags(product.description),
length: 80) %></dd>
</dl>
</td>
<td class="list_actions">
<%= link_to 'Show', product %><br/>
<%= link_to 'Edit', edit_product_path(product) %><br/>
<%= link_to 'Destroy', product, method: :delete,
data: { confirm: 'Are you sure?' } %>
</td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New product', new_product_path %>

Rails.
. Rails
list_line_even, list_line_odd,

.
truncate() .
truncate() strip_tags(),
HTML-.
, link_to 'Destroy'
data: { confirm: ' ?' }.
, Rails ,

. (
.)

92 II
, , index.
html.erb, , application.
css.scss,
application.html.erb. http://
localhost:3000/products. ,
.6.5.

. 6.5.

, ,
. .


-.
;; , .
;; ,
.
;; products (scaffold) ,
.
;; , ,
,
.

6 : 93
method: :delete?

, method: :delete. ,
ProductsController, HTTP-.
HTTP . HTTP
, , ,
. , , HTTP GET. HTTP
.
, HTTP DELETE.
Rails , .
, , Rails PUT
DELETE POST HTTP ,
.
.

. ,
- :
,
, Rails .
.
, , ,
.


.
,
. :
depot> rake db:rollback

, products .
rake db:migrate .
.
22
.

1,
. Git ( , , ),

.

94 II
depot> git config --global --add user.name "Sam Ruby"
depot> git config --global --add user.email rubys@intertwingly.net

:
depot> git config --global --list

Rails .gitignore, Git


, :
rails40/depot_a/.gitignore
#
#
#
#
#

See http://help.github.com/ignore-files/ for more about ignoring files.


If you find yourself ignoring temporary files generated by your text editor
or operating system, you probably want to add a global ignore instead:
git config --global core.excludesfile '~/.gitignore_global'

# Ignore bundler config.


/.bundle
# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal
# Ignore all logfiles and tempfiles.
/log/*.log
/tmp

- , ,
Unix .
, ls -a.
, ,
:
depot> git init
depot> git add .
depot> git commit -m "Depot Scaffold"

, ,
,
.
,
:
depot> git checkout .

:
;
.

, ,
Rails.
, , Depot, ,
, .

7.1. 1:
, 1,
- .
, . , , ,
,
.
, , URL , .

96 II
, ? . ,
,
. , . , .
, .
(
app/models/product.rb):
class Product < ActiveRecord::Base
end

. -
.
:
validates :title, :description, :image_url, presence: true

validates() Rails ().


.
presence: true . .7.1 ,
, ,
. :
,
. . ,
product.rb , Rails
, ,
.
, , .
numericality. greater_
than_or_equal_to ( ) 0.01:
validates :price, numericality: {greater_than_or_equal_to: 0.01}

, ,
, . 7.2.
0.01, ?
, 0.001.
, ,
. , ,
, 0.01, ,
.

7 : 97

. 7.1.

. ,
. Product. ,
, products ,
, :
validates :title, uniqueness: true

, URL-
. format,
.
, URL- : .gif,
.jpg .png.
validates :image_url, allow_blank: true, format: {
with: %r{\.(gif|jpg|png)\Z}i,
message: 'URL GIF, JPG PNG.'
}

98 II

. 7.2.

,
allow_blank.
, , ,
,
.
, :
, URL- ;
, 0.01;
;
URL- .
Product :

7 : 99
rails40/depot_b/app/models/product.rb
class Product < ActiveRecord::Base
validates :title, :description, :image_url, presence: true
validates :price, numericality: {greater_than_or_equal_to: 0.01}
validates :title, uniqueness: true
validates :image_url, allow_blank: true, format: {
with: %r{\.(gif|jpg|png)\Z}i,
message: 'must be a URL for GIF, JPG or PNG image.'
# URL GIF, JPG PNG
}
end

, . ,

.
, :
rake test

, . : should
create product ( ) should update product ( ). , - -
. .
,
?

test/controllers/products_controller_test.rb:
rails40/depot_b/test/controllers/products_controller_test.rb
require 'test_helper'
class ProductsControllerTest < ActionController::TestCase
setup do
@product = products(:one)

@update = {

title: 'Lorem Ipsum',

description: 'Wibbles are fun!',

image_url: 'lorem.jpg',

price: 19.95

}
end
test "should get index" do
get :index
assert_response :success
assert_not_nil assigns(:products)
end
test "should get new" do
get :new
assert_response :success
end

100 II
test "should create product" do
assert_difference('Product.count') do
post :create, product: @update
end
assert_redirected_to product_path(assigns(:product))
end

# ...
test "should update product" do
patch :update, id: @product, product: @update
assert_redirected_to product_path(assigns(:product))
end

end

# ...

, , . , .
. , ,
.
8.4 4: . .

7.2. 2:
Rails ,
. ,
Rails
rails.
models:
depot> dir test\models /w
[.]
[..]

product_test.rb

product_test.rb , Rails ,
, generate. , , Rails.
, Rails
test/models/product_test.rb :
rails40/depot_b/test/models/product_test.rb
require 'test_helper'
class ProductTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end

7 : 101

P r o d u c t T e s t A c t i v e
Support::TestCase. , ActiveSupport::TestCase MiniTest::Unit::TestCase, , Rails
MiniTest1, Ruby.
, , Ruby- MiniTest ( ?), Rails-.
MiniTest , .
.
Rails - "the truth". test...do
, Active Support ,
, -,
.
.
assert , , .
, , , , , , (true)
. , ,
.


. , , ,
, , .
, , errors() invalid?() , , ,
, , any?() .
, , , ,
, . .
, , .
assert,
, . , . assert , .
, . , Product ,
,
.

http://www.ruby-doc.org/stdlib-2.0/libdoc/minitest/unit/rdoc/MiniTest.html

102 II
assert !product.valid?

the truth :
rails40/depot_b/test/models/product_test.rb
test "product attributes must not be empty" do
#
product = Product.new
assert product.invalid?
assert product.errors[:title].any?
assert product.errors[:description].any?
assert product.errors[:price].any?
assert product.errors[:image_url].any?
end

rake
test:models. , , :
depot> rake test:models
.
Finished tests in 0.257961s, 3.8766 tests/s, 19.3828 assertions/s.
1 tests, 5 assertions, 0 failures, 0 errors, 0 skips

, .
, .
.
,
:
rails40/depot_c/test/models/product_test.rb
test "product price must be positive" do
#
product = Product.new(title:
"My Book Title",
description: "yyy",
image_url:
"zzz.jpg")
product.price = -1
assert product.invalid?
assert_equal ["must be greater than or equal to 0.01"],
product.errors[:price]
# 0.01
product.price = 0

end

assert product.invalid?
assert_equal ["must be greater than or equal to 0.01"],
product.errors[:price]
product.price = 1
assert product.valid?

7 : 103

,
-1, 0 +1, . ,
,
, c .
, ,
. (, -
, .)
URL-
: .gif, .jpg .png:
rails40/depot_c/test/models/product_test.rb
def new_product(image_url)
Product.new(
title:
"My Book Title",
description:
"yyy",
price: 1,
image_url: image_url)
end
test "image url" do
# url
ok = %w{ fred.gif fred.jpg fred.png FRED.JPG FRED.Jpg
http://a.b.c/x/y/z/fred.gif }
bad = %w{ fred.doc fred.gif/more fred.gif.more }
ok.each do |name|
assert new_product(name).valid?, "#{name} shouldn't be invalid"
#
end

end

bad.each do |name|
assert new_product(name).invalid?, "#{name} shouldn't be valid"
#
end

. : ,
, ,
.
.
, assert
. ,
, ,
, .
, , . ,
.

104 II
, , , . , .
Rails.


, . , , ,
, ,
.
Rails ( ). , , ,
products ,
, Rails .
test/fixtures.
YAML. YAML
.
. Product, , ,
products.yml.
Rails :
rails40/depot_b/test/fixtures/products.yml
# Read about fixtures at http://api.rubyonrails.org/classes/Fixtures.html
one:

title: MyString
description: MyText
image_url: MyString
price: 9.99

two:

title: MyString
description: MyText
image_url: MyString
price: 9.99

, . . , Rails,
one two.
. ,
.
,
.

7 : 105
:
, ,
. ,
product(:valid_order_for_fred), . ,
, p1 order4.
, .
.
, -
valid_order_for_fred? ,
. , order1, christmas_
order. customer1 fred.
, ,
(christmas_order) (invalid_credit_card), (valid_credit_card)
(aunt_mary).
, , .

,
-. , config/database.yml,
, ,
, , .
,
,

.
, Product:
rails40/depot_c/test/fixtures/products.yml
ruby:
title: Programming Ruby
description:
Ruby is the fastest
language out there.
delivered fast, you
price: 49.50
image_url: ruby.png

1.9
growing and most exciting dynamic
If you need to get working programs
should add Ruby to your toolbox.

,
Rails products. Rails (
!), ,
test/models/product_test.rb:
class ProductTest < ActiveSupport::TestCase
fixtures :products
#...
end

106 II
fixtures() , .
,
. , :products ,
products.yml.
.
ProductTest fixtures , products , ,
.
, , Rails,
fixtures. ,
.
,
.
.
. ,
, Rails .
database.yml config, ,
Rails :
db/development.sqlite3 .
;
db/test.sqlite3 ;
db/production.sqlite3 . , .

,
.
rake test, , rake
db:test:prepare.


, , ,
, .
,
. Rails . . ,
: ,
YAML-, , .
products(:ruby) Product, ,

7 : 107

. ,
.
rails40/depot_c/test/models/product_test.rb
test "product is not valid without a unique title" do
# ,
product = Product.new(title: products(:ruby).title,
description:
"yyy",
price:
1,
image_url:
"fred.gif")
assert product.invalid?

end

assert_equal ["has already been taken"], product.errors[:title]


#

,
Ruby. (title) ,
:
products(:ruby).title

Product, . ,
title .
Active Record
,
:
rails40/depot_c/test/models/product_test.rb
test " product is not valid without a unique title - i18n" do
product = Product.new(title:
products(:ruby).title,
description: "yyy",
price:
1,
image_url:
"fred.gif")
assert product.invalid?

end

assert_equal [I18n.translate('activerecord.errors.messages.taken')],
product.errors[:title]

I18n 15 : .
,
, . ,
, .
, .

108 II


,
.
;; .
;; , ,
.
;; .
;; .
;; , Rails, ,
, .
, ,
, , ,
, , . ,
.


:
Git, , ,
. ,
, git status:
depot> git status
# On branch master
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: app/models/product.rb
# modified: test/fixtures/products.yml
# modified: test/controllers/products_controller_test.rb
# modified: test/models/product_test.rb
# no changes added to commit (use "git add" and/or "git commit -a")


, git add git commit git commit -a:
depot> git commit -a -m 'Validation!'

7 : 109

, , ,
,
git checkout .
:length .
Product, ,
10 .
, .
( http://www.pragprog.com/wikis/wiki/
RailsPlayTime.)

;
;
CSS;
;
.

.
, ,
-. ,
.
.
. .
. , ,
. .

, , , ,
.
,
.

8 : 111

8.1. 1:
,
Depot. ,
. Store.
depot> rails generate controller Store index
create app/controllers/store_controller.rb
route get "store/index"
invoke erb
create app/views/store
create app/views/store/index.html.erb
invoke test_unit
create test/controllers/store_controller_test.rb
invoke helper
create app/helpers/store_helper.rb
invoke test_unit
create test/helpers/store_helper_test.rb
invoke assets
invoke coffee
create app/assets/javascripts/store.js.coffee
invoke scss
create app/assets/stylesheets/store.css.scss

, generate,
( StoreController
store_controller.rb), index().
http://localhost:3000/store/
index ( !) , -
.
URL- -. config/routes.
rb:
rails40/depot_d/config/routes.rb
Depot::Application.routes.draw do
get "store/index"
resources :products
#
#
#
#
#
#

The priority is based upon order of creation:


( :)
first created -> highest priority.
( -> .)
See how all your routes lay out with "rake routes".
( "rake routes".)

# You can have the root of your site routed with "root"
# ( "root")
root to: 'store#index', as: 'store'
# ...
end

112 II
,
. . ,
-. ,
.
( welcome store) as: 'store'. Rails store_path, 2.3
say_goodbye_path.
, . http://localhost:3000/ - (.8.1).

. 8.1.

, , , ,
. ,
, .
, . , , .

, . , index() store_controller.rb. , ,
:
rails40/depot_d/app/controllers/store_controller.rb
class StoreController < ApplicationController
def index

@products = Product.order(:title)
end
end

, ,
, : ,

8 : 113

, .
order(:title) Product.
.
index.html.erb app/views/store. (, [store] [index].
.html.erb , ERB-, HTML.)
rails40/depot_d/app/views/store/index.html.erb
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<h1>Your Pragmatic Catalog</h1>
<% @products.each do |product| %>
<div class="entry">
<%= image_tag(product.image_url) %>
<h3><%= product.title %></h3>
<%= sanitize(product.description) %>
<div class="price_line">
<span class="price"><%= product.price %></span>
</div>
</div>
<% end %>

sanitize()
(description). HTML,
. ( ,
1, ,
, , , .)
image_tag().
HTML- <img>, .
, , 2 , ,
StoreController, HTML- class store:
rails40/depot_d/app/assets/stylesheets/store.css.scss
//
//
//
//
//
//

Place all the styles related to the Store controller here.


( Store.)
They will automatically be included in application.css.
( application.css.)
You can use Sass (SCSS) here: http://sass-lang.com/
( Sass (SCSS) : http://sass-lang.com/)

.store {

h1 {

margin: 0;

http://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29

114 II

padding-bottom: 0.5em;
font: 150% sans-serif;
color: #226;
border-bottom: 3px dotted #77d;

/* store */
.entry {
overflow: auto;
margin-top: 1em;
border-bottom: 1px dotted #77d;
min-height: 100px;
img {
width: 80px;
margin-right: 5px;
margin-bottom: 5px;
position: absolute;
}
h3 {
font-size: 120%;
font-family: sans-serif;
margin-left: 100px;
margin-top: 0;
margin-bottom: 2px;
color: #227;
}
p, div.price_line {
margin-left: 100px;
margin-top: 0.5em;
margin-bottom: 0.8em;
}
.price {
color: #44a;
font-weight: bold;
margin-right: 3em;
}
}

(Refresh) , . 8.2. , -
. ,
, .
, , , .
-, ,
- .
- , -
, .
.

8 : 115

. 8.2. ()

8.2. 2:
- , , .
-.
application.html.
erb, class 2.
, , ,
, .
, ,
- .
, :
rails40/depot_e/app/views/layouts/application.html.erb
1
5
10
15
-

<!DOCTYPE html>
<html>
<head>
<title>Pragprog Books Online Store</title>
<%= stylesheet_link_tag "application", media: "all",
"data-turbolinks-track" => true %>
<%= javascript_include_tag "application",
"data-turbolinks-track" => true %>
<%= csrf_meta_tag %>
</head>
<body class="<%= controller.controller_name %>">
<div id="banner">
<%= image_tag("logo.png") %>
<%= @page_title || "Pragmatic Bookshelf" %>
</div>
<div id="columns">

116 II
<div id="side">
<ul>
<li><a href="http://www....">Home</a></li>
20
<li><a href="http://www..../faq">Questions</a></li>
<li><a href="http://www..../news">News</a></li>
<li><a href="http://www..../contact">Contact</a></li>
</ul>
</div>
25
<div id="main">
<%= yield %>
</div>
</div>
- </body>
30 </html>

HTML- 3 Rails
. 5 Rails, <link>,
, (turbolinks)1,
,
. 7 <link>,
.
, 9 ,
- ,
, 12 :
.
14 @page_title. 26.
yield Rails ,
, , ,
. ,
index.html.erb.
, application.css application.
css.scss. Git,
6, , ,
. Git
git mv. Git,
:
rails40/depot_e/app/assets/stylesheets/application.css.scss
/*
* This is a manifest file that'll be compiled into application.css, which will
* include all the files listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets,
* vendor/assets/stylesheets, or vendor/assets/stylesheets of plugins, if any,
* can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear
* at the top of the compiled file, but it's generally better to create a new

https://github.com/rails/turbolinks

8 : 117
* file per style scope.
*
*= require_self
*= require_tree .
*/
#banner {

background: #9c9;

padding: 10px;

border-bottom: 2px solid;

font: small-caps 40px/40px "Times New Roman", serif;

color: #282;

text-align: center;

img {

float: left;

}
}

#notice {

color: #000 !important;

border: 2px solid red;

padding: 1em;

margin-bottom: 2em;

background-color: #f0f0f0;

font: bold smaller sans-serif;


}

#columns {

background: #141;

#main {

margin-left: 17em;

padding: 1em;

background: white;

#side {

float: left;

padding: 1em 2em;

width: 13em;

background: #141;

ul {

padding: 0;

li {

list-style: none;

a {

color: #bfb;

font-size: small;

}
}

118 II
, -
, .
require_tree.
,
stylesheet_link_tag(), ,
, .
:
, ,
. , , . ,
, , , CSS.
, ,
. , ,
.
Sass,
. , img, #banner. ,
#side.
(Refresh) , . 8.3. , ,
,
.

. 8.3.

,
. ,
. 12.34 $12.34, 13
$13.00. .

8 : 119

8.3. 3:

Ruby sprintf(), . , , . , :
<span class="price"><%= sprintf("$%0.02f", product.price) %></span>

, .
,
.

. Rails
number_to_currency().
,
index :
<span class="price"><%= product.price %></span>

:
rails40/depot_e/app/views/store/index.html.erb
<span class="price"><%= number_to_currency(product.price) %></span>

, (Refresh) , .8.4.

. 8.4.

120 II
, ,
, ,
.

8.4. 4:

. , , - .
, :
depot> rake test

. , . ,
, ,
.
. ,
. ,
, , .
, ,
. Rails
.
, Rails :
rails40/depot_d/test/controllers/store_controller_test.rb
require 'test_helper'
class StoreControllerTest < ActionController::TestCase
test "should get index" do
get :index
assert_response :success
end
end

should get index ,


. . ,
, , . , :
rails40/depot_e/test/controllers/store_controller_test.rb
require 'test_helper'
class StoreControllerTest < ActionController::TestCase

8 : 121
test "should get index" do
get :index
assert_response :success
assert_select '#columns #side a', minimum: 4
assert_select '#main .entry', 3
assert_select 'h3', 'Programming Ruby 1.9'
assert_select '.price', /\$[,\d]+\.\d\d/
end

end

HTML-, CSS. ,
, (#), id,
, (.), class,
.
a,
id, side, , ,
id, columns.
, .
assert_select() , ?
. ,
class entry , id main. h3
Ruby.
. ,
:
rails40/depot_e/test/fixtures/products.yml
# Read about fixtures at http://api.rubyonrails.org/classes/Fixtures.html
one:

title: MyString
description: MyText
image_url: MyString
price: 9.99

two:

title: MyString
description: MyText
image_url: MyString
price: 9.99

ruby:
title: Programming Ruby 1.9
description:
Ruby is the fastest growing and most exciting dynamic
language out there. If you need to get working programs
delivered fast, you should add Ruby to your toolbox.
price: 49.50
image_url: ruby.png

122 II
, , assert_select(), . , . ,
. ,
. ,
, ,
( , ), ,
, .
: ,
,
. .
,
.
assert_select().
-1. ,
, .
,
(, , ):
depot> rake test:controllers

- ,
, , ,
. , , , Rails
. , HTML CSS,
.
, ,
.

8.5. 5:

,
. , ,
.
.
,
.
, , , .
http://api.rubyonrails.org/classes/ActionDispatch/Assertions/SelectorAssertions.html

8 : 123
rails40/depot_e/config/environments/development.rb
config.action_controller.perform_caching = true

, .
. ,
, ,
,
. , , , .
rails40/depot_e/app/models/product.rb
def self.latest
Product.order(:updated_at).last
end

,
- , ,
,
.
rails40/depot_e/app/views/store/index.html.erb
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<h1>Your Pragmatic Catalog</h1>
<% cache ['store', Product.latest] do %>
<% @products.each do |product| %>

<% cache ['entry', product] do %>


<div class="entry">
<%= image_tag(product.image_url) %>
<h3><%= product.title %></h3>
<%= sanitize(product.description) %>
<div class="price_line">
<span class="price"><%= number_to_currency(product.price) %></span>
</div>
</div>

<% end %>


<% end %>
<% end %>

,
. store, entry. ,
, , store, entry.
, , , Rails (Russian doll caching)1.
http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works

124 II
! , , Rails. ,
, ,
.
, ,
RailsGuides1 Caching with Rails (
Rails).
,
. ,
, ! ,
, , , , ,
.
, , ,
.
rails40/depot_f/config/environments/development.rb
config.action_controller.perform_caching = false

, ,
, .


,
-. .
;; .
;; index().
;; Store default_scope() -.
;; ( .html.erb) (
.html.erb).
;; .
;; (CSS).
;; .
;; .
,
!
http://guides.rubyonrails.org/caching_with_rails.html

8 : 125


.
.
, .
number_to_currency
.
, assert_select.
test/controllers/products_controller_test.rb.
, Git.
,
.
, 16.2.
( http://www.pragprog.com/wikis/wiki/

RailsPlayTime.)

:
;
;
.

, ,
, .
,
. ,
, . , .

9.1. 1:
-, ( )
.
, .
- ,
, ,
.
, , .
, cart.id, .

9 : 127

.
:
depot> rails generate scaffold Cart
...
depot> rake db:migrate
== CreateCarts: migrating ====================================================
-- create_table(:carts)
-> 0.0012s
== CreateCarts: migrated (0.0014s) ===========================================

Rails ,
:cart_id.
rails40/depot_f/app/controllers/concerns/current_cart.rb
module CurrentCart
extend ActiveSupport::Concern
private

end

def set_cart
@cart = Cart.find(session[:cart_id])
rescue ActiveRecord::RecordNotFound
@cart = Cart.create
session[:cart_id] = @cart.id
end

set_cart() :cart_id session


,
. ( ,
, - ,
nil), Cart,
, .
, set_cart() CurrentCart
(private). ( , !) Rails -
.

9.2. 2:

, -
.
Rails 20.3, .

128 II
. .
, .5.3, , Rails
:
depot> rails generate scaffold LineItem product:references cart:belongs_to
...
depot> rake db:migrate
== CreateLineItems: migrating ================================================
-- create_table(:line_items)
-> 0.0013s
== CreateLineItems: migrated (0.0014s) =======================================


(line item), (cart) (product). Rails . LineItem,
.
rails40/depot_f/app/models/line_item.rb
class LineItem < ActiveRecord::Base
belongs_to :product
belongs_to :cart
end

belongs to
. belongs_to().
LineItem belongs_to() Rails,
(line_items)
(carts) (products).
, . ,
belongs_to, :
,
belongs_to.
- ? , .
LineItem belongs_to, (Product),
, :
li = LineItem.find(...)
puts " #{li.product.title}"

, ,
.
cart.rb app/models has_
many():

9 : 129
rails40/depot_f/app/models/cart.rb
class Cart < ActiveRecord::Base

has_many :line_items, dependent: :destroy


end

has_many :line_items : (has many) .


,
. dependent: :destroy , .
, , , Rails
, .
, , (Cart)
, ( ) :
cart = Cart.find(...)
puts " : #{cart.line_items.count}"


has_many (Product). , .
, ,
.
rails40/depot_f/app/models/product.rb
class Product < ActiveRecord::Base

has_many :line_items

before_destroy :ensure_not_referenced_by_any_line_item
#...

private

# ,
def ensure_not_referenced_by_any_line_item
if line_items.empty?
return true
else
errors.add(:base, ' ')
return false
end
end

end

, ,
ensure_not_referenced_by_any_line_item().
, Rails
.
, Rails .
false, .

130 II
, errors.
, validates() .
,
.

19.2.

9.3. 3:
, ,
(Add to Cart).
.
, ,
index(), show(), new(), edit(), create(), update()
destroy(). create(). ( , new() , , ,
create().)
.
? , .
LineItem. , create() app/
controllers/line_items_controller.rb, ,
URL- (/line_items) HTTP- (POST).
. ,
link_to(), HTTP-
GET. POST, , , button_to().
, URL-,
Rails , _path
. line_items_path.
: line_items_path ,
? ,
.
line_items_path() :product_id.
Rails ,
.
, index.
html.erb, :
rails40/depot_f/app/views/store/index.html.erb
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<h1>Your Pragmatic Catalog</h1>

9 : 131

<% cache ['store', Product.latest] do %>


<% @products.each do |product| %>
<% cache ['entry', product] do %>
<div class="entry">
<%= image_tag(product.image_url) %>
<h3><%= product.title %></h3>
<%= sanitize(product.description) %>
<div class="price_line">
<span class="price"><%= number_to_currency(product.price) %></span>
<%= button_to 'Add to Cart', line_items_path(product_id: product) %>
</div>
</div>
<% end %>
<% end %>
<% end %>

. button_to
HTML- <form>, HTML- <div>.
, .
, , ,
, CSS-:
rails40/depot_f/app/assets/stylesheets/store.css.scss
p, div.price_line {
margin-left: 100px;
margin-top: 0.5em;
margin-bottom: 0.8em;

form, div {
display: inline;
}

, .9.1. , create()
,
. (id)
. Rails ( ) id. create(),
.
create()?
HTTP- GET, HTTP- POST, Rails . ,
app/controllers/line_items_controller.rb.
Depot.

132 II

. 9.1. (Add to Cart)

LineItemsController,
( ,
), .
( ) ,
1 (concern) CurrentCart.
rails40/depot_f/app/controllers/line_items_controller.rb
class LineItemsController < ApplicationController
include CurrentCart
before_action :set_cart, only: [:create]
before_action :set_line_item, only: [:show, :edit, :update, :destroy]
# GET /line_items
#...
end

CurrentCart , set_cart()
create().
20,
, Rails , , .
, ,
@line_item
show(), edit(), update() destroy().
, , @cart ,
create() app/
controllers/line_items_controller.rb1, .
, .

9 : 133
rails40/depot_f/app/controllers/line_items_controller.rb
def create

product = Product.find(params[:product_id])

@line_item = @cart.line_items.build(product: product)


respond_to do |format|
if @line_item.save
format.html { redirect_to @line_item.cart,
notice: 'Line item was successfully created.' }

format.json { render action: 'show',


status: :created, location: @line_item }

else

end

end

end

format.html { render action: 'new' }


format.json { render json: @line_item.errors,
status: :unprocessable_entity }

params :product_
id. params Rails . , . , .
@cart.line_items.build.
@cart
. , Rails
.

@line_item.
,
10.2 2: , JSON-. :
, ,
. ,
, .cart .
, ,
.
,
. test/
controllers/line_items_controller_test.rb.
rails40/depot_g/test/controllers/line_items_controller_test.rb
test "should create line_item" do
assert_difference('LineItem.count') do

post :create, product_id: products(:ruby).id


end

assert_redirected_to cart_path(assigns(:line_item).cart)
end

134 II
assigns, , scaffold.
, (
) .
:
depot> rake test test/controllers/line_items_controller_test.rb

, , (Add to Cart)
.
, .9.2.

. 9.2.

, , . , ,
.
( ):
rails40/depot_f/app/views/carts/show.html.erb
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<h2>Your Pragmatic Cart</h2>
<ul>
<% @cart.line_items.each do |item| %>
<li><%= item.product.title %></li>
<% end %>
</ul>

9 : 135

, ,
(Refresh) , ,
(.9.3).

. 9.3.

http://localhost:3000/ .
. , . ,
. ,
, .
,
. ,
. , .


, . ,
Rails.
;; Cart
,
.

136 II
;;
(concern), .
;;
.
;; ,
.


.

index store. ,
.
:
if session[:counter].nil?
...

, .
.

. :
pluralize,
(
21.5).
, -
.
,
, .
( : http://www.pragprog.com/wikis/wiki/

RailsPlayTime.)

10

;
;
;
.


. . ,

, , .

10.1. 1:


line_items. , , 6.1. ,
.

138 II
depot> rails generate migration add_quantity_to_line_items quantity:integer

, Rails ,
line_items,
. Rails : add_XXX_to_TABLE remove_XXX_from_TABLE, XXX . ,
.
Rails ,
. ,

1:
rails40/depot_g/db/migrate/20121130000004_add_quantity_to_line_items.rb
class AddQuantityToLineItems < ActiveRecord::Migration
def change

add_column :line_items, :quantity, :integer, default: 1


end
end

:
depot> rake db:migrate

Cart add_product(),
, , ,
, , ,
LineItem:
rails40/depot_g/app/models/cart.rb
def add_product(product_id)
current_item = line_items.find_by(product_id: product_id)
if current_item
current_item.quantity += 1
else
current_item = line_items.build(product_id: product_id)
end
current_item
end

find_by() where(),
LineItem, nil.
, :
Rails40/depot_g/app/controllers/line_items_controller.rb
def create
product = Product.find(params[:product_id])

@line_item = @cart.add_product(product.id)
respond_to do |format|
if @line_item.save

10 : 139
format.html
notice:
format.json
status:

else

end

end

end

{ redirect_to @line_item.cart,
'Line item was successfully created.' }
{ render action: 'show',
:created, location: @line_item }

format.html { render action: 'new' }


format.json { render json: @line_item.errors,
status: :unprocessable_entity }


:
Rails40/depot_g/app/views/carts/show.html.erb
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<h2>Your Pragmatic Cart</h2>
<ul>
<% @cart.line_items.each do |item| %>

<li><%= item.quantity %> &times; <%= item.product.title %></li>


<% end %>
</ul>

, (Add to Cart) ,
. , ,
, , ,
. , , , , ,
. , .
:
depot> rails generate migration combine_items_in_cart

Rails , , change().
up() down().
up():
rails40/depot_g/db/migrate/20121130000005_combine_items_in_cart.rb
def up
#
Cart.all.each do |cart|
#
sums = cart.line_items.group(:product_id).sum(:quantity)
sums.each do |product_id, quantity|
if quantity > 1

140 II
#
cart.line_items.where(product_id: product_id).delete_all

end

end

end

end

#
item = cart.line_items.build(product_id: product_id)
item.quantity = quantity
item.save!

, , .
.
.

(quantity) , ,
product_id. product_
id quantity.
, product_id quantity.
, ,
, ,
.
, Rails .
, ,
:
depot> rake db:migrate

, ,
, .10.1. ,
.

. 10.1. LineItem

10 : 141

,
down().
;
, ,
; .
:
rails40/depot_g/db/migrate/20121130000005_combine_items_in_cart.rb
def down
# quantity>1
LineItem.where("quantity>1").each do |line_item|
# add individual items
line_item.quantity.times do
LineItem.create cart_id: line_item.cart_id,
product_id: line_item.product_id, quantity: 1
end

end

end

#
line_item.destroy


:
depot> rake db:rollback

Rails rake-,
.
depot> rake db:migrate:status
database: /home/rubys/work/depot/db/development.sqlite3
Status
Migration ID
Migration Name
-------------------------------------------------up
20130407000001
Create products
up
20130407000002
Create carts
up
20130407000003
Create line items
up
20130407000004
Add quantity to line items
down
20130407000005
Combine items in cart


. ,
(.10.2).
( rake db:migrate), ,
, ,
.
,
. ,
, . ,

142 II
. , -
. , carts/nnn, nnn .
, wibble. ,
, . 10.3.
. ,
.

. 10.2.

. 10.3.

10 : 143

10.2. 2:
, . 10.3, ,
67 1, :
@cart = Cart.find(params[:id])

, Active Record
RecordNotFound, , , . ,
?
. ,
, . , , -
,
- ,
.
- . -, , Rails 2. -,
(- ), .
Rails . .
(- ),
. -
.
. , show() ,
, -
index, . ,
index,
. -
flash.
? , ,
,
. , ,
, ,
, . - ,
.
.
.
2
http://guides.rubyonrails.org/debugging_rails_applications.html#the-logger
1

144 II
-, invalid_cart(),
:
rails40/depot_h/app/controllers/carts_controller.rb
class CartsController < ApplicationController
before_action :set_cart, only: [:show, :edit, :update, :destroy]
rescue_from ActiveRecord::RecordNotFound, with: :invalid_cart
# GET /carts
# ...
private
# ...

def invalid_cart

logger.error "Attempt to access invalid cart #{params[:id]}"

redirect_to store_url, notice: 'Invalid cart'


end
end

rescue_from , Cart.find().
.
Rails- . logger
. error.
,
redirect_to(). :notice ,
- . , ? , URL- , http://.../cart/
wibble. , ,
.
, . URL-:
http://localhost:3000/carts/wibble

.
. (development.log log), :

Started GET "/carts/wibble" for 127.0.0.1 at 2013-01-29 09:37:39 -0500


Processing by CartsController#show as HTML
Parameters: {"id"=>"wibble"}
^[[1m^[[35mCart Load (0.1ms)^[[0m SELECT "carts".* FROM "carts" WHERE
"carts"."id" = ? LIMIT 1 [["id", "wibble"]]

Redirected to http://localhost:3000/
Completed 302 Found in 3ms (ActiveRecord: 0.4ms)

.10.4 .

10 : 145

Unix- , ,
tail less. Windows . , . Unix
tail -f. tail Windows1
2. , OSX
Console.app.
open .log.

. 10.4.

,
-.
, HTML .
,
.
, .
, cart_id .

http://gnuwin32.sourceforge.net/packages/coreutils.htm
http://tailforwin32.sourceforge.net/

1
2

146 II
rails40/depot_h/app/controllers/line_items_controller.rb
# Never trust parameters from the scary internet, only allow the white
# list through.
# ( , ,
# .)
def line_item_params
params.require(:line_item).permit(:product_id)
end

, .
rake test:controllers

, log/test.log
.
LineItemsControllerTest: test_should_update_line_item
----------------------------------------------------^[[1m^[[36m (0.0ms)^[[0m ^[[1mbegin transaction^[[0m
^[[1m^[[35mLineItem Load (0.1ms)^[[0m SELECT "line_items".* FROM
"line_items" WHERE "line_items"."id" = ? LIMIT 1 [["id", 980190962]]
Processing by LineItemsController#update as HTML
Parameters: {"line_item"=>{"product_id"=>nil}, "id"=>"980190962"}
^[[1m^[[36mLineItem Load (0.1ms)^[[0m ^[[1mSELECT "line_items".* FROM
"line_items" WHERE "line_items"."id" = ? LIMIT 1^[[0m [["id", "980190962"]]
Unpermitted parameters: cart_id
^[[1m^[[35m (0.0ms)^[[0m SAVEPOINT active_record_1
^[[1m^[[36m (0.1ms)^[[0m ^[[1mRELEASE SAVEPOINT active_record_1^[[0m
Redirected to http://test.host/line_items/980190962
Completed 302 Found in 2ms (ActiveRecord: 0.2ms)
^[[1m^[[35m (0.0ms)^[[0m rollback transaction

.
rails40/depot_h/test/controllers/line_items_controller_test.rb
test "should update line_item" do

patch :update, id: @line_item, line_item: { product_id: @line_item.product_id }


assert_redirected_to line_item_path(assigns(:line_item))
end

.
rake log:clear LOGS=test
rake test:controllers

.
, .
, ,
. ,
.

10 : 147

.
, ,
.

10.3. 3:

,
destroy() ,
. , ,
button_to():
rails40/depot_h/app/views/carts/show.html.erb
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<h2>Your Pragmatic Cart</h2>
<ul>
<% @cart.line_items.each do |item| %>
<li><%= item.quantity %> &times; <%= item.product.title %></li>
<% end %>
</ul>
<%= button_to 'Empty cart', @cart, method: :delete,

data: { confirm: 'Are you sure?' } %>

,
( !), destroy():
Rails40/depot_h/app/controllers/carts_controller.rb
def destroy

@cart.destroy if @cart.id == session[:cart_id]

session[:cart_id] = nil
respond_to do |format|

format.html { redirect_to store_url,

notice: ' !' }


format.json { head :no_content }
end
end

test/controllers/carts_

controller_test.rb.

Rails40/depot_i/test/controllers/carts_controller_test.rb
test "should destroy cart" do
assert_difference('Cart.count', -1) do

148 II
session[:cart_id] = @cart.id
delete :destroy, id: @cart

end

end
assert_redirected_to store_path


,
(.10.5).

. 10.5. -:

-,
:
Rails40/depot_i/app/controllers/line_items_controller.rb
def create
product = Product.find(params[:product_id])
@line_item = @cart.add_product(product.id)
respond_to do |format|
if @line_item.save

format.html { redirect_to @line_item.cart }


format.json { render action: 'show',
status: :created, location: @line_item }
else
format.html { render action: 'new' }
format.json { render json: @line_item.errors,
status: :unprocessable_entity }
end
end
end

: : product_path
product_url
, ,
product_path,
product_url. .
product_url , http://example.com/products/1. ,
redirect_to, HTTP 302 URL- .
URL- ,
product_url(domain: "example2.com", product: product).

10 : 149
product_path.
/products/1, link_to
"My lovely product", product_path(product) .
- ,
. redirect_to product_path,
, , , . link_to product_url,
HTML ,
.

, . <li> .
CSS:
Rails40/depot_i/app/views/carts/show.html.erb
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>

<h2>Your Cart</h2>
<table>
<% @cart.line_items.each do |item| %>
<tr>
<td><%= item.quantity %>&times;</td>
<td><%= item.product.title %></td>
<td class="item_price"><%= number_to_currency(item.total_price) %></td>
</tr>
<% end %>
<tr class="total_line">
<td colspan="2">Total</td>
<td class="total_cell"><%= number_to_currency(@cart.total_price) %></td>
</tr>
</table>

<%= button_to 'Empty cart', @cart, method: :delete,


data: { confirm: 'Are you sure?' } %>

, , LineItem Cart,
, , ,
. ,
:
Rails40/depot_i/app/models/line_item.rb
def total_price
product.price * quantity
end

C a r t Rails-
Array::sum(), , :

150 II
rails40/depot_i/app/models/cart.rb
def total_price
line_items.to_a.sum { |item| item.total_price }
end

carts.css.scss:

rails40/depot_i/app/assets/stylesheets/carts.css.scss
// Place all the styles related to the Carts controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
.carts {

.item_price, .total_line {

text-align: right;

.total_line .total_cell {

font-weight: bold;

border-top: 1px solid #595;

}
}

.10.6.

. 10.6. (Total)


.
:

10 : 151

;; , ;
;; ;
;; - ;
;; ;
;; ;
;; CSS.
, , Information Technology and Golf Weekly. ,
AJAX, , . ,
.


.
, , add_product() Cart
.
, . ,
, product: ruby.
,
.
.
, destroy() LineItemsController.
( : http://www.pragprog.com/wikis/wiki/

RailsPlayTime.)

:
AJAX

11

;
;
AJAX JavaScript;
jQuery UI;
DOM-;
AJAX-.

- AJAX.
AJAX?
( 2005 ) , . , , ,
.
, . ,
.
Depot.
, , ( ?).
.
JavaScript. , JavaScript
, , . (Jesse
James Garrett) AJAX ( -

11 : AJAX 153

Asynchronous JavaScript and XML, ,


).
, . ,
, . AJAX-
.
AJAX ,
, AJAX. .
.

11.1. 1:
show CartController
.html.erb.
, .
, .
.


. :
, .
, ,
, .
(, , ) Rails .
, .
() , . ,
, ,
.
. :
rails40/depot_i/app/views/carts/show.html.erb
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<h2>Your Cart</h2>
<table>
<% @cart.line_items.each do |item| %>
<tr>

154 II
<td><%= item.quantity %>&times;</td>
<td><%= item.product.title %></td>
<td class="item_price"><%= number_to_currency(item.total_price) %></td>
</tr>
<% end %>
<tr class="total_line">
<td colspan="2">Total</td>
<td class="total_cell"><%= number_to_currency(@cart.total_price) %></td>
</tr>
</table>
<%= button_to 'Empty cart', @cart, method: :delete,
data: { confirm: 'Are you sure?' } %>

, . , ,
? ,
( , AJAX).
, ,
.
, :
rails40/depot_j/app/views/carts/show.html.erb
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<h2>Your Cart</h2>
<table>

<%= render(@cart.line_items) %>


<tr class="total_line">
<td colspan="2">Total</td>
<td class="total_cell"><%= number_to_currency(@cart.total_price) %></td>
</tr>
</table>
<%= button_to 'Empty cart', @cart, method: :delete,
data: { confirm: 'Are you sure?' } %>

, . render()
. (
, ,
). , Rails
. , _line_item.html.erb
app/views/line_items.

11 : AJAX 155
rails40/depot_j/app/views/line_items/_line_item.html.erb
<tr>

<td><%= line_item.quantity %>&times;</td>


<td><%= line_item.product.title %></td>
<td class="item_price"><%= number_to_currency(line_item.total_price) %></td>
</tr>

. , , . line_item, , line_item.
, ,
. .
, ,
:
render("cart")

, cart? . @cart,
. ,
, . -
- . , , ,
, ( ).
,
. _cart.html.erb.
carts/show.html.erb, cart @cart . (, .)
rails40/depot_j/app/views/carts/_cart.html.erb
<h2>Your Cart</h2>
<table>

<%= render(cart.line_items) %>

<tr class="total_line">
<td colspan="2">Total</td>
<td class="total_cell"><%= number_to_currency(cart.total_price) %></td>
</tr>

</table>
<%= button_to 'Empty cart', cart, method: :delete,

data: { confirm: 'Are you sure?' } %>

Rails : Dont Repeat Yourselves


(DRY). . , , .
AJAX
, JavaScript .

156 II
,
:
rails40/depot_k/app/views/carts/show.html.erb
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<%= render @cart %>

,
:
rails40/depot_k/app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Pragprog Books Online Store</title>
<%= stylesheet_link_tag "application", media: "all",
"data-turbolinks-track" => true %>
<%= javascript_include_tag "application", "data-turbolinks-track" => true %>
<%= csrf_meta_tags %>
</head>
<body class="<%= controller.controller_name %>">
<div id="banner">
<%= image_tag("logo.png") %>
<%= @page_title || "Pragmatic Bookshelf" %>
</div>
<div id="columns">
<div id="side">

<div id="cart">

<%= render @cart %>

</div>

<ul>

<li><a href="http://www....">Home</a></li>
<li><a href="http://www..../faq">Questions</a></li>
<li><a href="http://www..../news">News</a></li>
<li><a href="http://www..../contact">Contact</a></li>
</ul>
</div>
<div id="main">
<%= yield %>
</div>
</div>
</body>
</html>

.
index , @cart .
:

11 : AJAX 157
rails40/depot_k/app/controllers/store_controller.rb
class StoreController < ApplicationController
include CurrentCart
before_action :set_cart
def index
@products = Product.order(:title)
end
end

, ,
, CartController,
, . SCSS
,
.
rails40/depot_k/app/assets/stylesheets/carts.css.scss
// Place all the styles related to the Carts controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
.carts, #side #cart {
.item_price, .total_line {
text-align: right;
}

.total_line .total_cell {
font-weight: bold;
border-top: 1px solid #595;
}


, , ,
, , .
, ,

:
rails40/depot_k/app/assets/stylesheets/application.css.scss
#side {
float: left;
padding: 1em 2em;
width: 13em;
background: #141;

form, div {
display: inline;
}
input {
font-size: small;
}

158 II

#cart {
font-size: smaller;
color: white;

table {
border-top: 1px dotted #595;
border-bottom: 1px dotted #595;
margin-bottom: 10px;
}

ul {

padding: 0;
li {
list-style: none;
a {
color: #bfb;
font-size: small;
}
}

, ,
.11.1. Webby Award.

. 11.1.


, ,
(Add to Cart).

11 : AJAX 159

. : create :
rails40/depot_k/app/controllers/line_items_controller.rb
def create
@cart = current_cart
product = Product.find(params[:product_id])
@line_item = @cart.add_product(product.id)
respond_to do |format|
if @line_item.save

format.html { redirect_to store_url }


format.json { render action: 'show',
status: :created, location: @line_item }
else
format.html { render action: 'new' }
format.json { render json: @line_item.errors,
status: :unprocessable_entity }
end
end
end

, .
, . ,
, .
- .
AJAX.

11.2. 2:
AJAX-
AJAX , , .
(Add to Cart)
LineItems .
HTML-, ,
, , .
, JavaScript, , , JavaScript (, ,
JavaScript Object Notation (JSON)).
, Rails .
, , Ruby (
Rails).

160 II
AJAX ,
. , AJAX-,
HTML-, .
create
button_to(). , AJAX. remote: true.
rails40/depot_l/app/views/store/index.html.erb
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<h1>Your Pragmatic Catalog</h1>
<% cache ['store', Product.latest] do %>
<% @products.each do |product| %>
<% cache ['entry', product] do %>
<div class="entry">
<%= image_tag(product.image_url) %>
<h3><%= product.title %></h3>
<%= sanitize(product.description) %>
<div class="price_line">
<span class="price"><%= number_to_currency(product.price) %></span>

<%= button_to 'Add to Cart', line_items_path(product_id: product),

remote: true %>


</div>
</div>
<% end %>
<% end %>
<% end %>

AJAX- . .
, HTML-,
, HTML , Document Object Model (DOM).
DOM .
create , JavaScript.
respond_to(), , .js.
, ,
. 4, .
respond_to() 20, .

11 : AJAX 161
rails40/depot_l/app/controllers/line_items_controller.rb
def create
product = Product.find(params[:product_id])
@line_item = @cart.add_product(product.id)

end

respond_to do |format|
if @line_item.save
format.html { redirect_to store_url }
format.js
format.json { render action: 'show',
status: :created, location: @line_item }
else
format.html { render action: 'new' }
format.json { render json: @line_item.errors,
status: :unprocessable_entity }
end
end

, create AJAX-,
Rails create template to render.
Rails , JavaScript, JS
JavaScript. .js.erb JavaScript
, , Ruby .
: create.js.erb. app/
views/line_items, :
rails40/depot_l/app/views/line_items/create.js.erb
$('#cart').html("<%= escape_javascript render(@cart) %>");

, id="cart", HTML.
, .
jQuery $,
jQuery.
$('#cart') jQuery HTML-,
id cart. html()1, .
render()
@cart. escape_
javascript(), Ruby ,
JavaScript.
, . , <%= %>.
? , , , .
, JavaScript.
http://api.jquery.com/html/

162 II
(Add to Cart). ,
.
. AJAX-.


AJAX Rails . ,
AJAX-
.
AJAX.
Depot AJAX,
.
- , , ?
- ,
. , .
- ?
development.log logs. Rails, .

create? , AJAX. JavaScript (
HTML), ,
, JavaScript?
, AJAX
.
Internet Explorer,
quirks mode,
IE, , .
Internet Explorer ,
AJAX , DOCTYPE.
:
<!DOCTYPE html>

-
,
, , 1.0,
AJAX-
2.0. , , .

11 : AJAX 163

, (Add to Cart)
, .
: ,
? , , , ?
, ,
. ? - ,
, 4 5.
, . . ,
, , . ,
- .

11.3. 3:
Rails JavaScript-. ,
jQuery UI1, - . ( ) Yellow
Fade Technique, .
:
, . Yellow Fade
Technique .11.2:
.
(Add to Cart) 2, . .

. 11.2. Yellow Fade Technique


1

http://jqueryui.com/

164 II
jQuery UI .
Gemfile.
rails40/depot_m/Gemfile
# Use jquery as the JavaScript library
gem 'jquery-rails'
gem 'jquery-ui-rails'

gem-, bundle install:


$ bundle install

, .
, jQuery-UI ,
, .
app/assets/javascripts/application.js.
rails40/depot_m/app/assets/javascripts/application.js
// This is a manifest file that'll be compiled into application.js, which will
// include all the files listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts,
// vendor/assets/javascripts, or vendor/assets/javascripts of plugins, if any,
// can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at
// the bottom of the compiled file.
//
// Read Sprockets README
// (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery.ui.effect-blind
//= require jquery_ujs
//= require turbolinks
//= require_tree .

2 assets/stylesheets/application.css.
, JavaScript,
. ,
, , ,
.

( , ).
, - ,
.
, . <tr>- .

11 : AJAX 165

. LineItemsController.
, :
rails40/depot_m/app/controllers/line_items_controller.rb
def create
@cart = current_cart
product = Product.find(params[:product_id])
@line_item = @cart.add_product(product.id)
respond_to do |format|
if @line_item.save
format.html { redirect_to store_url }

format.js { @current_item = @line_item }


format.json { render action: 'show',
status: :created, location: @line_item }
else
format.html { render action: 'new' }
format.json { render json: @line_item.errors,
status: :unprocessable_entity }
end
end
end

_line_item.html.erb ,
, .
, id current_item:
rails40/depot_m/app/views/line_items/_line_item.html.erb
<% if line_item == @current_item %>
<tr id="current_item">
<% else %>
<tr>
<% end %>

<td><%= line_item.quantity %>&times;</td>


<td><%= line_item.product.title %></td>
<td class="item_price"><%= number_to_currency(line_item.total_price) %></td>
</tr>

<tr>
, , id="current_item". JavaScript
, , .
create.js.erb:
rails40/depot_m/app/views/line_items/create.js.erb
$('#cart').html("<%= escape_javascript render(@cart) %>");

$('#current_item').css({'background-color':'#88ff88'}).
animate({'background-color':'#114411'}, 1000);

166 II
, ,
, '#current_item' $?
css() ,
animate() , 1000 , .
(Add to
Cart), , , , .

11.4. 4:

. , . , , - ?
, !
. , , HTML-
, - .
_cart:

<% unless cart.line_items.empty? %>


<div class="cart_title">Your Cart</div>
<table>
<%= render(cart.line_items) %>
<tr class="total_line">
<td colspan="2">Total</td>
<td class="total_cell"><%= number_to_currency(cart.total_price) %></td>
</tr>
</table>

<%= button_to 'Empty cart', cart, method: :delete,


confirm: 'Are you sure?' %>
<% end %>

,
:
. .
.
jQuery UI
. blind show(),
,
.
, .js.erb. create
- , ,

11 : AJAX 167

, ( , ,
, ).
, ,
.
:
rails40/depot_n/app/views/line_items/create.js.erb
if ($('#cart tr').length == 1) { $('#cart').show('blind', 1000); }

$('#cart').html("<%= escape_javascript render(@cart) %>");


$('#current_item').css({'background-color':'#88ff88'}).
animate({'background-color':'#114411'}, 1000);

, , .
.
,
HTML. , ,
- HTML-
,
, ,
blind.
, HTML-
CSS- display: none, .
application.html.erb, app/views/
layouts. :
<div id="cart"
<% if @cart.line_items.empty? %>
style="display: none"
<% end %>
>
<%= render(@cart) %>
</div>

<div> CSS- style=, ,


. , . > , (
), , , .
. , ,
.


- (
), .

168 II
app, .
depot> dir app /w
[.]
[..]
[mailers]
[models]

[assets]
[views]

[controllers] [helpers]

helpers. , ,

depot> dir app\helpers /w


application_helper.rb line_items_helper.rb store_helper.rb
carts_helper.rb products_helper.rb

Rails
(products store). Rails (,
) application_helper.rb.
,
,
, ,
.
hidden_div_if(),
, . ,
<div>, display: none,
. :
rails40/depot_n/app/views/layouts/application.html.erb
<%= hidden_div_if(@cart.line_items.empty?, id: 'cart') do %>
<%= render @cart %>
<% end %>


, application_helper.rb app/
helpers:
rails40/depot_n/app/helpers/application_helper.rb
module ApplicationHelper
def hidden_div_if(condition, attributes = {}, &block)

if condition

attributes["style"] = "display: none"

end

content_tag("div", attributes, &block)


end
end

Rails
content_tag(), , . &block, Ruby
, hidden_div_if(),
content_tag().

11 : AJAX 169

, -, , . ,
. . ,
AJAX ,
. , , , ,
.
rails40/depot_n/app/controllers/carts_controller.rb
def destroy
@cart.destroy
session[:cart_id] = nil
respond_to do |format|

format.html { redirect_to store_url }


format.json { head :no_content }
end
end

, AJAX-, .
, ,
. -, , CSS- display
. -, JavaScript, blind,
.
,
. .
, ? , JQuery
.

11.5. 5:

, , ,
, (
). onClick, ,
.
, , ,
,
(Add to Cart) .
:

170 II
rails40/depot_n/app/views/store/index.html.erb
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<h1>Your Pragmatic Catalog</h1>
<% cache ['store', Product.latest] do %>
<% @products.each do |product| %>
<% cache ['entry', product] do %>
<div class="entry">
<%= image_tag(product.image_url) %>
<h3><%= product.title %></h3>
<%= sanitize(product.description) %>
<div class="price_line">
<span class="price"><%= number_to_currency(product.price) %></span>
<%= button_to 'Add to Cart', line_items_path(product_id: product),
remote: true %>
</div>
</div>
<% end %>
<% end %>
<% end %>

, ,

app/assets/javascripts/store.js.coffee:

rails40/depot_n/app/assets/javascripts/store.js.coffee
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
$(document).on "ready page:change", ->

$('.store .entry > img').click ->

$(this).parent().find(':submit').click()

CoffeeScript1 ,
. CoffeeScript JavaScript
. JQuery , .
, .
: ->,
on,
: ready page:change. ready , , page:change
(turbolinks)2, -
. .
http://jashkenas.github.com/coffee-script/
https://github.com/rails/turbolinks/blob/master/README.md#turbolinks

1
2

11 : AJAX 171


, class="entry", , ,
class="store".
, , , Rails
JavaScript .

,
.
. , , this.
, div
class="entry".
.

, .11.1. -. . ,
.
, JavaScript,
,
50% .
, CoffeeScript. CoffeeScript: Accelerated JavaScript
Development1.
, . , ,
. , ,
:
depot> rake test
.....E...F.EEEE.........EEEE..

, . . , . .

11.6. ,
AJAX
:
ActionView::Template::Error: undefined method 'line_items' for nil:NilClass

Trevor Burnham. CoffeeScript: Accelerated JavaScript Development. The Pragmatic Bookshelf, Raleigh, NC and Dallas, TX, 2011.

172 II
, ,
. ,
, , http://localhost:3000/
products/, , . 11.3.

. 11.3.

. ,
(app/views/layouts/
application.html.erb), , , , , .
, @cart.
line_items , 'line_items' nil
(undefined method 'line_items' for nil).
, @cart, , nil.
,
. ,
, :
rails40/depot_o/app/views/layouts/application.html.erb
<% if @cart %>

<%= hidden_div_if(@cart.line_items.empty?, id: 'cart') do %>


<%= render @cart %>
<% end %>
<% end %>

11 : AJAX 173

, . ,
. . ,
11.1, .
, ,
, :
rails40/depot_o/test/controllers/line_items_controller_test.rb
test "should create line_item" do
assert_difference('LineItem.count') do
post :create, product_id: products(:ruby).id
end

assert_redirected_to store_path
end

.
, .
,
. , Depot.
.
.
AJAX-, , , ,
(Add to Cart). Rails
.
should create line item,

AJAX: should create line item via ajax:
rails40/depot_o/test/controllers/line_items_controller_test.rb
test "should create line_item via ajax" do
assert_difference('LineItem.count') do
xhr :post, :create, product_id: products(:ruby).id
end

end

assert_response :success
assert_select_jquery :html, '#cart' do
assert_select 'tr#current_item td', /Programming Ruby 1.9/
end

,
(xhr :post post, xhr XML-HttpRequest), . ,
HTML- , HTML- current_item, , Programming
Ruby 1.9. assert_select_jquery()

174 II
HTML
HTML , .
, CoffeeScript. , , , ,
, .
:
rails40/depot_o/test/controllers/store_controller_test.rb
test "markup needed for store.js.coffee is in place" do
get :index
assert_select '.store .entry > img', 3
assert_select '.entry input[type=submit]', 3
end

, - , , , . ,
:submit CSS, jQuery,
input[type=submit].
, ,
. Rails .
. ,
, .


AJAX.
;; . create .
;; LineItemsController.create()
AJAX remote: true.
;; ERb-, JavaScript-, . HTML-
, jQuery.
;; , ,
,
jQuery-UI.
;; , , jQuery ,
.
;; , ,
,
.

11 : AJAX 175

, AJAX- .
, AJAX-
. AJAX , ,
. ,
:
AJAX, .
. -, AJAX-, , ,
JavaScript, ,
(DOM),
Firebug Firefox, Developer Tools Internet Explorer, Developer Tools
Google Chrome, Web Inspector Safari Dragonfly Opera. -,
NoScript, Firefox,
JavaScript .
, JavaScript , .
- ,
, ,
JavaScript.


.

. blind- jQuery UI?
,
, , .
AJAX, AJAX.
( http://www.pragprog.com/wikis/wiki/RailsPlay

Time.)

12

;
belongs_to, has_many :through;
(form_for);
, ;
RSS- atom_helper,
.

.
,
. . .
. ,
,
. ,
. , .

12.1. 1:
, ,
.
, line_items, line_items

12 : 177

order_id orders
, .5.3, .
order line_items:
depot> rails generate scaffold Order name address:text email pay_type
depot> rails generate migration add_order_to_line_item order:references

, .
, string. , Rails ,
, .
, :
depot> rake db:migrate
== CreateOrders: migrating =======================================
-- create_table(:orders)
-> 0.0014s
== CreateOrders: migrated (0.0015s) ==============================
== AddOrderIdToLineItem: migrating ===============================
-- add_column(:line_items, :order_id, :integer)
-> 0.0008s
== AddOrderIdToLineItem: migrated (0.0009s) ======================


schema_migrations, db:migrate .
, ,
.

, ,
.
(Checkout). ,
new :
rails40/depot_o/app/views/carts/_cart.html.erb
<h2>Your Cart</h2>
<table>
<%= render(cart.line_items) %>
<tr class="total_line">
<td colspan="2">Total</td>
<td class="total_cell"><%= number_to_currency(cart.total_price) %></td>
</tr>
</table>

178 II
<%= button_to "Checkout", new_order_path, method: :get %>

<%= button_to 'Empty cart', cart, method: :delete,


confirm: 'Are you sure?' %>

, - .
. ,
.
rails40/depot_o/app/controllers/orders_controller.rb
class OrdersController < ApplicationController

include CurrentCart

before_action :set_cart, only: [:new, :create]


before_action :set_order, only: [:show, :edit, :update, :destroy]

end

# GET /orders
#...

, .
, , . .
return,
double render error,
, .
rails40/depot_o/app/controllers/orders_controller.rb
def new
if @cart.line_items.empty?
redirect_to store_url, notice: "Your cart is empty"
return
end

end

@order = Order.new

: ?
, , ,
. .
.
Rails, .
25.1 Active Merchant,
, .

, requires
item in cart, should get new, :

12 : 179
rails40/depot_o/test/controllers/orders_controller_test.rb
test "requires item in cart" do

get :new

assert_redirected_to store_path

assert_equal flash[:notice], 'Your cart is empty'


end

test "should get new" do


item = LineItem.new
item.build_cart
item.product = products(:ruby)
item.save!
session[:cart_id] = item.cart.id
get :new
assert_response :success
end

, , orders: ,
. , Rails, .
Rails,
new , -,
.
HTML- :
, ,
.
Order
@order. , , .
: ,
. - . ,
. , . ,
, , .

.
, , , , ,
.
, Rails .
.
.
, :

1 <%= form_for @order do |f| %>


2
<p>

180 II
3
<%= f.label :name, "Name:" %>
4
<%= f.text_field :name, size: 40 %>
5
</p>
6 <% end %>

. ,
form_for() HTML-.
. , @order, ,
.
, form_for Ruby- ( ). (, <p>).
( f).
, . form_for,
@order.
. , Rails , .
form_for ,
( text_field). .12.1.

. 12.1. form_for

, , , .
new , new.html.erb app/views/orders:
rails40/depot_o/app/views/orders/new.html.erb
<div class="depot_form">
<fieldset>

12 : 181
<legend>Please Enter Your Details</legend>
<%= render 'form' %>
</fieldset>
</div>

_form:
rails40/depot_o/app/views/orders/_form.html.erb
<%= form_for(@order) do |f| %>
<% if @order.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@order.errors.count, "error") %>
prohibited this order from being saved:</h2>
<ul>
<% @order.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>

<%= f.text_field :name, size: 40 %>


</div>
<div class="field">
<%= f.label :address %><br>

<%= f.text_area :address, rows: 3, cols: 40 %>


</div>
<div class="field">
<%= f.label :email %><br>

<%= f.email_field :email, size: 40 %>


</div>
<div class="field">
<%= f.label :pay_type %><br>

<%= f.select :pay_type, Order::PAYMENT_TYPES,

prompt: 'Select a payment method' %>


</div>
<div class="actions">

<%= f.submit 'Place Order' %>


</div>
<% end %>

Rails HTML- . ,
text_field, email_field text_area. 21.2 .
. ,
Order.
, order.rb
:

182 II
rails40/depot_o/app/models/order.rb
class Order < ActiveRecord::Base

PAYMENT_TYPES = [ "Check", "Credit card", "Purchase order" ]


end

select.
:prompt, ,
.
CSS:
rails40/depot_o/app/assets/stylesheets/application.css.scss
.depot_form {
fieldset {
background: #efe;
legend {
color: #dfd;
background: #141;
font-family: sans-serif;
padding: 0.2em 1em;
}
}
form {
label {
width: 5em;
float: left;
text-align: right;
padding-top: 0.2em;
margin-right: 0.1em;
display: block;
}
select, textarea, input {
margin-left: 0.5em;
}
.submit {
margin-left: 4em;
}
br {
display: none
}
}
}

. -
(Checkout).
- , . 12.2.
! new, . Order
.
, .

12 : 183

. 12.2.

, - , ,
, . ,
, .

. , , .
rails40/depot_o/app/models/order.rb
class Order < ActiveRecord::Base
# ...

validates :name, :address, :email, presence: true

validates :pay_type, inclusion: PAYMENT_TYPES


end

,
, @order.errors, , .
, :
rails40/depot_o/test/fixtures/orders.yml
# Read about fixtures at
# http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html
one:

name: Dave Thomas


address: MyText
email: dave@example.org
pay_type: Check

184 II
two:

name: MyString
address: MyText
email: MyString
pay_type: MyString

, , ,
:
rails40/depot_o/test/fixtures/line_items.yml
# Read about fixtures at
# http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html
one:
product: ruby

order: one
two:

product: ruby
cart: one

, . ,
.


create(),
.
1. Order.
2. .
3. . ,
.
4. , ,
, .
:
rails40/depot_o/app/models/line_item.rb
class LineItem < ActiveRecord::Base

belongs_to :order
belongs_to :product
belongs_to :cart
def total_price
product.price * quantity
end
end

12 : 185

, ,
, , :
rails40/depot_o/app/models/order.rb
class Order < ActiveRecord::Base
has_many :line_items, dependent: :destroy
# ...
end

:
rails40/depot_o/app/controllers/orders_controller.rb
def create
@order = Order.new(order_params)

@order.add_line_items_from_cart(@cart)

end

respond_to do |format|
if @order.save
Cart.destroy(session[:cart_id])
session[:cart_id] = nil
format.html { redirect_to store_url, notice:
'Thank you for your order.' }
format.json { render action: 'show', status: :created,
location: @order }
else
@cart = current_cart
format.html { render action: 'new' }
format.json { render json: @order.errors,
status: :unprocessable_entity }
end
end

Order,
. ,
, .
( , ) . ( ).
, .
, .
redirect_to()
. , .
create add_
line_items_from_cart(), :

186 II
rails40/depot_p/app/models/order.rb
class Order < ActiveRecord::Base
# ...

def add_line_items_from_cart(cart)

cart.line_items.each do |item|

item.cart_id = nil

line_items << item

end

end
end

, ,
. cart_id nil,
.
line_items . ,
, order_id
. Rails
, has_many() belongs_to(), Order LineItem. line_items
Rails.
: ?
, Order :
new create. , .
: new Order ,
. ,
, , Ruby. .
create Order, .
.
, : ,
, ,
- , . , , ,
save().

, :
rails40/depot_p/test/functional/orders_controller_test.rb
test "should create order" do
assert_difference('Order.count') do
post :create, order: { address: @order.address, email: @order.email,
name: @order.name, pay_type: @order.pay_type }
end

assert_redirected_to store_path
end

12 : 187

, , (Place Order)

. , , .12.3.
- , .12.4,
(Place Order), , . ?
.
depot> sqlite3 -line db/development.sqlite3
SQLite version 3.7.4
Enter ".help" for instructions
sqlite> select * from orders;
id = 1
name = Dave Thomas
address = 123 Main St
email = customer@example.com
pay_type = Check
created_at = 2013-01-29 02:31:04.964785
updated_at = 2013-01-29 02:31:04.964785
sqlite> select * from line_items;
id = 10
product_id = 2
cart_id =
created_at = 2013-01-29 02:30:26.188914
updated_at = 2013-01-29 02:31:04.966057
quantity = 1
price = 36
order_id = 1
sqlite> .quit

. 12.3. !

188 II
(
, ),
, .

. 12.4.

, AJAX-
, -: .
JavaScript,
, , . , - . ,
( , JavaScript ). , :
<div>, -,
.
: Atom?
RSS-,
RSS 1.0, RSS 2.0 Atom, 2000, 2002 2005 .

12 : 189
. , RSS- ,
,
.
Ruby ,
, RSS.
.
Rails ,
RSS- Atom.
(IETF) -
, Rails atom_feed,
, Rails
, .

rails40/depot_p/app/views/line_items/create.js.erb
$('#notice').hide();

if ($('#cart tr').length == 1) { $('#cart').show('blind', 1000); }


$('#cart').html("<%= escape_javascript render(@cart) %>");
$('#current_item').css({'background-color':'#88ff88'}).
animate({'background-color':'#114411'}, 1000);

, -
, HTML- id, notice, . , id, notice, ,
jQuery . hide()
, . , .
. RSS-,
Atom.

12.2. 2: Atom-
RSS-, Atom, ,

. Rails ,
, .
:
rails40/depot_p/app/controllers/products_controller.rb
def who_bought
@product = Product.find(params[:id])

190 II
@latest_order = @product.orders.order(:updated_at).last
if stale?(@latest_order)
respond_to do |format|
format.atom
end
end
end

, . , 8.5, 5: ,
,
? , , .
, ,
, .
,
RSS-.
, , ,
, ,
ETag. , ,
.
Rails, . , Rails
. .
if.
format.atom, Rails who_
bought.atom.builder. XML, Builder,
RSS- Atom, atom_feed:
rails40/depot_p/app/views/products/who_bought.atom.builder
atom_feed do |feed|
feed.title "Who bought #{@product.title}"
feed.updated @latest_order.try(:updated_at)
@product.orders.each do |order|
feed.entry(order) do |entry|
entry.title "Order #{order.id}"
entry.summary type: 'xhtml' do |xhtml|
xhtml.p "Shipped to #{order.address}"
xhtml.table do
xhtml.tr do
xhtml.th 'Product'
xhtml.th 'Quantity'
xhtml.th 'Total Price'
end

12 : 191
order.line_items.each do |item|
xhtml.tr do
xhtml.td item.product.title
xhtml.td item.quantity
xhtml.td number_to_currency item.total_price
end
end

end

end

end

end

xhtml.tr do
xhtml.th 'total', colspan: 2
xhtml.th number_to_currency \
order.line_items.map(&:total_price).sum
end

xhtml.p "Paid by #{order.pay_type}"


end
entry.author do |author|
author.name order.name
author.email order.email
end

, Builder, 24.1 XML Builder.


: . ,
updated_at Rails
.
,
. , . .
, .
, (products) (orders)
(line_items) :
rails40/depot_p/app/models/product.rb
class Product < ActiveRecord::Base
has_many :line_items

has_many :orders, through: :line_items


#...
end

, . XHTML,
, . , (pay_type).

192 II
, .
HTTP GET-
( , ), (
):
rails40/depot_p/config/routes.rb
Depot::Application.routes.draw do
resources :orders
resources :line_items
resources :carts
get "store/index"

end

resources :products do
get :who_bought, on: :member
end
#
#
#
#
#

The priority is based upon order of creation:


first created -> highest priority.
See how all your routes lay out with "rake routes".
You can have the root of your site routed with "root"
...

:
depot> curl --silent http://localhost:3000/products/3/who_bought.atom
<?xml version="1.0" encoding="UTF-8"?>
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
<id>tag:localhost,2005:/products/3/who_bought</id>
<link type="text/html" href="http://localhost:3000" rel="alternate"/>
<link type="application/atom+xml"
href="http://localhost:3000/info/who_bought/3.atom" rel="self"/>
<title>Who bought Programming Ruby 1.9</title>
<updated>2013-01-29T02:31:04Z</updated>
<entry>
<id>tag:localhost,2005:Order/1</id>
<published>2013-01-29T02:31:04Z</published>
<updated>2013-01-29T02:31:04Z</updated>
<link rel="alternate" type="text/html"
href="http://localhost:3000/orders/1"/>
<title>Order 1</title>
<summary type="xhtml">
<div xmlns="http://www.w3.org/1999/xhtml">
<p>Shipped to 123 Main St</p>
<table>
...
</table>
<p>Paid by check</p>
</div>
</summary>
<author>
<name>Dave Thomas</name>

12 : 193
<email>customer@pragprog.com</email>
</author>
</entry>
</feed>

.
RSS-.
, . , ,
. ,
, ,
- . ( : - Rails.)


.
;; ,
.
;; .
;; RSS-,
.


.
, who_bought HTML-,
XML- JSON-. XML- @product.to_
xml(include: :orders). JSON.
, (Checkout) , ?
?
Order. ?
-
?
( http://www.pragprog.com/wikis/wiki/

RailsPlayTime.)

13

:

.

-, RSS-,
. - .

.
. , .

, . ,
,
.

13.1. 1:

Rails :
, , .
.

13 : 195


Rails, Depot::Application.configure.
,
, environment.rb config;
config/
environments.
. , :
config.action_mailer.delivery_method = :smtp

:smtp :sendmail :test.


:smtp :sendmail , ,
Action Mailer.

.
:test ,
. ,
( ActionMailer::Base.
deliveries). . , :smtp. , Rails
, .
,
development.rb config/environments, :
Depot::Application.configure do
config.action_mailer.delivery_method = :test
end

:sendmail , /usr/sbin. ,
sendmail
. sendmail -i -t.
,
:smtp. , Action Mailer, SMTP-
.
, -, (, -, Rails
).
.
.

196 II
Gmail. ,
.
Depot::Application.configure do
config.action_mailer.delivery_method = :smtp

end

config.action_mailer.smtp_settings = {
address:
"smtp.gmail.com",
port:
587,
domain:
"domain.of.sender.net",
authentication:
"plain",
user_name:
"dave",
password:
"secret",
enable_starttls_auto: true
}

, ,
.


, .
, , Rails -
. Rails ,
app/mailers. ,
. ( ,
HTML XML). ,
-.
: ,
. rails generate mailer ,
, :
depot> rails generate mailer OrderNotifier received shipped
create app/mailers/order_notifier.rb
invoke erb
create
app/views/order_notifier
create
app/views/order_notifier/received.text.erb
create
app/views/order_notifier/shipped.text.erb
invoke
test_unit
create
test/mailers/order_notifier_test.rb

, OrderNotifier app/mailers , , app/views/

13 : 197
notifier. ( , -

.)
. , . ,
OrderNotifier :
rails40/depot_q/app/mailers/order_notifier.rb
class OrderNotifier < ActionMailer::Base

default from: 'Sam Ruby <depot@example.com>'


# Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup:
#
# en.order_notifier.received.subject
#
def received
@greeting = "Hi"
mail to: "to@example.org"
end
# Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup:
#
# en.order_notifier.shipped.subject
#
def shipped
@greeting = "Hi"

end

end

mail to: "to@example.org"

, ,
. . render() mail().
, :to ( ), :cc,
:from :subject,
, . ,
mail ,
, :from .
.
, , , (subject) , 15
: .
:subject.
, ,
,
.

198 II


- app/views/order_notifier , OrderNotifier.
.erb.
( HTML ). ,
. , received.text.erb,
.
:
rails40/depot_q/app/views/order_notifier/received.text.erb
() <%= @order.name %>
Pragmatic Store .
:
<%= render @order.line_items -%>
,
.

, ,
.
, ,
truncate():
rails40/depot_q/app/views/line_items/_line_item.text.erb
<%= sprintf("%2d x %s",
line_item.quantity,
truncate(line_item.product.title, length: 50)) %>

received()
OrderNotifier:
rails40/depot_r/app/mailers/order_notifier.rb
def received(order)
@order = order

end

mail to: order.email, subject: ' Pragmatic Store'

order received,

mail(), ,
.

13 : 199


()
.
rails40/depot_r/app/controllers/orders_controller.rb
def create
@order = Order.new(order_params)
@order.add_line_items_from_cart(@cart)

end

respond_to do |format|
if @order.save
Cart.destroy(session[:cart_id])
session[:cart_id] = nil
OrderNotifier.received(@order).deliver
format.html { redirect_to store_url, notice:
'Thank you for your order.' }
format.json { render action: 'show', status: :created,
location: @order }
else
format.html { render action: 'new' }
format.json { render json: @order.errors,
status: :unprocessable_entity }
end
end

shipped() ,
received():
rails40/depot_r/app/mailers/order_notifier.rb
def shipped(order)
@order = order
end

mail to: order.email, subject: ' Pragmatic Store '

,
.
.


-
, - HTML. Rails , , (
) , .

200 II

. received received.text.
erb. Rails .
HTML.
.
- , :
rails40/depot_r/app/views/order_notifier/shipped.html.erb
<h3> Pragmatic Order </h3>
<p>
, :
</p>
<table>
<tr><th colspan="2">-</th><th></th></tr>
<%= render @order.line_items -%>
</table>

, , :
rails40/depot_r/app/views/line_items/_line_item.html.erb
<% if line_item == @current_item %>
<tr id="current_item">
<% else %>
<tr>
<% end %>
<td><%= line_item.quantity %>&times;</td>
<td><%= line_item.product.title %></td>
<td class="item_price"><%= number_to_currency(line_item.total_price) %></td>
</tr>

. ,
, ,
Rails ,
,
.
, ,
, Rails .

- , ,
order_notifier_test.rb test/mailers.

13 : 201

,
.
, ,
:
rails40/depot_r/test/mailers/order_notifier_test.rb
require 'test_helper'
class OrderNotifierTest < ActionMailer::TestCase
test "received" do

mail = OrderNotifier.received(orders(:one))

assert_equal " Pragmatic Store", mail.subject

assert_equal ["dave@example.org"], mail.to

assert_equal ["depot@example.com"], mail.from

assert_match /1 x Programming Ruby 1.9/, mail.body.encoded


end

end

test "shipped" do
mail = OrderNotifier.shipped(orders(:one))
assert_equal " Pragmatic Store ", mail.subject
assert_equal ["dave@example.org"], mail.to
assert_equal ["depot@example.com"], mail.from
assert_match /<td>1&times;<\/td>\s*<td>Programming Ruby 1.9<\/td>/,
mail.body.encoded
end

( )
, .
assert_match()
. ,
default :from OrderNotifier.
, , , , , ,
. .
:
?
Action Mailer Rails-, . ,
.
. Action
Mailer receive(), .
Mail::Message, .
, ()
.

202 II

.
Rails- runner , ,
, . , ,
procmail, ,
. procmail,
, (subject) Bug Report,
runner:
RUBY=/opt/local/bin/ruby
TICKET_APP_DIR=/Users/dave/Work/depot
HANDLER='IncomingTicketHandler.receive(STDIN.read)'
:0 c
* ^Subject:.*Bug Report.*
| cd $TICKET_APP_DIR && $RUBY bin/rails runner $HANDLER
receive() Action Mailer. , Mail,
Mail receive(), .

13.2. 32:

Rails , , . ,
, .

-. ,
,

. ,
, ,
.

. -
( ),
, ,
.
, ,
. -
, .

.

13 : 203

, ,
.
, :
. , . ,
. , ,
, , .
, ,
.
.
. ,
, ..
Rails
. ,
.
depot> rails generate integration_test user_stories
invoke test_unit
create test/integration/user_stories_test.rb

, Rails
_test.
:
require 'test_helper'
class UserStoriesTest < ActionDispatch::IntegrationTest
# test "the truth" do
#
assert true
# end
end

. ,
products.
, :
fixtures :products

. ,
, orders, line_items,
. ,
Ruby, :
rails40/depot_r/test/integration/user_stories_test.rb
LineItem.delete_all
Order.delete_all
ruby_book = products(:ruby)

204 II
:
.
rails40/depot_r/test/integration/user_stories_test.rb
get "/"
assert_response :success
assert_template "index"

.
get. ,
get() .
,
URL-
.
: , . ,
Ajax-,
xml_http_request(). ,
:
rails40/depot_r/test/integration/user_stories_test.rb
xml_http_request :post, '/line_items', product_id: ruby_book.id
assert_response :success
cart = Cart.find(session[:cart_id])
assert_equal 1, cart.line_items.size
assert_equal ruby_book, cart.line_items[0].product

:
.... :
Rails40/depot_r/test/integration/user_stories_test.rb
get "/orders/new"
assert_response :success
assert_template "new"

.
,
. HTTP,
save_order ,
. , . post_via_redirect() post-,
,
.
rails40/depot_r/test/integration/user_stories_test.rb
post_via_redirect "/orders",
order: { name:
address:

"Dave Thomas",
"123 The Street",

13 : 205
email:
pay_type:

assert_response :success
assert_template "index"
cart = Cart.find(session[:cart_id])
assert_equal 0, cart.line_items.size

"dave@example.com",
"Check" }

, .
orders, ,
:
rails40/depot_r/test/integration/user_stories_test.rb
orders = Order.all
assert_equal 1, orders.size
order = orders[0]
assert_equal
assert_equal
assert_equal
assert_equal

"Dave Thomas", order.name


"123 The Street", order.address
"dave@example.com", order.email
"Check", order.pay_type

assert_equal 1, order.line_items.size
line_item = order.line_items[0]
assert_equal ruby_book, line_item.product

, ,
:
rails40/depot_r/test/integration/user_stories_test.rb
mail = ActionMailer::Base.deliveries.last
assert_equal ["dave@example.com"], mail.to
assert_equal 'Sam Ruby <depot@example.com>', mail[:from].value
assert_equal "Pragmatic Store Order Confirmation", mail.subject

.
:
rails40/depot_r/test/integration/user_stories_test.rb
require 'test_helper'
class UserStoriesTest < ActionDispatch::IntegrationTest
fixtures :products
#
#
#
#

A user goes to the index page. They select a product, adding it to their
cart, and check out, filling in their details on the checkout form. When
they submit, an order is created containing their information, along with a
single line item corresponding to the product they added to their cart.

test "buying a product" do


LineItem.delete_all
Order.delete_all
ruby_book = products(:ruby)

206 II
get "/"
assert_response :success
assert_template "index"
xml_http_request :post, '/line_items', product_id: ruby_book.id
assert_response :success
cart = Cart.find(session[:cart_id])
assert_equal 1, cart.line_items.size
assert_equal ruby_book, cart.line_items[0].product
get "/orders/new"
assert_response :success
assert_template "new"
post_via_redirect "/orders",
order: { name:
"Dave Thomas",
address:
"123 The Street",
email:
"dave@example.com",
pay_type:
"Check" }
assert_response :success
assert_template "index"
cart = Cart.find(session[:cart_id])
assert_equal 0, cart.line_items.size
orders = Order.all
assert_equal 1, orders.size
order = orders[0]
assert_equal
assert_equal
assert_equal
assert_equal

"Dave Thomas",
"123 The Street",
"dave@example.com",
"Check",

order.name
order.address
order.email
order.pay_type

assert_equal 1, order.line_items.size
line_item = order.line_items[0]
assert_equal ruby_book, line_item.product

end

end

mail = ActionMailer::Base.deliveries.last
assert_equal ["dave@example.com"], mail.to
assert_equal 'Sam Ruby <depot@example.com>', mail[:from].value
assert_equal "Pragmatic Store Order Confirmation", mail.subject

,
, . 25.3
RailsPlugins.org, , ,
,
.

13 : 207

,
,
Depot.


,
;; Rails-: ,

;
;; , , HTML ;
;;
, , ,
.


:
orders ship_date ( ) ,
OrdersController.
,
- , ,
10.2 2: .
.

14

;
;
;
rails;
;
Active Record.

, ,
.
. . ,
,
-.
, ,
.
, . .

14 : 209

14.1. 1:
,
.
, . ,
, ,

.
depot> rails generate scaffold User name:string password:digest

Rails, 3.1.0, wiki1,


, - ,
, has_secure_password().
(password) digest, , Rails. , , :
depot> rake db:migrate

user:
rails40/depot_r/app/models/user.rb
class User < ActiveRecord::Base
validates :name, presence: true, uniqueness: true
has_secure_password
end

(
).
has_secure_password().
, , , ,
, ?
has_secure_password(): Rails .
,
password:digest.
gem- bcrypt-ruby
Gemfile.
rails40/depot_r/Gemfile
# Use ActiveModel has_secure_password
gem 'bcrypt-ruby', '~> 3.0.0'

gem-.
depot> bundle install

, .

http://pragprog.com/wikis/wiki/ChangesToRails

210 II
:
, .


, .
.
. : index(),
show(), new(), edit(), update() delete(). Rails
. ,
show(), .
.
-.
rails40/depot_r/app/controllers/users_controller.rb
def create
@user = User.new(user_params)
respond_to do |format|
if @user.save

format.html { redirect_to users_url,

notice: " #{@user.name} ." }


format.json { render action: 'show',
status: :created, location: @user }
else
format.html { render action: 'new' }
format.json { render json: @user.errors,
status: :unprocessable_entity }
end
end
end

:
def update
respond_to do |format|
if @user.update(user_params)

format.html { redirect_to users_url,

notice: " #{@user.name} ." }


format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: @user.errors,
status: :unprocessable_entity }
end
end
end

, , , :

14 : 211
def index
@users = User.order(:name)
end

. ,
:
rails40/depot_r/app/views/users/index.html.erb
<h1>Listing users</h1>

<% if notice %>

<p id="notice"><%= notice %></p>

<% end %>


<table>
<thead>

<tr>
<th>Name</th>
<th></th>
<th></th>
<th></th>
</tr>

</thead>
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= user.name %></td>
<td><%= link_to 'Show', user %></td>
<td><%= link_to 'Edit', edit_user_path(user) %></td>
<td><%= link_to 'Destroy', user, method: :delete,
data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New User', new_user_path %>

, , , .
, . , legend fieldset.
, <div> class,
.
rails40/depot_r/app/views/users/_form.html.erb
<div class="depot_form">
<%= form_for @user do |f| %>
<% if @user.errors.any? %>

212 II
<div id="error_explanation">
<h2><%= pluralize(@user.errors.count, "error") %>
, :</h2>
<ul>
<% @user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<fieldset>
<legend> </legend>
<div class="field">
<%= f.label :name, 'Name:' %>
<%= f.text_field :name, size: 40 %>
</div>
<div class="field">
<%= f.label :password, ':' %>:
<%= f.password_field :password, size: 40 %>
</div>
<div class="field">
<%= f.label :password_confirmation, ':' %>:
<%= f.password_field :password_confirmation, size: 40 %>
</div>
<div class="actions">
<%= f.submit %>
</div>
</fieldset>
<% end %>
</div>

. http://localhost:3000/users/new.
.14.1.
(Create User)
-:
depot> sqlite3 -line db/development.sqlite3 "select * from users"
id = 1
name = dave
password_digest = $2a$10$lki6/oAcOW4AWg4A0e0T8uxtri2Zx5g9taBXrd4mDSDVl3rQRWRNi
created_at = 2013-01-29 14:40:06.230622
updated_at = 2013-01-29 14:40:06.230622

, ,
.
create():

14 : 213
rails40/depot_r/test/controllers/users_controller_test.rb

test "should create user" do


assert_difference('User.count') do
post :create, user: { name: 'sam', password: 'secret',
password_confirmation: 'secret' }
end

end

assert_redirected_to users_path

. 14.1.

update(),
.
test "should update user" do
patch :update, id: @user, user: { name: @user.name, password: 'secret',
password_confirmation: 'secret' }
assert_redirected_to users_path
end

, ,
.
rails40/depot_r/test/fixtures/users.yml
# Read about fixtures at
# http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html
one:
name: dave
password_digest: <%= BCrypt::Password.create('secret') %>

214 II
two:
name: susannah
password_digest: <%= BCrypt::Password.create('secret') %>

,
, password_digest. ,
, Rails .
, , , ,
.

14.2. 2:

?
,
.
- ( ,
).
,
,
.
,
:
.
depot> rails generate controller Sessions new create destroy
depot> rails generate controller Admin index

SessionsController#create , ,
. User :user_id.
:
rails40/depot_r/app/controllers/sessions_controller.rb
def create

user = User.find_by(name: params[:name])

if user and user.authenticate(params[:password])

session[:user_id] = user.id

redirect_to admin_url

14 : 215
else

end

end

redirect_to login_url, alert: " "

- : ,
. , ,
sessions#new:
rails40/depot_r/app/views/sessions/new.html.erb
<div class="depot_form">
<% if flash[:alert] %>
<p id="notice"><%= flash[:alert] %></p>
<% end %>
<%= form_tag do %>
<fieldset>
<legend>, </legend>
<div>
<%= label_tag :name, ':' %>
<%= text_field_tag :name, params[:name] %>
</div>
<div>
<%= label_tag :password, ':' %>
<%= password_field_tag :password, params[:password] %>
</div>
<div>
<%= submit_tag "Login" %>
</div>
</fieldset>
<% end %>
</div>

, . form_for form_tag,
- HTML- <form>. text_field_tag password_field_tag, ,
HTML- <input>.
. , , , .
params ,
. params
.
.
label_tag HTML-
<label>. .
, .

216 II
.14.2 ,
params: params[:name], ,
,
.

. 14.2. ,

,
.
,
.
, , , :
rails40/depot_r/app/controllers/sessions_controller.rb
def destroy

session[:user_id] = nil

redirect_to store_url, notice: " "


end

14 : 217

, , ,
.

, . index.html.erb
app/views/admin. ( pluralize(),
order orders
, .)
rails40/depot_r/app/views/admin/index.html.erb
<h1> </h1>
<%= Time.now %>
<%= pluralize(@total_orders, "order") %>.

index():
rails40/depot_r/app/controllers/admin_controller.rb
class AdminController < ApplicationController
def index

@total_orders = Order.count
end
end

, .
,
, , , . , ,
, Rails ,
GET-, POST- ..

config/routes.rb:
rails40/depot_r/config/routes.rb
Depot::Application.routes.draw do

get 'admin' => 'admin#index'

controller :sessions do

get 'login' => :new

post 'login' => :create

delete 'logout' => :destroy

end
get "sessions/create"
get "sessions/destroy"
resources :users
resources :orders
resources :line_items
resources :carts
get "store/index"
resources :products do

218 II

end

get :who_bought, on: :member


end
# The priority is based upon order of creation:
# first created -> highest priority.
# See how all your routes lay out with "rake routes".
# You can have the root of your site routed with "root"
root to: 'store#index', as: 'store'
# ...

, root 8.1,
1: .
get . ( ) , sessions/new, sessions/
create sessions/destroy.
admin URL-,
( /index), .
, , URL- (
session/create login),
HTTP-. , login , new create,
: HTTP GET HTTP POST.
:
controller(). . ,
, 20.1
.
,
,
.14.3.

. 14.3.

, :

14 : 219
rails40/depot_r/test/functional/sessions_controller_test.rb
require 'test_helper'
class SessionsControllerTest < ActionController::TestCase
test "should get new" do
get :new
assert_response :success
end

test "should login" do


dave = users(:one)
post :create, name: dave.name, password: 'secret'
assert_redirected_to admin_url
assert_equal dave.id, session[:user_id]
end

test "should fail login" do


dave = users(:one)
post :create, name: dave.name, password: 'wrong'
assert_redirected_to login_url
end

test "should logout" do


delete :destroy
assert_redirected_to store_url
end

end

, ,
(
).

14.3. 3:
,
. , Rails (callback).
Rails ,
, , , . , ,
admin.
session[:user_id]. , , , . ,
, .
? admin, ,

220 II
, ApplicationController
. application_controller.rb app/controllers. ,
,
.
rails40/depot_r/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base

before_action :authorize
# ...

protected

end

end

def authorize
unless User.find_by(id: session[:user_id])
redirect_to login_url, notice: ", "
end

before_action() authorize() .
. , .
, . ,
- .
, , .
skip_before_action() StoreController:
rails40/depot_r/app/controllers/store_controller.rb
class StoreController < ApplicationController

skip_before_action :authorize

SessionsController:
rails40/depot_r/app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

skip_before_action :authorize

. ,
:
rails40/depot_r/app/controllers/carts_controller.rb
class CartsController < ApplicationController

skip_before_action :authorize, only: [:create, :update, :destroy]


# ...
private
# ...

14 : 221

end

def invalid_cart
logger.error "Attempt to access invalid cart #{params[:id]}"
redirect_to store_url, notice: 'Invalid cart'
end

:
rails40/depot_r/app/controllers/line_items_controller.rb
class LineItemsController < ApplicationController

skip_before_action :authorize, only: :create

( new):
rails40/depot_r/app/controllers/orders_controller.rb
class OrdersController < ApplicationController

skip_before_action :authorize, only: [:new, :create]

, http://
localhost:3000/products .

.
,
,
. , ,
, test_helper setup().
, ,

login_as() logout().
rails40/depot_r/test/test_helper.rb
class ActiveSupport::TestCase
# ...
# Add more helper methods to be used by all tests here...
def login_as(user)
session[:user_id] = users(user).id
end
def logout
session.delete :user_id
end

end

def setup
login_as :one if defined? session
end

, setup() login_as(),
.

222 II
, .
:
, ? , ?
, !

14.4. 4:


,
:user_id:
rails40/depot_r/app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Pragprog Books Online Store</title>
<%= stylesheet_link_tag "application", media: "all",
"data-turbolinks-track" => true %>
<%= javascript_include_tag "application", "data-turbolinks-track" => true %>
<%= csrf_meta_tag %>
</head>
<body class="<%= controller.controller_name %>">
<div id="banner">
<%= image_tag("logo.png") %>
<%= @page_title || "Pragmatic Bookshelf" %>
</div>
<div id="columns">
<div id="side">
<% if @cart %>
<%= hidden_div_if(@cart.line_items.empty?, id: 'cart') do %>
<%= render @cart %>
<% end %>
<% end %>
<ul>
<li><a href="http://www....">Home</a></li>
<li><a href="http://www..../faq">Questions</a></li>
<li><a href="http://www..../news">News</a></li>
<li><a href="http://www..../contact">Contact</a></li>
</ul>

<% if session[:user_id] %>


<ul>
<li><%= link_to 'Orders', orders_path %></li>
<li><%= link_to 'Products', products_path %></li>

14 : 223
<li><%= link_to 'Users', users_path %></li>
</ul>
<%= button_to 'Logout', logout_path, method: :delete %>
<% end %>
</div>
<div id="main">
<%= yield %>
</div>
</div>
</body>
</html>

.
, , . , - .

, .14.4,
, dave, . , , , .
.
,
. , .
:
, .
,
. rails console, Rails
Ruby irb, Rails-. ,
,
Ruby .

user,
.
depot> rails console
Loading development environment.
>> User.create(name: dave, password: secret,
password_confirmation: secret)
=> #<User:0x2933060 @attributes={...} ... >
>> User.count
=> 1

224 II

. 14.4.

>>
. User,
, , , , .
Rails ( ,
).
, . ?
. , , .
: - ,
. . .
, , , .
, Active Record.
: validate Active
Record . , Active Record
, . after_
destroy(), SQL- delete.
,
, , ,

14 : 225

delete, , , .
:
rails40/depot_s/app/models/user.rb
after_destroy :ensure_an_admin_remains
private
def ensure_an_admin_remains
if User.count.zero?
raise " "
end
end

. .
-, ,
. , users
,
, .
-, ,
begin-end , . , ,
ActiveRecord::Rollback, ,
ActiveRecord::Base.transaction.
rails40/depot_s/app/controllers/users_controller.rb
def destroy
begin
@user.destroy
flash[:notice] = " #{@user.name} "
rescue StandardError => e
flash[:notice] = e.message
end

respond_to do |format|
format.html { redirect_to users_url }
format.json { head :no_content }
end

:
,
.
, .
.
.
, , , ,

226 II
,
. 26.3 RailsPlugins.org.


.
;; has_secure_password .
;;

authorize().
;; , rails console ( ,
, ).
;; , .


.
,
, .
,
, , .
, ,
.
,
( ).
rails console.
, .
, , false returned.
:
>> prd = Product.new
=> #<Product id: nil, title: nil, description: nil, image_url:
nil, created_at: nil, updated_at: nil, price:

14 : 227
#<BigDecimal:246aa1c,'0.0',4(8)>>
>> prd.save
=> false
>> prd.errors.full_messages
=> ["Image url must be a URL for a GIF, JPG, or PNG image",
"Image url can't be blank", "Price should be at least 0.01",
"Title can't be blank", "Description can't be blank"]

authenticate_or_request_with_http_basic()
:authorize, request.format
Mime::HTML.
RSS- Atom:
curl --silent --user dave:secret \
http://localhost:3000/products/2/who_bought.atom


, , , . ,
logout()
, .
( http://www.pragprog.com/wikis/wiki/
RailsPlayTime.)

15

:
;
I18n.

,
, , ,
. - , - ,
. .
, . , ,
. . ,
,
-. .
: ,
?
, . , Rails-
. , , , ,
, . ,
, ,
, , ,
.

15 : 229

, . , , ,
, .
,
!

15.1. 1:
, , .
rails40/depot_s/config/initializers/i18n.rb
#encoding: utf-8
I18n.default_locale = :en
LANGUAGES = [
['English', 'en'],
["Espa&ntilde;ol".html_safe, 'es']
]

.
, I18n . I18n , , internationalization ().
internationalization i, n
.
. ,
, , espaol , .
, -.
, UTF-8.
HTML- n .
, .
html_safe Rails ,
HTML.
Rails ,
.
, , en- es ( ),
URL-. , , ,
, .
config/
routes.rb:

230 II
rails40/depot_s/config/routes.rb
Depot::Application.routes.draw do
get 'admin' => 'admin#index'
controller :sessions do
get 'login' => :new
post 'login' => :create
delete 'logout' => :destroy
end
get "sessions/create"
get "sessions/destroy"
resources :users
resources :products do
get :who_bought, on: :member
end

end

scope '(:locale)' do
resources :orders
resources :line_items
resources :carts
root 'store#index', as: 'store', via: :all
end

:locale. , :locale
, . ,
,
.
, http://localhost:3000/
(English), ,
http://localhost:3000/en. http://localhost:3000/es
, ,
.
config.routes,
.
: Rails
. http://
localhost:3000/rails/info/routes, , .15.1,
. , , rake routes 20, REST:
.
, .

15 : 231

before_action default_
url_options.
, ApplicationController.

. 15.1.
rails40/depot_s/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base

before_action :set_i18n_locale_from_params
# ...
protected

def set_i18n_locale_from_params

if params[:locale]

if I18n.available_locales.map(&:to_s).include?(params[:locale])

I18n.locale = params[:locale]

else

flash.now[:notice] =

"#{params[:locale]} translation not available"

logger.error flash.now[:notice]

end

end

end

end

def default_url_options
{ locale: I18n.locale }
end

set_i18n_locale_from_params : ,

232 II
, ,
. , .
default_url_options ,
, URL-, , .
:locale. , , , ,
, . , .
, . 15.2.

. 15.2.

. 15.3.

15 : 233


-, , /en. ,
, ,
( . 15.3), , , .
, .

15.2. 2:
. ,
. , , I18n.
translate. I18n.t,
t.
,
. , t, , ,
. .
rails40/depot_s/app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Pragprog Books Online Store</title>
<%= stylesheet_link_tag "application", media: "all",
"data-turbolinks-track" => true %>
<%= javascript_include_tag "application", "data-turbolinks-track" => true %>
<%= csrf_meta_tags %>
</head>
<body class="<%= controller.controller_name %>">
<div id="banner">
<%= image_tag("logo.png") %>

<%= @page_title || t('.title') %>


</div>
<div id="columns">
<div id="side">
<% if @cart %>
<%= hidden_div_if(@cart.line_items.empty?, id: 'cart') do %>
<%= render @cart %>
<% end %>
<% end %>
<ul>

<li><a href="http://www...."><%= t('.home') %></a></li>


<li><a href="http://www..../faq"><%= t('.questions') %></a></li>
<li><a href="http://www..../news"><%= t('.news') %></a></li>
<li><a href="http://www..../contact"><%= t('.contact') %></a></li>
</ul>
<% if session[:user_id] %>

234 II
<ul>

<li><%= link_to 'Orders', orders_path %></li>


<li><%= link_to 'Products', products_path %></li>
<li><%= link_to 'Users', users_path %></li>
</ul>
<%= button_to 'Logout', logout_path, method: :delete %>
<% end %>
</div>
<div id="main">
<%= yield %>
</div>
</div>
</body>
</html>

layouts/application.html.erb,
en.layouts.application.
:
rails40/depot_s/config/locales/en.yml
en:
layouts:
application:
title:
home:
questions:
news:
contact:

"Pragmatic Bookshelf"
"Home"
"Questions"
"News"
"Contact"

:
rails40/depot_s/config/locales/es.yml
es:
layouts:
application:
title:
home:
questions:
news:
contact:

"Publicaciones de Pragmatic"
"Inicio"
"Preguntas"
"Noticias"
"Contacto"

YAML . YAML
, ,
.
Rails YAML-, .
.15.4 ,
.

15 : 235

. 15.4. :

, Add to Cart( ). :
rails40/depot_s/app/views/store/index.html.erb
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<h1><%= t('.title_html') %></h1>
<% cache ['store', Product.latest] do %>
<% @products.each do |product| %>
<% cache ['entry', product] do %>
<div class="entry">
<%= image_tag(product.image_url) %>
<h3><%= product.title %></h3>
<%= sanitize(product.description) %>
<div class="price_line">
<span class="price"><%= number_to_currency(product.price) %></span>

<%= button_to t('.add_html'), line_items_path(product_id: product),


remote: true %>
</div>
</div>
<% end %>
<% end %>
<% end %>

,
:
rails40/depot_s/config/locales/en.yml
en:
store:

236 II
index:
title_html:
add_html:

"Your Pragmatic Catalog"


"Add to Cart"

:
rails40/depot_s/config/locales/es.yml
es:
store:
index:
title_html:
add_html:

"Su Cat&aacute;logo de Pragmatic"


"A&ntilde;adir al Carrito"

, , title_html add_html _html, , HTML


, .
,
. , Rails . Rails , html
( , .html.)
HTML.
, ,
.15.5.

. 15.5.

, :

15 : 237
rails40/depot_s/app/views/carts/_cart.html.erb
<h2><%= t('.title') %></h2>

<table>
<%= render(cart.line_items) %>
<tr class="total_line">
<td colspan="2">Total</td>
<td class="total_cell"><%= number_to_currency(cart.total_price) %></td>
</tr>
</table>

<%= button_to t('.checkout'), new_order_path, method: :get %>


<%= button_to t('.empty'), cart, method: :delete,

data: { confirm: 'Are you sure?' } %>

:
rails40/depot_s/config/locales/en.yml
en:
carts:
cart:
title:
empty:
checkout:

"Your Cart"
"Empty cart"
"Checkout"

rails40/depot_s/config/locales/es.yml
es:
carts:
cart:
title:
empty:
checkout:

"Carrito de la Compra"
"Vaciar Carrito"
"Comprar"

, , (. 15.6).
.
, .
.
,
,
, () ,
USD $US.
.
, .
, ,
. , Rails ,

238 II
, . en:

. 15.6. Carrito bonita ( )


rails40/depot_s/config/locales/en.yml
en:
number:
currency:
format:
unit:
precision:
separator:
delimiter:
format:

"$"
2
"."
","
"%u%n"

es:
rails40/depot_s/config/locales/es.yml
es:
number:
currency:
format:
unit:
precision:
separator:
delimiter:
format:

"$US"
2
","
"."
"%n&nbsp;%u"

15 : 239

(unit), (precision),
(separator) (delimiter)
number.currency.format. . (format) : %n , &nbsp;
,
, %u .
.15.7.

. 15.7. Mas dinero, por favor ( , )

15.3. 3:
. :
rails40/depot_s/app/views/orders/new.html.erb
<div class="depot_form">
<fieldset>

<legend><%= t('.legend') %></legend>


<%= render 'form' %>
</fieldset>
</div>

, :
rails40/depot_s/app/views/orders/_form.html.erb
<%= form_for(@order) do |f| %>
<% if @order.errors.any? %>
<div id="error_explanation">

240 II
<h2><%= pluralize(@order.errors.count, "error") %>
prohibited this order from being saved:</h2>
<ul>
<% @order.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name, size: 40 %>
</div>
<div class="field">

<%= f.label :address, t('.address_html') %><br>


<%= f.text_area :address, rows: 3, cols: 40 %>
</div>
<div class="field">
<%= f.label :email %><br>
<%= f.email_field :email, size: 40 %>
</div>
<div class="field">
<%= f.label :pay_type %><br>
<%= f.select :pay_type, Order::PAYMENT_TYPES,

prompt: t('.pay_prompt_html') %>


</div>
<div class="actions">

<%= f.submit t('.submit') %>


</div>
<% end %>

:
rails40/depot_s/config/locales/en.yml
en:
orders:
new:

legend:
form:
name:
address_html:
email:
pay_type:
pay_prompt_html:
submit:

"Please Enter Your Details"


"Name"
"Address"
"E-mail"
"Pay with"
"Select a payment method"
"Place Order"

rails40/depot_s/config/locales/es.yml
es:
orders:
new:

15 : 241
legend:
form:
name:
address_html:
email:
pay_type:
pay_prompt_html:
submit:

"Por favor, introduzca sus datos"


"Nombre"
"Direcci&oacute;n"
"E-mail"
"Forma de pago"
"Seleccione un m&eacute;todo de pago"
"Realizar Pedido"

.15.8.

. 15.8.

Realizar Pedido , .15.9.

, Active Record, ,
:
rails40/depot_s/config/locales/es.yml
es:
activerecord:
errors:
messages:
inclusion:
blank:

"no est&aacute; incluido en la lista"


"no puede quedar en blanco"

errors:
template:
body:

"Hay problemas con los siguientes campos:"

242 II
header:
one:
"1 error ha impedido que este %{model} se guarde"
other: "%{count} errores han impedido que este %{model} se guarde"

, , ,
: errors.template.header.one , , errors.template.header.other ,
.

.

. 15.9.

HTML-, , ( Rails
). .
:
rails40/depot_t/app/views/orders/_form.html.erb
<%= form_for(@order) do |f| %>
<% if @order.errors.any? %>
<div id="error_explanation">

<h2><%=raw t('errors.template.header', count: @order.errors.count,

model: t('activerecord.models.order')) %>.</h2>

<p><%= t('errors.template.body') %></p>

<ul>
<% @order.errors.full_messages.each do |msg| %>
<li><%=raw msg %></li>
<% end %>
</ul>
</div>

15 : 243
<% end %>
<!-- ... -->

,
( ).
, . 15.10.

. 15.10.

, .
,
. .
YAML-:
rails40/depot_t/config/locales/es.yml
es:
activerecord:
models:
order: "pedido"
attributes:
order:
address:
name:
email:
pay_type:

"Direcci&oacute;n"
"Nombre"
"E-mail"
"Forma de pago"

, , Rails.

244 II
.15.11;
, Thank you for your
order ( ).

. 15.11.

-:
rails40/depot_t/app/controllers/orders_controller.rb
def create
@order = Order.new(order_params)
@order.add_line_items_from_cart(@cart)

end

respond_to do |format|
if @order.save
Cart.destroy(session[:cart_id])
session[:cart_id] = nil
OrderNotifier.received(@order).deliver
format.html { redirect_to store_url, notice:
I18n.t('.thanks') }
format.json { render action: 'show', status: :created,
location: @order }
else
@cart = current_cart
format.html { render action: 'new' }
format.json { render json: @order.errors,
status: :unprocessable_entity }
end
end

, :

15 : 245
rails40/depot_t/config/locales/en.yml
en:
thanks: "Thank you for your order"

rails40/depot_t/config/locales/es.yml
es:
thanks: "Gracias por su pedido"

.15.12.

. 15.12.

15.4. 4:

, .
, image_
tag:
rails40/depot_t/app/views/layouts/application.html.erb
<div id="banner">

<%= form_tag store_path, class: 'locale' do %>

246 II
<%= select_tag 'set_locale',
options_for_select(LANGUAGES, I18n.locale.to_s),
onchange: 'this.form.submit()' %>
<%= submit_tag 'submit' %>
<%= javascript_tag "$('.locale input').hide()" %>
<% end %>
<%= image_tag("logo.png") %>
<%= @page_title || t('.title') %>
</div>

form_tag ,
. class
CSS.
select_tag
, locale. ,
LANGUAGES, , , (
I18n). onchange,
. , JavaScript,
.
submit_tag , JavaScript . , JavaScript
, JavaScript,
, ,
.

, ,
:set_locale:
rails40/depot_t/app/controllers/store_controller.rb
def index

if params[:set_locale]

redirect_to store_url(locale: params[:set_locale])

else

@products = Product.order(:title)

end
End

CSS-:
rails40/depot_t/app/assets/stylesheets/application.css.scss
.locale {
float: right;
margin: -0.25em 0.1em;
}

.15.13.
.

15 : 247

, .
, .
.

. 15.13.


.
;; .
;; ,
, .
;; , I18n, t()
.


.
locale products
,
. , -

248 II
, .
, .

, ES_es.
Order::PAYMENT_TYPES, . ( )
. .
( http://www.pragprog.com/wikis/wiki/

RailsPlayTime.)

16

-;
MySQL;
Bundler Git
Capistrano.

.
, ,
, . ,
, . Wired magazine,
.
, .
, ,
.16.1.
,
- . ,
- WEBRick . SQLite3,
gem- . Git-,

250 II
,
gem-.
Git ,
, .

. 16.1.

, Apache httpd Phusion


Passenger. MySQL,
.
Capistrano ,

.
!
, . Depot Apache,
MySQL Passenger -,
.
Git, Bundler Capistrano.
. , , ,
, .
, , .

, , .

. , !

16 : 251

16.1. 1:
Phusion Passenger
MySQL
Rails-
, , WEBrick.
. rails server

3000. Rails- .
Rails-
. , , .
, Rails
.
:
Microsoft Windows?
Windows ,
Rails
Unix, Linux Mac OS X. ,
Phusion Passenger, Ruby on Rails
.

Linux Mac OS X.

. -,
Apache, Lighttpd Zeus, , . - Ruby, ,
. , .
Rails-
, Apache,
, . HTTP - Passenger
, Rails,
.


, . , . , VirtualBox Ubuntu. Ubuntu,
12.04 LTS.

252 II
, 1,
Rails. , Rails
Bundler.
$ gem install bundler

, Depot,
.
Bundler ,
.
$ bundle install

,
:
$ rake about
$ rake test
$ rails server

. , ,
.
, , ,
, , .
,
, ,
.

Passenger
,
- Apache. Linux
Apache 1.3 Linux. ,
Mac OS X, ,
. Mac OS 10.8 System PreferencesSharing Web Sharing. Mac
OS X 10.8 Terminal.
$ sudo apachectl start
$ sudo launchctl load -w /System/Library/LaunchDaemons/org.apache.httpd.plist

Passenger:
$ gem install passenger --version 4.0.8
$ passenger-install-apache2-module

, , . , Ubuntu 13.04 (Raring


Ringtail) , libcurl4-openssl-dev, apache2-prefork-dev,

16 : 253
libapr1-dev libaprutil1-dev. , -

Passenger .
,
.
Apache-.
,
Apache-.

Passenger , ,
, , . LoadModule, .
, Passenger.

LoadModule passenger_module /home/rubys/.rvm/.../ext/apache2/mod_passenger.so


PassengerRoot /home/rubys/.rvm/gems/ruby-2.0.0-p0/gems/passenger-4.0.1
PassengerDefaultRuby /home/rubys/.rvm/wrappers/ruby-2.0.0-p0/ruby

, Apache,
:
$ apachectl -V | grep HTTPD_ROOT
$ apachectl -V | grep SERVER_CONFIG_FILE

apache2ctl, httpd. , .
,
, . Mac OS X, , httpd.conf
:
Include /private/etc/apache2/other/*.conf

httpd.conf, , Passenger, passenger.conf,


. Ubuntu /etc/apache2/conf .d/
passenger.


.
,
.
ServerName :
<VirtualHost *:80>
ServerName depot.yourhost.com

254 II
DocumentRoot /home/rubys/deploy/depot/public/
<Directory /home/rubys/deploy/depot/public>
AllowOverride all
Options MultiViews
Order allow,deny
Allow from all
</Directory>
</VirtualHost>

, DocumentRoot

public Rails- , public .

Apache
. Mac OS X httpd.conf
( ):
#Include /private/etc/apache2/extra/httpd-vhosts.conf

, , dummy-host.example.com .
Ubuntu
/etc/apache2/sitesavailable . ,
depot, :
sudo a2ensite depot

,
VirtualHost , ServerName
DocumentRoot . , :
NameVirtualHost *:80

, ,
Listen 80.
- Apache:
$ sudo apachectl restart

, .
/etc/hosts. Windows- C:\
windows\system32\drivers\etc\. ,
, .
/etc/hosts :
127.0.0.1 depot.yourhost.com

! , ( ). URL-
, , 80.
.

16 : 255

,
(The address or port is invalid), , NameVirtualHost , ,
. ,
,
.
, ,
RailsEnv VirtualHost
Apache-:
RailsEnv development


Apache restart.txt tmp :
$ touch tmp/restart.txt

passenger-install-apache2-module ,
.

MySQL
- SQLite1 , . , SQLite -
. , , -
.
SQLite, , . MySQL.
Linux OS X
- MySQL2.
Mac OS X 10.7 (x86, 64-) DMG Archive
10.8. ,
No thanks, just take me to the downloads! (,
, !).
MySQL,
Gemfile gem- mysql:
rails40/depot_t/Gemfile
group :production do
gem 'mysql2'
end

1
2

http://www.sqlite.org/whentouse.html
http://dev.mysql.com/downloads/mysql/

256 II
gem- production,
. ,
gem- sqlite3 development test.
gem- bundle install.
MySQL
. , Ubuntu
libmysqlclient-dev.

mysql , , phpmyadmin
CocoaMySQL, :
depot>
mysql>
mysql>
->
mysql>

mysql -u root
CREATE DATABASE depot_production DEFAULT CHARACTER SET utf8;
GRANT ALL PRIVILEGES ON depot_production.*
TO 'username'@'localhost' IDENTIFIED BY 'password';
EXIT;

, ,

. .
config/database.yml .
,
, .
:
production:
adapter: sqlite3
database: db/production.sqlite3
pool: 5
timeout: 5000

- :
production:
adapter: mysql2
encoding: utf8
reconnect: false
database: depot_production
pool: 5
username: username
password: password
host: localhost

, ,
.


:
depot> rake db:setup RAILS_ENV="production"

16 : 257

. ,
:
-- create_table("carts", {:force=>true})
-> 0.1722s
-- create_table("line_items", {:force=>true})
-> 0.1255s
-- create_table("orders", {:force=>true})
-> 0.1171s
-- create_table("products", {:force=>true})
-> 0.1172s
-- create_table("users", {:force=>true})
-> 0.1255s
-- initialize_schema_migrations_table()
-> 0.0006s
-- assume_migrated_upto_version(20121130000008, "db/migrate")
-> 0.0008s

- , ! ,
, . .
, production:
database.yml. ,
( mysqladmin - ).
, database.yml , .
, .
. MySQL, :
depot> mysql depot_production
mysql>

,
- dummy? ( , .)
mysql> create table dummy(i int);
mysql> drop table dummy;

, rake
db:migrate , database.yml.
socket:, ,
(#).
(No
such file or directory) , mysql.sock, , Ruby- MySQL
MySQL. ,
,

258 II
,
- MySQL. , Ruby- MySQL. ,
, socket: database.yml
MySQL- .
MySQL (Mysql not loaded),
, Ruby- MySQL.
Rails 2.5.
,
, (Client does not support authentication protocol requested by server),
MySQL-.
MySQL , , , - http://dev.mysql.com/doc/mysql/en/old-client.html,
MySQL- set password for 'some_user'@'some_host' =
OLD_PASSWORD('newpwd');.
Windows MySQL Cygwin, , localhost. 127.0.0.1.
, database.yml.
YAML, ,
.
, . ( , Ruby Python,
Python ?)
rake db:setup , .
, .
. Rails
, .
, . , . ,

.
.

16.2. 2:
Capistrano
, ,
, , ,

16 : 259

,
. ,
,
,
.
, .


, ,
. , , Capistrano,
.
(software configuration management SCM). , Subversion. ,
Git,
. Git,
SCM-, . Capistrano , , .
, . ,
, ,
, Git-. ,
:
$ mkdir -p ~/git/depot.git
$ cd ~/git/depot.git
$ git --bare init

, : SCM- -
, Capistrano SCM ,
. (
)
:
$ test -e ~/.ssh/id_dsa.pub || ssh-keygen -t dsa
$ cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys

SSH . ,
known_hosts.
, , . Capistrano
current Rails, public. ,

260 II
DocumentRoot Directory httpd.conf,
,
:
DocumentRoot /home/rubys/deploy/depot/current/public/
<Directory /home/rubys/deploy/depot/current/public>

Apache. ,
depot/current/public . ,
.
, , , Gemfile
config/database.yml, Depot
Depot .
!
.


Gemfile, ,
Capistrano.
rails40/depot_t/Gemfile
# Use Capistrano for deployment

gem

'rvm-capistrano', group: :development


. RVM , ,
rvm-.
Capistrano, bundle install. 12, 3 gem- bcryptruby.
,
:
$
$
$
$

cd your_application_directory
git init
git add .
git commit -m "initial commit"

, ,

, .
Bundler,
package. ,
, :
$ bundle package
$ git add Gemfile.lock vendor/cache
$ git commit -m "bundle gems"

16 : 261

Bundler 24.3 Bundler.


:
$ git remote add origin ssh://user@host/~/git/depot.git
$ git push origin master

user host
.
, . , .
, . ,
.


. , .
.
SCM-, .
, , ,
.
Capistrano
, :
$ capify .
[add] writing './Capfile'
[add] writing './config/deploy.rb'
[done] capified!

, Capistrano
. Capfile Capistrano Rakefile.
.
.
rails40/depot_t/Capfile
load 'deploy' if respond_to?(:namespace) # cap2 differentiator
# Uncomment if you are using Rails' asset pipeline

load 'deploy/assets'

load 'config/deploy' # remove this line to skip loading any of the default tasks

, config/deploy.rb, , . Capistrano , ,
:
rails40/depot_t/config/deploy.rb
require 'bundler/capistrano'

262 II
# be sure to change these
set :user, 'rubys'
set :domain, 'depot.pragprog.com'
set :application, 'depot'
# adjust if you are using RVM, remove if you are not
set :rvm_type, :user
set :rvm_ruby_string, 'ruby-2.0.0-p247'
require 'rvm/capistrano'
# file paths
set :repository, "#{user}@#{domain}:git/#{application}.git"
set :deploy_to, "/home/#{user}/deploy/#{application}"
# distribute your applications across servers (the instructions below put them
# all on the same server, defined above as 'domain', adjust as necessary)
role :app, domain
role :web, domain
role :db, domain, :primary => true
# you might need to set this if you aren't seeing password prompts
# default_run_options[:pty] = true
# As Capistrano executes in a non-interactive mode and therefore doesn't cause
# any of your shell profile scripts to be run, the following might be needed
# if (for example) you have locally installed gems or applications. Note:
# this needs to contain the full values for the variables set, not simply
# the deltas.
# default_environment['PATH']='<your paths>:/usr/local/bin:/usr/bin:/bin'
# default_environment['GEM_PATH']='<your paths>:/usr/lib/ruby/gems/1.8'
# miscellaneous options
set :deploy_via, :remote_cache
set :scm, 'git'
set :branch, 'master'
set :scm_verbose, true
set :use_sudo, false
set :rails_env, :production
namespace :deploy do
desc "cause Passenger to initiate a restart"
task :restart do
run "touch #{current_path}/tmp/restart.txt"
end

end

desc "reload the database with seed data"


task :seed do
run "cd #{current_path}; rake db:seed RAILS_ENV=#{rails_env}"
end

after "deploy:update_code", :bundle_install


desc "install the necessary prerequisites"
task :bundle_install, :roles => :app do
run "cd #{release_path} && bundle install"
end

16 : 263

, . , ,
:user, :domain :application. :repository ,
Git-. :deploy_to
, Apache ( ,
public ).
, , Capistrano, RVM1.
Ruby (RVM) , set :rvm_type, :system
:user. :rvm_ruby_string, Ruby, .
RVM, .
default_run_options default_environment , . (miscellaneous options) Git,
,
Rails.
: Capistrano, Passenger,
.
.

:
$ cap deploy:setup

Capistrano . ,
default_run_options deploy.rb . , .
:
$ cap deploy:check

, default_environment deploy.rb.
, , , .
: (seed) , .
$ cap deploy:seed

.
http://beginrescueend.com/integration/capistrano/

264 II

. .
,
. , , ,
. Capistrano. ,
:
$
$
$
$

git
git
git
cap

add .
commit -m "add cap files"
push
deploy

SCM-. Git , ,
,
..
, -
.
- , :
$ cap deploy:rollback

, ,
. , , Passenger
.

16.3. 3:

, , ,
, . .
, , -,
Apache, .
rails console.


, , tail, , .

16 : 265

. Apache
,
production.log .
, , ,
:
#
$ cd /home/rubys/deploy/depot/current
$ tail -f log/production.log

, ,
. ,
.




. ,
. .
Rails.
:
#
$ cd /home/rubys/deploy/depot/current/
$ rails console production
Loading production environment.
irb(main):001:0> p = Product.find_by(title: "CoffeeScript")
=> #<Product:0x24797b4 @attributes={. . .}
irb(main):002:0> p.price = 29.00
=> 29.00
irb(main):003:0> p.save
=> true

,
. ,
. .
, .
, , ,
.

266 II
.

. ,
.
Logger. ,
( )
, config/environments/production.rb,
:
config.logger = Logger.new(config.paths['log'].first, 'daily')

, , :
require 'active_support/core_ext/numeric/bytes'
config.logger = Logger.new(config.paths['log'].first, 10, 10.megabytes)

, active_support,
, Active Support.
, Rails, Active Support:
config.active_support.bare = true


:
config.logger = SyslogLogger.new

- http://rubyonrails.org/

deploy.



. ,
,
, . , , Rails
,
Rails
. ,

Rails.
, .
. ,

16 : 267

SQL- Active Record,


.
: . . .
.
, Depot. ,
, ( ,
).


. ,
,
, -,
, ,
.

.
;; Phusion Passenger Apache httpd, -,
.
;; MySQL, .
;; , Bundler Git.
;; Capistrano, , .


.
,
,
( !)
. ,
database.yml shared , Capistrano
current .
, , ,

268 II
, . Capistrano Git . .
:
rvm rbenv1 ruby-build2;
mysql PostgreSQL3;
Phusion Passenger Apache httpd
Unicorn4 nginx5.
.
.
( http://www.pragprog.com/wikis/wiki/
RailsPlayTime.)



3

4

5

1
2

https://github.com/sstephenson/rbenv/#readme
https://github.com/sstephenson/ruby-build#readme
http://www.postgresql.org/
http://unicorn.bogomips.org/
http://wiki.nginx.org/Main


Depot

17

:
Rails: , , , ,
;
.

! ,
Rails. ,
III. II.

17.1. Rails
3 Rails- , . ,
Depot. , , .

,
, . Depot
: Cart, LineItem, Order, Product User.
id, created_at updated_at.
string (: title,
name), integer (quantity), text (description, address),

270 II
decimal (price) (product_id, cart_id).
,
, password.
has_many belongs_to,
, Carts LineItems
Products.

, .
, .
. , . ,
, , , , ,
. ( , .)
, ,
- . Active Record, , , ,
.
,
,
.
,
.

,
. Rails : (edit), (index), (new)
(show), form,
edit new. ,
.
admin, sessions store.
,
. . JavaScript, 2.0, - .
,
.

17 Depot 271

,
, .
HTML-,
, , , RSS- Atom. ,
,
.

:
, admin, sessions store.
:
, .
,
. HTML, JSON Atom.
, , . , ,
(consern), CurrentCart.
, ( ) ( ).
, . ,
.
RSS- Atom.
.


Rails-, .
, MySQL.
, admin
session - . who_bought (
), RSS- Atom,
.

272 II
(en), (es) .
.
Capistrano ,
.

.
. .
Rails , .
, Ajax, , .
.
, ,
,
.

(Apache httpd),
(MySQL).

Phusion Passenger, Bundler,
Git.
-
Capistrano.

.
SQLite
-, WEBrick.
, .

17.2.

17 Depot 273

Rails
Ruby RDoc1 . ,
, ,
, .
README.rdoc ,
. RDoc,
.
HTML- rake:
depot> rake doc:app

,
doc/app. .17.1.

. 17.1.

, .
Rake .
depot> rake stats
+----------------------+-------+-------+---------+---------+-----+-------+
| Name
| Lines | LOC
| Classes | Methods | M/C | LOC/M |
+----------------------+-------+-------+---------+---------+-----+-------+
| Controllers
| 622
| 382
| 9
| 56
| 6
| 4
|
| Helpers
| 26
| 24
| 0
| 1
| 0
| 22
|
| Models
| 112
| 72
| 5
| 7
| 1
| 8
|

http://rdoc.sourceforge.net/

274 II
| Mailers
| 29
| 11
| 1
| 2
| 2
| 3
|
| Javascripts
| 50
| 5
| 0
| 0
| 0
| 0
|
| Libraries
| 0
| 0
| 0
| 0
| 0
| 0
|
| Controller tests
| 404
| 283
| 8
| 0
| 0
| 0
|
| Helper tests
| 32
| 24
| 8
| 0
| 0
| 0
|
| Model tests
| 130
| 90
| 5
| 2
| 0
| 43
|
| Mailer tests
| 25
| 18
| 1
| 0
| 0
| 0
|
| Integration tests
| 198
| 138
| 2
| 9
| 4
| 13
|
+----------------------+-------+-------+---------+---------+-----+-------+
| Total
| 1628 | 1047 | 39
| 77
| 1
| 11
|
+----------------------+-------+-------+---------+---------+-----+-------+
Code LOC: 494
Test LOC: 553
Code to Test Ratio: 1:1.1

, ,
. ,
.
Rails.

III


Rails


Rails

18

Depot, Rails. Rails (


).
. , ,
.
Rails: Active Record,
Active Resource, Action Pack ( Action Controller, Action View),
Active Support. . Rails
, ,
.
, Rails,
,
Rails.
. , : , .

18.1.
Rails , , ,
. , my_app
rails new my_app, , .18.1,
.

18 Rails 277

. 18.1. Rails

:
- Rails?

Rails, , . ,
Active Record Action View. Rails,
, ,
, . Rails
- .
.
.

.
config.ru Rack Webserver Interface,
Rails Metal, Rail-

278 III Rails


Rack Middlewares.
Rails Guides1.
Gemfile Rails-. gem- bcrypt-ruby Depot. ,
- , .
Rails, . Bundler2 config/application.rb config/
boot.rb.
Gemfile.lock , Rails-.
Bundler .
Rakefile , , .. , rake -T.
, rake
D.
README Rails.
, ( ).


app. .18.2,
app. app ,
Rails, Active Record, Action Controller
Action View.


7.2 2: , 8.4 4: 13.2
32: , Rails , test
, , , , .

http://guides.rubyonrails.org/rails_on_rack.html
https://github.com/carlhuda/bundler

1
2

18 Rails 279

. 18.2. app


, 17.2 , doc ,
, doc/, Rails
Rake- doc:app.
, Rails
, : doc:rails Rails, doc:guides .
gem- redcarpet
Gemfile bundle install.

280 III Rails


Rails ,
. ,
rake -T doc.


lib , , . , ,
PDF, 1.
( send_data()). , PDF,
lib.
lib , , . ,
, - .
, ,
, lib.
,
lib. lib
,
. , - Pragmatic Programmer , ,

PDF, lib/pdf_stuff.
Rails lib
, require.
, .
config/application.rb :
config.autoload_paths += %W(#{Rails.root}/lib)

lib
(autoload_paths),
. ,
Rails . , PDF, receipt.rb
lib/pdf_stuff.
PdfStuff::Receipt, Rails .
,
, Ruby , require. lib, . ,
..., , Pragmatic Programmer.

18 Rails 281

lib/easter.rb, ,
, :
require "easter"

lib, require. ,
:
require "shipping/airmail"

Rake-
lib tasks.
Rake-,
. Rake,
, . Rails
Rake- .
. Rake-, ,
schema_migration. Rake- Ruby,
.rake. db_schema_
migrations.rake:
rails40/depot_t/lib/tasks/db_schema_migrations.rake
namespace :db do
desc " "
task :schema_migrations => :environment do
puts ActiveRecord::Base.connection.select_values(
'select version from schema_migrations order by version' )
end
end

,
Rake-:
depot> rake db:schema_migrations
(in /Users/rubys/Work/...)
20121130000001
20121130000002
20121130000003
20121130000004
20121130000005
20121130000006
20121130000007

Rake-
Rake : http://rubyrake.org/.

282 III Rails


Rails ,
. ( ) log.
, development.log,
test.log production.log. , , .
, ,
( ,
config ).

-
public . -
.
( , ) , ,
.


,
,
, , bin.
bundle binstubs.
Rails. ,
rails. ,
, Rails .
console
Rails-.
dbconsole

.
destroy
,
generate.
generate
. , , , , -.

generate , :
rails generate migration

new

18 Rails 283

Rails-.
runner
. Rails (rails console).
cron- ,
, .
server
Rails- -, Mongrel
( ) WEBrick. Depot .


, , Rails tmp,
. -,
. Rails , ,
- ,
.


vendor .
vendor Rails ,
, 16, .
gem-
, vendor/cache.


config , Rails.
Depot , , , .
Rails .
Rails config/
environment.rb config/application.rb. ,
, (
) .
app/controllers .
app/models.

284 III Rails


vendor lib, plugin.
app, app/helpers, app/mailers, app/services lib.
, .
, Rails .
environments, , .
, Rails , ,
.
, , ..
, , .
.
, , . ,

. 16 : , rake RAILS_ENV, Phusion
Passenger RailsEnv Apache. WEBrick
rails server -e:
depot> rails server -e development
depot> rails server -e test
depot> rails server -e production

, , .

config/environments.
, . , ,
Configuring Rails Applications, doc:guides, .
1.

18.2.
, Rails
. , , Person, Rails ,
http://guides.rubyonrails.org/configuring.html

18 Rails 285

people (.. person). ,


.
, Rails. ,
.

,

.
Ruby ,
, .
-: , ( ) . (
.)
, order_status,
LineItem.
Rails . -, ,
, . -, Rails , .
orders third_parties.
, Rails ,
.
Rails , , . ,
, .
, , Ruby,
LineItem. Rails
:
line_items.
,
;
line_item.rb ( app/models).
Rails . store, .
Rails , StoreController
store_controller.rb app/controllers.
Rails StoreHelper,
store_helper.rb app/helpers.

286 III Rails



app/views/store.

, store.html.
erb store.xml.erb app/views/layouts.
.

line_items

app/models/line_item.rb

LineItem


URL

http://../store/list

app/controllers/store_controller.rb

StoreController

list

app/views/layouts/store.html.erb


URL

http://../store/list

app/views/store/list.html.erb ( .builder)

module StoreHelper

app/helpers/store_helper.rb

. Ruby- , Ruby,
require. Rails
, require
Rails-, , .
Rails
. ( ),
.


app/controllers .
. ,
, ,
.

18 Rails 287

, admin.
Rails .
, , admin/book,
Rails app/controllers/admin book_controller.

_controller.rb, , app/controllers.
:
?
. : (Product)
(products). : (Order has_many :line_items).
,
, . ,
, ,
. .
, Rails ,
. , , ,
.

, (, admin/xxx content/xxx)
book. , book_controller.rb ,
admin, content, app/controllers. BookController. Rails - ,
.
, Rails , app/controllers Ruby,
.
, book admin
class Admin::BookController < ActionController::Base
# ...
end

book content Content:


class Content::BookController < ActionController::Base
# ...
end

,
.
app/views. , ,

288 III Rails


http://my.app/admin/book/edit/1234


app/views/admin/book/edit.html.erb

,
myapp> rails generate controller Admin::Book 1 2 ...


Rails , .
, .
.
;; API-,
Rails.
;; Rake- .
;; ,
Rails.
Rails,
Active Record.


Active Record

19

establish_connection;
, , ;
;
, , ;
.

Active Record , Rails - (ORM).


Rails, .

, Depot. Active Record ,
, , ,
( CRUD-).
, Active Record (
).

19.1.

Depot , Order.
,

290 III Rails


, email,
String. Rails
id, , . Rails , ,
. , Rails
, .
, , Rails
. .



, ActiveRecord::Base,
Order, . Active Record , , ,
.
, , ,
.

Order

orders

LineItem

line_items

TaxAgency

tax_agencies

Person

people

Batch

batches

Datum

data

Diagnosis

diagnoses

Quantity

quantities

Rails: , .
Rails , .
Rails :
rails40/depot_t/config/initializers/inflections.rb
# Be sure to restart your server when you modify this file.
#
#
#
#
#
#
#
#
#

Add new inflection rules using the following format. Inflections


are locale specific, and you may define rules for as many different
locales as you wish. All of these examples are active by default:
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.plural /^(ox)$/i, '\1en'
inflect.singular /^(ox)en/i, '\1'
inflect.irregular 'person', 'people'
inflect.uncountable %w( fish sheep )
end

19 Active Record 291


#
#
#
#

These inflection rules are supported but not enabled by default:


ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym 'RESTful'
end

ActiveSupport::Inflector.inflections do |inflect|
inflect.irregular 'tax', 'taxes'
end


Rails, ,
, table_name
:
class Sheep < ActiveRecord::Base
self.table_name = "sheep"
end

Active Record
.
, . ,
, Order -
orders. , Active Record . ,
, Active Record
, .
Depot orders
:
rails40/depot_r/db/migrate/ 20121130000007_create_orders.rb
class CreateOrders < ActiveRecord::Migration
def change
create_table :orders do |t|
t.string :name
t.text :address
t.string :email
t.string :pay_type
t.timestamps
end
end
end

,
rails console. :
depot> rails console
Loading development environment (Rails 4.0.0)
>> Order.column_names
=> ["id", "name", "address", "email", "pay_type", "created_at", "updated_at"]

pay_type:

292 III Rails


>> Order.columns_hash["pay_type"]
=> #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x00000003618228
@name="pay_type", @sql_type="varchar(255)", @null=true, @limit=255,
@precision=nil, @scale=nil, @type=:string, @default=nil,
@primary=false, @coder=nil>

, Active Record
pay_type. ,
, ,
, null. Rails
Order.
Active Record, , . ,
orders :
depot> sqlite3
id =
name =
address =
email =
pay_type =
created_at =
updated_at =

-line db/development.sqlite3 "select * from orders limit 1"


1
Dave Thomas
123 Main St
customer@example.com
Check
2013-01-29 14:39:12.375458
2013-01-29 14:39:12.375458

: ?

(database administrator, DBA) , ,


. Active Record ,
,
.
. , ,
XML- . , , ,
.
, DRY (Dont
Repeat Yourself). ,
.

, .
,
.

Active Record,
. id 1 ( Fixnum),
name "Dave Thomas" ..

19 Active Record 293

-.
Rails ,
:
o = Order.find(1)
puts o.name
o.name = "Fred Smith"

#=> "Dave Thomas"


#

, .
, , Active Record Ruby ( ,
,
Time). , , _before_type_cast,
:
product.price_before_type_cast
product.updated_at_before_type_cast

#=> 34.95,
#=> "2013-02-13 10:13:14"


read_attribute() write_attribute().
.
SQL Ruby . 19.1.
Decimal Boolean .
19.1. SQL Ruby
SQL

Ruby

SQL

Ruby

int, integer

Fixnum

-float, double

Float

decimal, numeric

BigDecimal

char, varchar, string

String

interval, date

Date

datetime, time

Time

clob, blob, text

String

boolean

Rails Decimals
Fixnum; BigDecimal,
.
Boolean Rails
, :
user = User.find_by(name: "Dave")
if user.superuser?
grant_privileges
end

294 III Rails


, , ,
Rails ,
.

,
Active Record
, Active Record.
.
created_at, created_on, updated_at, updated_on

.
,
. , Rails- _on
_at , .
id
,
(. ).
xxx_id
,
xxx.
xxx_count
xxx.
, act_as_list1, .
, .

19.2.

Depot LineItems ( ) : Cart, Order Product. ,
. Orders Products
LineItems.
.

https://github.com/rails/acts_as_list

19 Active Record 295


Active Record .
. , Order.find(1) Order, , ,
.
Rails- , , - , , id. ,
Active Record ,
.
, , books ISBN.
Active Record, :
class LegacyBook < ActiveRecord::Base
self.primary_key = "isbn"
end

Active Record ,
(,
).
,
, . ,
- id.
Active Record, id. ,
, primary_key=.
id, ,
isbn:
book = LegacyBook.new
book.id = "0-12345-6789"
book.title = "My Great American Novel"
book.save
# ...
book = LegacyBook.find("0-12345-6789")
puts book.title
# => "My Great American Novel"
p book.attributes
#=> {"isbn" =>"0-12345-6789",
#
"title"=>"My Great American Novel"}

, isbn title, id . , id.


.

296 III Rails


Ruby- id() hash(),
. , .
,
( ).
: Rails
( ==),
. , ,
. -
( ), ==.
, id
.


Active Record : ,
. has_one, has_many, belongs_to has_and_belongs_to_many.


(, , )
,
.
(orders) (invoices):
(.19.1).

. 19.1.

, Rails Order
has_one Invoice belongs_to.

19 Active Record 297

: ,
, belongs_to.


. ,
. , ,
, (.19.2).
line_items

orders

id

id

order_id

name

...

...

class LineItem < ActiveRecord::Base


belongs_to :order
#...
end

class Order < ActiveRecord::Base


has_many :line_items
#...
end

. 19.2.

Active Record ( ,
) has_many, , ,
belongs_to. LineItem : belongs_to :order, , orders,
: has_many :line_items.
- : (line item) , belongs_to.


. ,
. . ,
(.19.3).
Rails has_and_belongs_
to_many . ,
, habtm.

298 III Rails

. 19.3.

Rails , . ,
. Active Record , ,
. categories
products, Active Record
categories_products.
. Depot LineItems,
Products Carts, Orders.
,
quantity ().

, , .

19.3. , , ,
(CRUD Create, Read,
Update,Delete)
, SQLite MySQL, ,
Structured Query Language (SQL). Rails
, . ,
SQL-, .
SQL, , Rails ,
select, from, where, group by .. SQL, ,
Rails , ,
.

19 Active Record 299

Order
Depot. Active Record : , ,
.


, Rails , ,
, . ,
, Order.new(). ( ).
save() .
.
rails40/e1/ar/new_examples.rb
an_order = Order.new
an_order.name
= "Dave Thomas"
an_order.email
= "dave@example.com"
an_order.address = "123 Main St"
an_order.pay_type = "check"
an_order.save

Active Record .
, . ,
, .
rails40/e1/ar/new_examples.rb
Order.new do |o|
o.name = "Dave Thomas"
# . . .
o.save
end

, Active Record
, . .
, HTML-
.
rails40/e1/ar/new_examples.rb
an_order = Order.new(
name:
"Dave Thomas",
email:
"dave@example.com",
address:
"123 Main St",
pay_type:
"check")
an_order.save

300 III Rails


,
id. Active Record ,
, id
, .
,
.
rails40/e1/ar/new_examples.rb
an_order = Order.new
an_order.name = "Dave Thomas"
# ...
an_order.save
puts "The ID of this order is #{an_order.id}"

new() Order , . Active Record create(), ,


.
rails40/e1/ar/new_examples.rb
an_order = Order.create(
name:
"Dave Thomas",
email:
"dave@example.com",
address:
"123 Main St",
pay_type:
"check")

create() , ,
:
rails40/e1/ar/new_examples.rb
orders = Order.create(
[ { name:
"Dave Thomas",
email:
"dave@example.com",
address:
"123 Main St",
pay_type:
"check"
},
{ name:
"Andy Hunt",
email:
"andy@example.com",
address:
"456 Gentle Drive",
pay_type:
"po"
} ] )

new() create()
:
@order = Order.new(order_params)

- , ,
. orders_controller.rb Depot.

19 Active Record 301


, ,
, Active Record - ,
, , .

. find(),
.
, , ( ActiveRecord::RecordNotFound,
, ). , find() , .
, RecordNotFound ,
id (
,
id, ):
an_order = Order.find(27) # find the order with id == 27
# id ,
#
product_list = Product.find(params[:product_ids])

- , . Active
Record ,
.
: ?
, . ,
. Person.find(5) people.
id, 5. id, 5,
. ,
Rails RecordNotFound.
, , . Person.where(name: Dave).first ,
( ) :
, Dave. , - . ,
. , ,
nil ,
.

SQL Active Record


Active Record SQL,
where(), SQL- where. ,

302 III Rails


, Dave
'po', :
pos = Order.where("name = 'Dave' and pay_type = 'po'")

ActiveRecord::Relation, ,
Order.
, ,
(, -)?
, , :
#
name = params[:name]
# !!!
pos = Order.where("name = '#{name}' and pay_type = 'po'")

, . ? ,
,
SQL-,
Rails. 18, . ,
SQL-
.
SQL-
Active Record. Active Record
SQL,
. , .
where() , Rails SQL-. SQL
,
.

SQL . , .. , :
name = params[:name]
pos = Order.where(["name = ? and pay_type = 'po'", name])

.
:name, , ,
,
:
name = params[:name]
pay_type = params[:pay_type]
pos = Order.where("name = :name and pay_type = :pay_type",
pay_type: pay_type, name: name)

19 Active Record 303

. params
, . ,
,
:
pos = Order.where("name = :name and pay_type = :pay_type",
params[:order])

. ,
Rails where, ,
.
:
pos = Order.where(params[:order])

:
-, .
, :
pos = Order.where(name: params[:name],
pay_type: params[:pay_type])

, Active Record
, SQL. SQL Active Record
.

Like
like, :
#
User.where("name like '?%'", params[:name])

Rails SQL ,
, . Rails name .
like :
#
User.where("name like ?", params[:name]+"%")

, , ,
, -.


, ,
, ActiveRecord::Relation,
first() all().

304 III Rails


, , , first()
. ,
nil. , to_a()
. ActiveRecord::Relation
Array, each() map().
all().
, ,
.
.
.
order
SQL , -
, . order()
,
order by. , , Dave,
, ( ):
orders = Order.where(name: 'Dave').
order("pay_type, shipped_at DESC")

limit
limit()
.
, . ,
:
orders = Order.where(name: 'Dave').
order("pay_type, shipped_at DESC").
limit(10)

offset
offset() limit(). .
#
#
#
#

, ,
page_size.
,
page_num ( ).

def Order.find_on_page(page_num, page_size)


order(:id).limit(page_size).offset(page_num*page_size)
end

n ,
offset limit.
select
ActiveRecord::Relation , select * from....

19 Active Record 305

select() ,
* select.
, , . , podcasts , -,
, ,
(blob) MP3. , .
select() .
list = Talk.select("title, speaker, recorded_on")

joins
joins() ,
, .
SQL- , ,
, .
.
,
Programming Ruby:
LineItem.select('li.quantity').
where("pr.title = 'Programming Ruby 1.9'").
joins("as li inner join products as pr on li.product_id = pr.id")

readonly
readonly() ActiveRecord::Resource
Active Record, .
joins() select()
readonly.
group
group() group by SQL-:
summary = LineItem.select("sku, sum(amount) as amount").
group("sku")

lock
lock() , SQL,
, . , MySQL
, , ,
, .
, , :
Account.transaction do
ac = Account.where(id: id).lock("LOCK IN SHARE MODE").first

306 III Rails


ac.balance -= amount if ac.balance > amount
ac.save

end

lock()
true, , ( "for
update"). ( 19.5 )
.
,
, ,
. Rails .


Rails
. , :
average
max
min
total
number

=
=
=
=
=

Order.average(:amount)
Order.maximum(:amount)
Order.minimum(:amount)
Order.sum(:amount)
Order.count

, , .
, :
Order.where("amount > 20").minimum(:amount)

. , , ,
, .
group, ,
,
. ,
:
result = Order.group(:state).maximum(:amount)
puts result
#=> {"TX"=>12345, "NC"=>3456, ...}

. ,
( "TX", "NC" ..).
, each(). , .
order limit
.

19 Active Record 307

, , ,
,
:
result = Order.group(:state).
order("max(amount) desc").
limit(3)

, order
SQLite (
max).



. Rails . Active Record Proc-,
:
class Order < ActiveRecord::Base
scope :last_n_days, lambda { |days| where('updated < ?' , days) }
end


:
orders = Order.last_n_days(7)

.
class Order < ActiveRecord::Base
scope :checks, -> { where(pay_type: :check) }
end

.
, :
orders = Order.checks.last_n_days(7)

, ,
. ,
SQL-.
ActiveRecord::Relation :
in_house = Order.where('email LIKE "%@pragprog.com"')

, :
in_house.checks.last_n_days(7)

308 III Rails


where; , limit, order, join ..
, Rails , order
limit,
.
,
. Rails , , , ,
,
.

SQL

SQL-. find_by_sql() .
, SQL- select ( , SQL , find()),
(, )
. , .
select *, .
rails40/e1/ar/find_examples.rb
orders = LineItem.find_by_sql("select line_items.* from line_items, orders
" +
"
where order_id = orders.id
" +
"
and orders.name = 'Dave Thomas'
")


, .
, , attributes(),
attribute_names() attribute_present?(). ,
-, ,
true, .
rails40/e1/ar/find_examples.rb
orders = Order.find_by_sql("select name, pay_type from orders")
first = orders[0]
p first.attributes
p first.attribute_names
p first.attribute_present?("address")

:
{"name"=>"Dave Thomas", "pay_type"=>"check"}
["name", "pay_type"]
false

19 Active Record 309

find_by_sql()
, , .
SQL- as xxx,
.
rails40/e1/ar/find_examples.rb
items = LineItem.find_by_sql ("
select *,
"
products.price as unit_price,
"
quantity*products.price as total_price,
"
products.title as title
"
from line_items, products
"
where line_items.product_id = products.id
li = items[0]
puts "#{li.title}: #{li.quantity}x#{li.unit_price} => #{li.total_price}"

" +
" +
" +
" +
" +
")

, find_by_sql , .
,
.
Order.find_by_sql(["select * from orders where amount > ?",
params[:amount]])

Rails find_by_sql(). ,
find(),
.


,
( ), , -
.
(
19.5 ). ,
. Active Record
reload(), :
stock = Market.find_by(ticker: "RUBY")
loop do
puts "Price = #{stock.price}"
sleep 60
stock.reload
end

, , reload()
.

310 III Rails


, ,
, Active Record
.
Active Record (,
orders), , save().
,
,
.
, , , Active Record . , Active Record,
: ,
.
123:
table:
order = Order.find(123)
order.name = "Fred"
order.save

Active Record ,
id, name paytype,
. ( : ,
find_by_sql(),
id.)
orders = Order.find_by_sql("select id, name, pay_type from orders where id=123")
first = orders[0]
first.name = "Wilma"
first.save

:
SQL ?
, , ,
. - SQL, -
, -
.
Active Record .
, SQL, ,
, . ,
(
insert ),
SQL
.

19 Active Record 311


, find_by_sql()
, ,
. ,
- , ,
, .

save() Active Record


update():
order = Order.find(321)
order.update(name: "Barney", email: "barney@bedrock.com")

update() , , ,
:
def save_after_edit
order = Order.find(params[:id])
if order.update(order_params)
redirect_to action: :index
else
render action: :edit
end
end

, update() update_all(). update()


id . , , .
order = Order.update(12, name: "Barney", email: "barney@bedrock.com")

update() (id) , , ,
, .
, update_all()
set where SQL- update. ,
10% ,
Java:
result = Product.update_all("price = 1.1*price", "title like '%Java%'")

, update_all(),
( Oracle) , .

save, save!, create create!


, save create ,
:

312 III Rails


save true, ,
nil.
save! true, ,
.
create Active Record ,
. , , .
create! ,
.
.
save() true,
:
if order.save
#
else
#
end

, save() ,
, . ,
Active Record, , save()
, ,
.
.
, , save!().
, RecordInvalid:
begin
order.save!
rescue RecordInvalid => error
#
end


Active Record . , , delete() delete_all(),
. delete() (id)
. delete_all() ,
( , ). ,
.
, .

19 Active Record 313


Order.delete(123)
User.delete([2,3,4,5])
Product.delete_all(["price > ?", @expensive_price])

, Active Record,
destroy.
Active Record.
destroy()
, .
, .
order = Order.find_by(name: "Dave")
order.destroy
# ... ""

: destroy() (
(id) ) destroy_all()
( ).
destroy().
.
Order.destroy_all(["shipped_at < ?", 30.days.ago])

, delete(), destroy()?
delete() ,
Active Record, destroy() . , , -, , destroy.
7, :
. .

19.4.
Active Record ,
,
. , Active Record
. , .
,
, ,
.
Active Record .
- Active Record. , before_destroy destroy(),

314 III Rails


after_destroy destroy().
: after_find after_initialize, before_xxx. ( ,
.)
.19.4 , Rails
,
. , before_ after_validation
.

. 19.4. Active Record

before_validation after_validation
on::create ( ) on::update
( ), ,

.
after_find , after_initialize
Active Record.
,
.
.
. ,
.
, . ,
(private) (protected)
.

19 Active Record 315

.
.
class Order < ActiveRecord::Base
before_validation :normalize_credit_card_number
after_create do |order|
logger.info " #{order.id} "
end
protected
def normalize_credit_card_number
self.cc_number.gsub!(/[-\s]/, '')
end
end

. , ,
false (
false),
.
, ,
, ( Proc-)
(eval metods),
. 1.

, .
.
, (before_save(), after_create() ..).
app/models.
, ,
.
.
,
normalize_credit_card_
number() .
, .
,
.

http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html#labelTypes+of+

callbacks

316 III Rails


class CreditCardCallbacks

end

#
def before_validation(model)
model.cc_number.gsub!(/[-\s]/, '')
end


:
class Order < ActiveRecord::Base
before_validation CreditCardCallbacks.new
# ...
end
class Subscription < ActiveRecord::Base
before_validation CreditCardCallbacks.new
# ...
end

,
cc_number; Order Subscription.
,
, .
, . .
,
.
,
. ,
, .
. , before_save,
after_save after_find.
, , ,
after_find() after_save()
.
rails40/e1/ar/encrypt.rb
class Encrypter
# ,
#
def initialize(attrs_to_manage)
@attrs_to_manage = attrs_to_manage
End

19 Active Record 317


#
# NSA DHS
def before_save(model)
@attrs_to_manage.each do |field|
model[field].tr!("a-z", "b-za")
end
end
#
def after_save(model)
@attrs_to_manage.each do |field|
model[field].tr!("b-za", "a-z")
end
end

end

#
alias_method :after_find, :after_save

,
- .
, Encrypter
:
require "encrypter"
class Order < ActiveRecord::Base
encrypter = Encrypter.new([:name, :email])
before_save encrypter
after_save encrypter
after_find encrypter
protected
def after_find
end
end

Encrypter before_save,
after_save after_find. ,
before_save() ..
after_find()? , , after_find after_initialize
. , Active Record ,
after_find, after_find()
. after_find,
after_find().
, , , ,
, ,

318 III Rails


Order. , ,
Active Record.
ActiveRecord::Base:
rails40/e1/ar/encrypt.rb
class ActiveRecord::Base
def self.encrypt(*attr_names)
encrypter = Encrypter.new(attr_names)
before_save encrypter
after_save encrypter
after_find encrypter
define_method(:after_find) { }
end
end


, .
class Order < ActiveRecord::Base
encrypt(:name, :email)
end


:
o = Order.new
o.name = "Dave Thomas"
o.address = "123 The Street"
o.email = "dave@example.com"
o.save
puts o.name
o = Order.find(o.id)
puts o.name

(
) :
ar> ruby encrypt.rb
Dave Thomas
Dave Thomas


:
depot> sqlite3
id =
user_id =
name =
address =
email =

-line db/development.sqlite3 "select * from orders"


1
Dbwf Tipnbt
123 The Street
ebwf@fybnqmf.dpn

19 Active Record 319

,
, ,
. , 19.4 ,
,
.
Order ,
.
.
,
, 1 (concern).

19.5.
,
, . , ( Active
Record), .
:
account1.deposit(100)
account2.withdraw(100)

#
#

. ,
, -
(, )?
(account1) 100
(account2). 100
.
. .
SQL-,
. , - , .
Active Record
transaction(). ,
, .
,
Active Record .
, :
Account.transaction do
account1.deposit(100)

http://37signals.com/svn/posts/3372-put-chubby-models-on-a-diet-with-concerns

320 III Rails

end

account2.withdraw(100)

. . ( , , .)
rails40/e1/ar/transactions.rb
create_table :accounts, force: true do |t|
t.string :number
t.decimal :balance, precision: 10, scale: 2, default: 0
end

.
.

.
rails40/e1/ar/transactions.rb
class Account < ActiveRecord::Base
validates :balance, numericality: {greater_than_or_equal_to: 0}
def withdraw(amount)
adjust_balance_and_save!(-amount)
end
def deposit(amount)
adjust_balance_and_save!(amount)
end

end

private
def adjust_balance_and_save!(amount)
self.balance += amount
save!
end

adjust_balance_and_save!().
. save!
. ( , save! ,
, , .)

:
rails40/e1/ar/transactions.rb
peter = Account.create(balance: 100, number: "12345")
paul = Account.create(balance: 200, number: "54321")
Account.transaction do
paul.deposit(10)
peter.withdraw(10)
end

19 Active Record 321

, :
depot> sqlite3
id =
number =
balance =
id =
number =
balance =

-line db/development.sqlite3 "select * from accounts"


1
12345
90
2
54321
210

.
, 350,
, Peter,
. :
rails40/e1/ar/transactions.rb
peter = Account.create(balance: 100, number: "12345")
paul = Account.create(balance: 200, number: "54321")

rails40/e1/ar/transactions.rb
Account.transaction do
paul.deposit(350)
peter.withdraw(350)
end

, ,
:
.../validations.rb:736:in `save!': Validation failed: Balance is negative
from transactions.rb:46:in `adjust_balance_and_save'
: : :
from transactions.rb:80

, :
depot> sqlite3
id =
number =
balance =
id =
number =
balance =

-line db/development.sqlite3 "select * from accounts"


1
12345
100
2
54321
200

. , ?
, ,
:
rails40/e1/ar/transactions.rb
peter = Account.create(balance: 100, number: "12345")
paul = Account.create(balance: 200, number: "54321")

322 III Rails


rails40/e1/ar/transactions.rb
begin
Account.transaction do
paul.deposit(350)
peter.withdraw(350)
end
rescue
puts " "
end
puts " Paul #{paul.balance}"
puts " Peter #{peter.balance}"

:

Paul 550.0
Peter -250.0

,
. , Active Record
,
, .



, , Active Record
.
SQL- (
). , ,
.
?
, . Active Record , , save() ( destroy()), ; ,
.

SQL-.
- . ACID-: (Atomic),
(Consistency), (Isolation) (Durable) ( , ).
,
,
.

19 Active Record 323


,
, , , . ,
, , . , ,
.
,
7 : ,
Active Record, Rails-. , , Rails, 18, .
Action Pack,
Rails, .

Action Dispatch
Action Controller

20

:
Representational State Transfer
(REST);
;
;
;
;
;
, .

Rails- Action Pack. Ruby-:


Action Dispatch, Action Controller Action View. Action Dispatch
. Action Controller
.
Action View Action Controller .
, Depot (/) index() StoreController.

, app/views/store/index.html.erb. , Action Pack.

.
Action Dispatch Action Controller, Action View.

20 Action Dispatch Action Controller 325

Active Record ,
, Active Record Ruby-. Action Pack
Active Record. , ,
, .
, Rails. ,
Action Controller, Action View Active Record, ,
Rails ( ) .
Action Controller Rails.
, Rails- . URL-.
. , ,
.

20.1.
-
, .
, , , ? -
, , ..
?
, Rails : , , ,
, .
URL , . , . , ,
.
Rails URL- Action Dispatch ,
. ,
Rails , ,
.
, HTTP-
, .
Rails URL-
URL- HTTP-,
. , URL URL
. Rails . , ,
Representational State Transfer (REST)

326 III Rails

REST:
, REST, 5
(Roy Fielding) 20001. ,
REST, ,
:
, .

.
, ( ) ( URL). REST
. REST
,

.
. REST?
-, REST ,
, .
,
.
-, REST ( ) . , REST, .
( ) .
, , ,
.
. REST, , .
HTTP, HTTP ( GET, PUT, PUTCH, POST
DELETE). .
, URL.
Depot . . , .
.
.
, HTTP- GET
, /products.
, . , Rails,
(
). GET-,
URL /products/1.

http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm

20 Action Dispatch Action Controller 327

HTTP POST, /products, ,


. , ,
:
GET-, ,
POST-, .
. , GET- /
products/1, . HTTP- PUT URL-.
, HTTP- DELETE,
URL.
. , .
, REST
(GET, POST, PUTCH, PUT DELETE)
URL (/users, /user/1, ...).
, ,
REST. , Rails
. REST
. -
.
Rails ;
, . , config/routes.rb, 6,
Rails-.
Depot::Application.routes.draw do
resources :products
end

resources
. ,
ProductsController, .
. , rake routes:
Prefix Verb
products

new_product
edit_product
product

URI Pattern
Controller#Action
GET
/products(.:format)
{:action=>"index", :controller=>"products"}
POST
/products(.:format)
{:action=>"create", :controller=>"products"}
GET
/products/new(.:format)
{:action=>"new", :controller=>"products"}
GET
/products/:id/edit(.:format)
{:action=>"edit", :controller=>"products"}
GET
/products/:id(.:format)
{:action=>"show", :controller=>"products"}

328 III Rails


PATCH

/products/:id(.:format)
{:action=>"update", :controller=>"products"}
DELETE
/products/:id(.:format)
{:action=>"destroy", :controller=>"products"}

.
- , ,
, . ( ) , HTTP-, (
) .
. ,
, ,
.
, .
, ,
, .
index
.
create
, POST-,
.
new
.
. , new , .
show
,
params[:id].
update
,
params[:id], , .
edit
,
params[:id], , .
destroy
, params[:id].
, , , CRUD (create,
read, update, and delete).
,
, .

20 Action Dispatch Action Controller 329

-
,
:only :except, :
resources :comments, except: [:update, :destroy]

,
, products_url edit_product_url(id:1).
,
.
.
:
rails40/depot_a/app/controllers/products_controller.rb
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
# GET /products
# GET /products.json
def index
@products = Product.all
end
# GET /products/1
# GET /products/1.json
def show
end
# GET /products/new
def new
@product = Product.new
end
# GET /products/1/edit
def edit
end
# POST /products
# POST /products.json
def create
@product = Product.new(product_params)

end

respond_to do |format|
if @product.save
format.html { redirect_to @product,
notice: 'Product was successfully created.' }
format.json { render action: 'show', status: :created,
location: @product }
else
format.html { render action: 'new' }
format.json { render json: @product.errors,
status: :unprocessable_entity }
end
end

330 III Rails


# PATCH/PUT /products/1
# PATCH/PUT /products/1.json
def update
respond_to do |format|
if @product.update(product_params)
format.html { redirect_to @product,
notice: 'Product was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: @product.errors,
status: :unprocessable_entity }
end
end
end
# DELETE /products/1
# DELETE /products/1.json
def destroy
@product.destroy

end

respond_to do |format|
format.html { redirect_to products_url }
format.json { head :no_content }
end

private
# Use callbacks to share common setup or constraints between actions.
def set_product
@product = Product.find(params[:id])
end

end

# Never trust parameters from the scary internet, only allow the white
# list through.
def product_params
params.require(:product).permit(:title, :description, :image_url, :price)
end

,
RESTful. URL-.
,
respond_to(). 11 : AJAX,
Rails , . , ,
, , HTML- JSON. .
, , .
HTTP-
. , index
:

20 Action Dispatch Action Controller 331


rails40/depot_a/app/views/products/index.html.erb
<h1>Listing products</h1>
<table>
<% @products.each do |product| %>
<tr class="<%= cycle('list_line_odd', 'list_line_even') %>">
<td>

<%= image_tag(product.image_url, class: 'list_image') %>


</td>
<td class="list_description">
<dl>
<dt><%= product.title %></dt>
<dd><%= truncate(strip_tags(product.description),
length: 80) %></dd>
</dl>
</td>
<td class="list_actions">
<%= link_to 'Show', product %><br/>
<%= link_to 'Edit', edit_product_path(product) %><br/>
<%= link_to 'Destroy', product, method: :delete,
data: { confirm: 'Are you sure?' } %>
</td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New product', new_product_path %>


GET-, link_to
. HTTP DELETE, link_to method: :delete.


Rails ,
. 12.2 2: Atom-
, ,
. Rails, :
Depot::Application.routes.draw do
resources :products do
get :who_bought, on: :member
end
end

332 III Rails


. :
who_bought, HTTP-
GET. .
:member
:collection, . ,
, .


, . ,
. ,
.
Rails :
resources :products do
resources :reviews
end

.
,
. , . 4
99, /products/99/
reviews/4.
/products/:product_id/reviews/:id product_review, review.
.
, ,
, rake
routes.


. ,
who_bought (
).
(concern),
.
concern :reviewable do
resources :reviews
end

20 Action Dispatch Action Controller 333


resources :products, concern: :reviewable
resources :users, concern: :reviewable

products .


URL. (shallow route nesting):
resources :products, shallow: true do
resources :reviews
end

:
/products/1
/products/1/reviews
/reviews/2

=> product_path(1)
=> product_reviews_index_path(1)
=> reviews_path(2)

, rake routes.


REST . /products -
, HTML-. URL
- ,
(YAML, JSON , ,
XML).
, Rails HTTP- Accept , respond_to. Accept
( ). ,
Rails URL. , Rails ,
:format.
:format MIME-,
.
GET

/products(.:format)
{:action=>"index", :controller=>"products"}

( ) , :format .
nil .
respond_to()
:

334 III Rails


def show
respond_to do |format|
format.html
format.xml { render xml: @product.to_xml }
format.yaml { render text: @product.to_yaml }
end
end

/store/show/1 /store/show/1.html
HTML, /store/show/1.xml XML-, /store/show/1.yaml YAML.
HTTP-:
GET HTTP://pragprog.com/store/show/123?format=xml

, resources,
.
,
, , . , . , ,
, XML,
- .
, .
Rails , . ,
.
. , ,
.
.
, .

.

20.2.
, Action Dispatch
. ,
.


,
, .
. ,
method_missing(),

20 Action Dispatch Action Controller 335

, .
, ,
. ,
. ,
, , AbstractControl
ler::ActionNotFound.


(
).
, URL .
action_name
.
cookies
cookie-. cookie- .
Rails cookie-. Rails.
headers
HTTP-, . Cache-Control no-cache.
Content-Type. , cookie cookie API.
params
, , (
, ).
, , params[:id]
params['id'] . Rails-
, .
request
. :
request_method , : :delete, :get, :head, :post :put;
method , request_method, :head, :get,

;
delete?, get?, head?, post? put? true false ;

336 III Rails


xml_http_request? xhr? true, AJAX. ,
method;
url() URL, ;
protocol(), host(), port(), path() query_string()
URL, ,
: protocol://host:port/path?query_string;
domain() ;
host_with_port() host:port ;
port_string() :port ,
(80 HTTP, 443 HTTPS);
ssl?() true, SSL-,
HTTPS;
remote_ip() IP-.
, -;
env() .
, , :
request.env['HTTP_ACCEPT_LANGUAGE']

accepts() , Mime::Type,
MIME- Accept.
format() ,
Accept, Mime::HTML .
content_type() MIME- .
put- post-.
headers() HTTP-.
body() -.
content_length() , .

Rails gem- Rack.
Rack::Request.
response
, .
Rails . ,

.
session
, , .
Rails.
Action Pack logger.

20 Action Dispatch Action Controller 337


.
.
.
MVC- , , ,
, .
, . , .
. AJAX-.
HTTP-,
.
- ( HTML). -
(, PDF- ).
.
,
render(), redirect_to() send_xxx(). ( DoubleRenderError.)
,
.
, , , .
. , , , , -,
. . ,
, .
,
(, .html.erb, .xml.builder .js.coffee).
, Rails ,
html.erb.


,
. Rails : erb,
Ruby- ( HTML); builder, XML-

338 III Rails


; RJS, JavaScript. , 21.1 .
, action controller
app/views/controller/action.type.xxx ( type , html, atom js, xxx : erb, rxml, coffee scss).
app/views . :
ActionController.prepend_view_path __

render() Rails .
, , .
:
# !
def update
@user = User.find(params[:id])
if @user.update(user_params)
render action: show
end
render template: "fix_user_errors"
end

, render ( redirect_to) . . , , update,


( render ).
,
( , 21,
).
render()
render()
. app/views/blog/index.html.erb:
class BlogController < ApplicationController
def index
render
end
end

( render, ):
class BlogController < ApplicationController
def index
end
end

20 Action Dispatch Action Controller 339

(, , ):
class BlogController < ApplicationController
end

render(text: )
.
HTML-.
class HappyController < ApplicationController
def index
render(:text => " !")
end
end

ender(inline: , [ type: "erb"|"builder"|"coffee"|"scss" ],


r
[ locals: ] )
, .
locals:.
method_missing(),
. ,

.
class SomeController < ApplicationController
if RAILS_ENV == "development"
def method_missing(name, *args)
render(inline: %{
<h2> : #{name}</h2>
:<br/>
<%= debug(params) %> })
end
end
end

render(action: _)
. action: render() ,
, , ,
.
def display_cart
if @cart.empty?
render(action: :index)
else
# ...
end
end

340 III Rails


, render(action:...) ; .
, , render().
, :
render(action:...)
, .
render(template: , [:locals => ] )

. template: , , .
app/views/blog/short_list:
class BlogController < ApplicationController
def index
render(template: "blog/short_list")
end
end

render(file: )
,
(, - ,
Rails-). .
layout: true.
render(partial: , )
. 21,
.
render(nothing: true)
, .
render(xml: )
,
application/xml.
render(json: , [:callback => ] )
JSON,
application/json. callback: ,
.
render(:update) do |page| ... end
RJS-, .
render(:update) do |page|
page[:cart].replace_html partial: 'cart', object: @cart
page[:cart].visual_effect :blind_down if @cart.total_items == 1
end

20 Action Dispatch Action Controller 341

render() :status,
:layout :content_type. :status , HTTP-.
"200OK". render() 3xx Rails redirect().
:layout ,
. ( 8.2 2:
. 21.6 .)
false, .
nil true, , , . :layout ,
, .
:nothing, .
:content_type , HTTP- Content-Type.
,
. render_to_string() ,
render(),

, - .
render_to_string .
render , DoubleRender.



, . (, , ).

send_data
, .
send_data(, )

.
, , , .
def sales_graph
png_data = Sales.plot_for(Date.today.month)
send_data(png_data, type: "image/png", disposition: "inline")
end

342 III Rails


:
:disposition

:filename

, ( inline) ( attachment, ).

:status

,
.

:type

(
octet-stream).

true, :filename
, Rails ContentDisposition.
,

.

:url_based_filename

"200 OK").
application/

send_file
.
send_file(, )

. Content-Length, Content-Type, Content-Disposition Content-TransferEncoding.


:
:buffer_size

:disposition

, , (
:stream true).

:filename

, ( inline)
( attachment,
).

:status

, . ,
,
.

true
false

false,
. ,
:buffer_size.

:stream

"200 OK").

20 Action Dispatch Action Controller 343

:type

(
octet-stream).

application/

send_ , headers.
def send_secret_file
send_file("/files/secret_list")
headers["Content-Description"] = "Top secret"
end

, 21.4 Rails-.

HTTP- . : , ,
. URL,
,
, ( 301)
( 307) .
-; ,
, .
Rails-
- .
. URL
,
.
,
URL .
. -,
. ,
, .
:
class BlogController
def display
@article = Article.find(params[:id])
end
def add_comment
@article = Article.find(params[:id])
comment = Comment.new(params[:comment])
@article.comments << comment

344 III Rails

end

end

if @article.save
flash[:note] = " "
else
flash[:note] = " "
end
# !
render(action: 'display')

, .
add_comment()
render(action: 'display'), display,
.
.
URL, blog/add_comment, . , URL
- blog/add_comment. ,
Refresh () (, ,
- ), URL add_comment
. , .
- .
- .

display. Rails- redirect_to().
Refresh (),
display .
def add_comment
@article = Article.find(params[:id])
comment = Comment.new(params[:comment])
@article.comments << comment
if @article.save
flash[:note] = " "
else
flash[:note] = " "
end

redirect_to(action: 'display')
end

Rails , .
( ) URL ( )
. .
redirect_to(action: ..., )
, . URL

20 Action Dispatch Action Controller 345

url_for(), redirect_to()
Rails, .
redirect_to()
. ( http://),
. - URL, , (
url_for
URL).
def save
order = Order.new(params[:order])
if order.save
redirect_to action: "display"
else
session[:error_count] ||= 0
session[:error_count] += 1
if session[:error_count] < 4
self.notice = ", "
else
#
redirect_to("/help/order_entry.html")
end
end
end

redirect_to(:back)
URL, HTTP_
REFERER .
def save_details
unless params[:are_you_sure] == 'Y'
redirect_to(:back)
else
...
end
end

(
). URL
.
:
headers["Status"] = "301 Moved Permanently"
redirect_to("http://my.new.home")

, , ,
.

346 III Rails


. Rails , .

20.3. ,

, ,
Active Record,
-. Depot
, ,
, .
-.
URL .
.

Rails
Rails , ,
. cookie-,
( ), . , -
. Cart ,
. Rails ,
, , , , Rails
. , ,
.
:
?
cookie-. Rails 4 . ,
, .
, ( ) , ,
, .
.
. -, Rails .
( ) 32-
( 1632 ). . Rails

20 Action Dispatch Action Controller 347

cookie- ( _session_id)
.
Rails .
-, Rails
, . Rails
, .

Ruby.
session, . .
, Rails
. , .
? , .
, ,
. ( ).
,
( Marshal- Ruby).
, , , - .
- , Rails,
. Rails , , Ruby
, .
,
.
class BlogController < ApplicationController
model :user_preferences
# . . .

(
), application_controller.rb app/controllers.
: ,
Cookie-
Rails, , . , ?! ,

, ?
, , , ,
. , -

348 III Rails


, .
, -
.
. Cookie- 4 ,
.
, cart_id, .
,
. . , cart_id 5
8 - . , Rails ,
, ,
.


.
, ,
, . .
, cookie-, , .
?

-
, .
, cookie, 4 .
, . , .
, ,
- .
,
.
,
.
, -
.
, ,
.
,
- . ,

,
, ,
cookie .
.
, .
,

20 Action Dispatch Action Controller 349

. , , , ,
, . .

. ,
Ruby .
, ,
, .
. ,
, . , , ,
, . , , .
,
, .
. ,
, .


Rails .
. ,
.
session_store
ActiveRecord::Base ,
.
ActiveSupport::Cache::Store.
, CamelCase.
session_store = :cookie_store
, Rails, 2.0, . , ,
,
4. Depot.
session_store = :active_record_store
gem- activerecord-session_store 1,
ActiveRecordStore.
1

https://github.com/rails/activerecord-session_store#installation

350 III Rails


session_store = :drb_store
DRb , Ruby .
DRbStore, Rails DRb ( -).
, ,
DRb. DRb Marshal.
session_store = :mem_cache_store
memcached
, Dormando1. memcached
,
- - .
session_store = :memory_store

.
, . , Rails- ,
-, .
session_store = :file_store
.
, Rails- ,
, . :prefix, :suffix :tmpdir.


? , : .
,
, ,
. ,
, , ,
, , . ,
,
. ,
, .
, , cookie_
store.
1

http://memcached.org/

20 Action Dispatch Action Controller 351

, memcached , ,
, CookieStore, Active Record DRb-. ,
cookie-, ,
Active Record.
, ,
, DRb-.



, ,
.
. , ,
.
.
, . ,
,
. - , ,
.
, cookie,
. . , cookie
.

. ,
, ,
, , .
.
Active Record
updated_at sessions. , ( ), SQL-:
delete from sessions
where now() - updated_at > 3600;

, DRb-,
DRb-.
,

352 III Rails


. (
), .
, , reset_session() ( ,
, ).


redirect_
to(), .
. ,
, ,
. .
.
. , , ,
, , .
. , - ,
, .
, .
,
. ,
- , ,
, -
. , ,
.
. , display() , - , .

. flash.now,
, .
flash.now , flash.
keep ,
, .
flash.keep , .
. ,
(, ),
.

20 Action Dispatch Action Controller 353

, . , .
.


,
, , -
, ,
( ). , . , , ,
.
Rails : , ,
. ()
. , , ,
.
,
.


,
. Rails . -
, , .
.
.
.
false, , .
, .

- 14.3 3: . ,
, .
.

. - , .

354 III Rails


, filter(), .
,
( ).
, :only :except. :only
, . :except ,
.
before_action after_action .
, prepend_before_
action() prepend_after_action().

, .
, (,
<customer/> ).
,
.
. .
.
.
yield, .
, .
, yield ,
, yield, .
yield,
false .
,
.
around_action
.
,
: .
call().
.
filter().
.
yield.
, , :only :except.
-: . ,
, .

20 Action Dispatch Action Controller 355


, ,
,
. , , .
-
, , skip_before_action skip_after_action.
:only :except.
(, ),
skip_action. , ( ).
skip_before_action 14.3,
3: .


Action Dispatch Action Controller,
. . ,
. Active Record Action
View - , .
REST,
Rails . ,
. , (, JSON XML). .
, Action Controller , ,
. ,
, , .
,
Depot. ,
,

.
Action Pack,
Action View, .


Action View

21

;
: ;
;
.

, , , ,
.
, ,
. ,
, , .
. ActionView
, ,
HTML, XML JavaScript.
, Action View MVC, .
, Rails .
: , .

, .

21 Action View 357

21.1.
: ,
. ,
, .
?
?
?


render() , app/views . ,
, . ,
Depot products store.
app/views/products app/views/store. ,
, .
.
, :
render(action:
render(template:
render(file:

'__' )
'/' )
'/' )

. , .


,
. ,
, .

.
- , flash, headers, logger, params, request, response
session. flash, , , ,
.
. , html.erb- -

358 III Rails


,
debug():
<h4></h4> <%= debug(session) %>
<h4></h4> <%= debug(params) %>
<h4></h4> <%= debug(response) %>

controller, ( , ActionController::Base).
base_path.


Rails .
Builder-, Builder XML. Builder 24.1 XML Builder.
CoffeeScript-, JavaScript,
, .
ERB-,
Ruby. HTML-. ERB- 24.2
HTML ERB.
SCSS-, CSS
.
, ERb-,
Depot.
. 20 Action Dispatch Action Controller, .
: , ,
.
, , , Rails
.

21.2.
HTML , ,
. ,
, .
Rails , . 21.5 ,
.

21 Action View 359

HTML . .21.1. ,
- ;

.

. 21.1.

,
:
rails40/views/app/views/form/input.html.erb

1
<%= form_for(:model) do |form| %>
<p>
<%= form.label :input %>
<%= form.text_field :input, :placeholder => 'Enter text here...' %>
5
</p>
<p>
<%= form.label :address, :style => 'float: left' %>
<%= form.text_area :address, :rows => 3, :cols => 40 %>
</p>
10
<p>
<%= form.label :color %>:
<%= form.radio_button :color, 'red' %>
<%= form.label :red %>
<%= form.radio_button :color, 'yellow' %>
15
<%= form.label :yellow %>
<%= form.radio_button :color, 'green' %>
<%= form.label :green %>
</p>
20
<p>
<%= form.label 'condiment' %>:
<%= form.check_box :ketchup %>

360 III Rails


25
30
35
40
-

<%= form.label :ketchup %>


<%= form.check_box :mustard %>
<%= form.label :mustard %>
<%= form.check_box :mayonnaise %>
<%= form.label :mayonnaise %>

</p>
<p>

<%= form.label :priority %>:


<%= form.select :priority, (1..10) %>

</p>
<p>

<%= form.label :start %>:


<%= form.date_select :start %>

</p>
<p>

<%= form.label :alarm %>:


<%= form.time_select :alarm %>

</p>
<% end %>

, 3.
. ,
.
text_field() text_area() ( 4 8).
, ,
.
, , ,
. ,
, , ,
.
, HTML5. , Rails ,
.
search_
field(), telephone_field(), url_field(), email_field(), number_field()
range_field(). -. -,
. , Safari Mac
.
. , Opera URL .
iPad
, , @.

21 Action View 361

, ,
, , .
. ,
text_field(), email_field() .
12, 22 32 .
, .
select(),
(Enumeration), , , . ,
,
1.
, 37 42 , ,
. , Rails 2. hidden_field()
password_field(). (hidden) ,
.
, . (password) ,
.
.
- ,
, gem-. Rails Guides3.
,
.

21.3.
.21.2 ,
, HTML- .
, name (), country () password
(). HTML, .
, . , country HTML- , user[country].
, POST-. Rails params.
1
2
3

http://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html
http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html
http://guides.rubyonrails.org/form_helpers.html

362 III Rails


(, , , ) . , Rails ,
,
. , , . ,
.

params

id=123

{ id: "123" }

user[name]=Dave

{ user: { name: "Dave" }}

user[address][city]=Wien

{ user: { address: { city: "Wien" }}}

. 21.2. ,

21 Action View 363

,
:
user.update(user_params)

Rails .

.html.erb .21.2, , HTML-

form_for() text_field().
, , params , . . .

21.4. Rails
.
,

- , .
HTTP- POST multipart/form-data. ,
.
<input> type="file". , .
,
.
, ,
. pictures,
:
rails40/e1/views/db/migrate/20121130000004_create_pictures.rb
class CreatePictures < ActiveRecord::Migration
def change
create_table :pictures do |t|
t.string :comment
t.string :name
t.string :content_type
# MySQL
# 64 , , ,
#
t.binary :data, :limit => 1.megabyte
end
end
end

364 III Rails



. get ,
picture :
rails40/e1/views/app/controllers/upload_controller.rb
class UploadController < ApplicationController
def get
@picture = Picture.new
end
# . . .
private
# ,
# .
def picture_params
params.require(:picture).permit(:comment, :uploaded_picture)
end
end

get , (
). , ,
:
rails40/e1/views/app/views/upload/get.html.erb
<% form_for(:picture,
url: {action: 'save'},
html: {multipart: true}) do |form| %>
:
:

<%= form.text_field("comment") %><br/>


<%= form.file_field("uploaded_picture") %><br/>

<%= submit_tag(" ") %>


<% end %>

.
uploaded_picture.
. , .
rails40/e1/views/app/models/picture.rb
class Picture < ActiveRecord::Base
validates_format_of :content_type,
with: /^image/,
message: " "
def uploaded_picture=(picture_field)
self.name = base_part_of(picture_field.original_filename)
self.content_type = picture_field.content_type.chomp
self.data = picture_field.read
end

end

def base_part_of(file_name)
File.basename(file_name).gsub(/[^\w._-]/, '')
end

21 Action View 365

, , -
uploaded_picture=(). , ,
. ,
, read() data. content_type original_
filename, . - ,
, .
, ,
image/xxx, - JavaScript.
save, , :
rails40/e1/views/app/controllers/upload_controller.rb
def save
@picture = Picture.find(params[:id])
if @picture.save
redirect_to(action: 'show', id: @picture.id)
else
render(action: :get)
end
end

, ? URL
URL image. , 123,
URL upload/picture/123.
send_data(). ,
, ,
:
rails40/e1/views/app/controllers/upload_controller.rb
def picture
@picture = Picture.find(params[:id])
send_data(@picture.data,
filename: @picture.name,
type: @picture.content_type,
disposition: "inline")
end

show,
. picture:
rails40/e1/views/app/controllers/upload_controller.rb
def show
@picture = Picture.find(params[:id])
end

366 III Rails


image , . .21.3 get show
:
rails40/e1/views/app/views/upload/show.html.erb
<h3><%= @picture.comment %></h3>
<img src="<%= url_for(:action => 'picture', :id => @picture.id) %>"/>

. 21.3.


, Paperclip1 ( thoughtbot) attachment_fu2 ( (Rick Olson)).
, ( ), , .
,
, .
Rails . ,
, , Rails.

https://github.com/thoughtbot/paperclip#readme
https://github.com/technoweenie/attachment_fu

1
2

21 Action View 367

21.5.
, .
.
, .
.
. -,
,
,
. : ,
.
.
-, html.erb HTML. ,
HTML-.
, HTML. Ruby-
.
,
, , ,
.
Rails .
, ,
.
- . HTML (XML, JavaScript)
.



. , ,
application_helper.rb. , , Rails , .
,
. , , ProductController,
ProductHelper product_helper.rb, app/helpers. rails generate controller
.
11.4 4: ,
- hidden_div_if(),
.

368 III Rails


, .
:
<h3><%= @page_title || "Pragmatic Store" %></h3>

, ,
. ,
store_helper.rb app/helpers.
module StoreHelper
def page_title
@page_title || "Pragmatic Store"
end
end

-:
<h3><%= page_title %></h3>

( , , , ,
.)

,

Rails -,
. ,
, , Action View RDoc, , .

,
- , .
<%= distance_of_time_in_words(Time.now, Time.local(2013, 12, 25)) %>
4 months
<%= distance_of_time_in_words(Time.now, Time.now + 33, include_

seconds: false) %>
1 minute
<%= distance_of_time_in_words(Time.now, Time.now + 33, include_

seconds: true) %>
Half a minute
<%= time_ago_in_words(Time.local(2012, 12, 25)) %>
7 months
<%= number_to_currency(123.45) %>
$123.45

21 Action View 369

<%= number_to_currency(234.56, unit: "CAN$", precision: 0) %>


CAN$235
<%= number_to_human_size(123_456) %>
120.6 KB
<%= number_to_percentage(66.66666) %>
66.667%
<%= number_to_percentage(66.66666, precision: 1) %>
66.7%
<%= number_to_phone(2125551212) %>
212-555-1212
<%= number_to_phone(2125551212, area_code: true, delimiter: " ")

%>
(212) 555 1212
<%= number_with_delimiter(12345678) %>
12,345,678
<%= number_with_delimiter(12345678, delimiter: "_") %>
12_345_678
<%= number_with_precision(50.0/3, precision: 2) %>
16.67
debug() , YAML , HTML-.
.
<%= debug(params) %>
--- !ruby/hash:HashWithIndifferentAccess
name: Dave
language: Ruby
action: objects
controller: test

. .
<%= simple_format(@trees) %>
, .
(Joyce Kilmer)
Trees, HTML- :
<p> I think that I shall never see <br />A poem lovely as a tree.</p> <p>A
tree whose hungry mouth is prest <br />Against the sweet earths flowing
breast; </p>
<%= excerpt(@trees, "lovely", 8) %>
...A poem lovely as a tre...

370 III Rails


<%= highlight(@trees, "tree") %>
I think that I shall never see A poem lovely as a <strong class="highlight">tree</
strong>. A <strong class="highlight">tree</strong> whose hungry mouth is
prest Against the sweet earths flowing breast;
<%= truncate(@trees, length: 20) %>
I think that I sh...
.
<%= pluralize(1, "person") %> but <%= pluralize(2, "person") %>
1 person but 2 people (1 , 2)
-
URL , ,
. ,
.
, 2, , cycle() , .

. current_cycle() reset_cycle().
, -

,
Markdown (BlueCloth)1 Textile (RedCloth)2.
, , , HTML.


ActionView::Helpers::AssetTagHelper ActionView::Helpers::
UrlHelper , , .
link_to(), :
<%= link_to " ", new_comments_path %>

link_to() ,
. ,
.
HTML- :
<%= link_to "", product_path(@product),
{ class: "dangerous", method: 'delete' }
%>

https://github.com/rtomayko/rdiscount
http://redcloth.org/

1
2

21 Action View 371

,
.
JavaScript.
:method ,
,
POST, PUT, PATCH DELETE, GET-.
JavaScript-, , JavaScript ,
GET-.
:data . :confirm,
. ,
JavaScript , .
<%= link_to "Delete", product_path(@product),
method: :delete,
data: { confirm: ' ?' }
%>

button_to() , link_to(),
, .
, . ,
, :
.
Rails , ,
. link_to_if() link_to_unless() , , link_to().
true ( link_to_if),
false ( link_to_unless),
, .
, ,
.
link_to_unless_current() ,
,
:
<ul>
<% %w{ }.each do |action| %>
<li>
<%= link_to_unless_current(action.capitalize, action: action) %>
</li>
<% end %>
</ul>

link_to_unless_current() , , , . -

372 III Rails


current_page(), , URI
.
url_for(), link_to(),
URL-:
<%= link_to("", "http://my.site/help/index.html") %>

image_tag() <img>.
:size ( x) width height:
<%= image_tag("/assets/dave.png", class: "bevel", size: "80x120") %>
<%= image_tag("/assets/andy.png", class: "bevel",
width: "80", height: "120") %>

:alt, Rails
. /, Rails , /images.
, link_to()
image_tag():
<%= link_to(image_tag("delete.png", size: "50x22"),
product_path(@product),
data: { confirm: " ?" },
method: :delete)
%>

mail_to() :mailto,
.
, HTML-.

:bcc,
:cc, :body :subject.
, encode: "javascript" JavaScript
, ,
-. , ,
, JavaScript.
<%= mail_to("support@pragprog.com", "Contact Support",
subject: " #{@user.name}",
encode: "javascript") %>


:replace_at :replace_dot,
.
.
AssetTagHelper -,
JavaScript
Atom-.

21 Action View 373

Depot, stylesheet_
link_tag() javascript_link_tag():
rails40/depot_r/app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Pragprog Books Online Store</title>
<%= stylesheet_link_tag "application", media: "all",
"data-turbolinks-track" => true %>
<%= javascript_include_tag "application", "data-turbolinks-track" => true %>
<%= csrf_meta_tags %>
</head>

javascript_include_tag() JavaScript-
(, assets/javascripts) HTML . all: javascript_include_
tag :defaults,
Rails jQuery.js.
RSS- Atom- , URL-
.
URL
RSS Atom XML-:
<html>
<head>
<%= auto_discovery_link_tag(:atom, products_url(format: 'atom')) %>
</head>
. . .

, JavaScriptHelper
JavaScript. JavaScript,

.
, , , images stylesheets, assets. , ,
(),
.
.
asset_host:
config.action_controller.asset_host = "http://media.my.url/assets"

, Rails
:
,
, ,
Rails. ,

374 III Rails


Rails, ,
.

21.6.

,
HTML-. ,
Rails, DRY . - .
,
.
HTML- ( -, , ,
).
. -
,
.
, Rails
.

Rails , . , ,
, , (, ). ,
generate, ,
.
Rails ,
. ,
( , ,
). Rails
( ).
, Rails , HTML, .

21 Action View 375

:
<html>
<head>
<title>Form: <%= controller.action_name %></title>
<%= stylesheet_link_tag 'scaffold' %>
</head>
<body>
<%= yield :layout %>
</body>
</html>

HTML- head body.


,
CSS-. yield.
. , Rails
:layout. yield
. :layout
, yield :layout yield.
.
my_action.html.erb :
<h1><%= @msg %></h1>

HTML:
<html>
<head>
<title>Form: my_action</title>
<link href="/stylesheets/scaffold.css" media="screen"
rel="Stylesheet" type="text/css" />
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>


, , Rails ,
, , ,
.
.
store, Rails store ( .html.erb
.xml.builder) app/views/layouts. layouts

376 III Rails


application, ,
.
,
layout. . standard.html.erb
standard.xml.builder store. Rails
app/views/layouts.
class StoreController < ApplicationController
layout "standard"
end

# ...

, ,
:only :except:
class StoreController < ApplicationController
layout "standard", except: [ :rss, :atom ]
end

# ...

layout nil,
.
,
. , -
, ,
- - -,
. Rails
.
layout ,
,
:
class StoreController < ApplicationController
layout :determine_layout
# ...
private

end

def determine_layout
if Store.is_closed?
"store_down"
else
"standard"
end
end

21 Action View 377

,
layout. ,
render :layout,
(
- ):
def rss
render(layout: false) # never use a layout
end
def checkout
render(layout: "layouts/simple")
end


, .
,
( ,
). . , :
<html>
<head>
<title><%= @title %></title>
<%= stylesheet_link_tag 'scaffold' %>
</head>
<body>
<h1><%= @title %></h1>
<%= yield :layout %>
</body>
</html>


@title:
<% @title = " " %>
<p>
:
</p>
<p>

. .

</p>

. , yield :layout ,
,
.

378 III Rails


, ,
.

content_for, , yield, .
content_for
, .
Rails , .
<h1> </h1>
<% content_for(:sidebar) do %>
<ul>
<li> </li>
<li> </li>
<li> <%= "dynamic" %> () </li>
</ul>
<% end %>
<p>

,
, .

</p>

yield :sidebar:
<!DOCTYPE .... >
<html>
<body>
<div class="sidebar">
<p>

</p>
<div class="page-specific-sidebar">
<%= yield :sidebar %>
</div>
</div>
</body>
</html>


JavaScript <head>, ,
..


-
.

21 Action View 379

. - ,
, . ,

.
Rails (
). : , ,
.

.
. . , ,
, .
, _article.html.erb app/views/blog:
<div class="article">
<div class="articleheader">
<h3><%= article.title %></h3>
</div>
<div class="articlebody">
<%= article.body %>
</div>
</div>

render(partial:):
<%= render(partial: "article", object: @an_article) %>
<h3> </h3>
. . .

:partial render() ( ).

Ruby- ( a-b
20042501). :object , .
, .
@an_article, ,
article. , article.title.
, render :locals. ,

:

380 III Rails


render(partial: 'article',
object: @an_article,
locals: { authorized_by: session[:user_name],
from_ip: request.remote_ip })


.
, ,
, .. ,
, .
render() :partial
:collection. :partial
, :collection
.
(article)
_article.html.erb :
<%= render(partial: "article", collection: @article_list) %>

article
, .
article_counter
.
:spacer_template ,
. , :
rails40/e1/views/app/views/partial/_list.html.erb
<%= render(partial: "animal",
collection: %w{ },
spacer_template: "spacer")
%>

_animal.html.erb _spacer.html.erb
. _animal.html.erb :
rails40/e1/views/app/views/partial/_animal.html.erb
<p>: <%= animal %></p>

_spacer .html.erb :
rails40/e1/views/app/views/partial/_spacer.html.erb
<hr />

21 Action View 381


render :partial , Rails
,
. (/),
Rails , , ,
, . , , , app/views.
.
, Rails-, app/views, shared. :
<%= render("shared/header", locals: {title: @article.title}) %>
<%= render(partial: "shared/post", object: @article) %>
. . .

@article post.


,
:
<%= render partial: "user", layout: "administrator" %>
<%= render layout: "administrator" do %>
# ...
<% end %>

app/views,
, , app/views/users/_administrator.html.erb.


, .
, , .
AJAX ,
, , ,
.

382 III Rails


, . :
,
.


Rails-, ,
Rails
.
. Rails : ERB, Builder, CoffeeScript SCSS.
HTML, XML, CSS JavaScript
. 25.2 Haml.
, ,
.
.
-, ,
.
, Rails: , , , ,
HTML-.
Action View
. ,

. ,
, .
Rail-
.
,
.

22

;
;
;
;
SQL.

Rails , . ,
. ,
.
.
, ,
,
.
,
, . Rails , , : , .. ,
, .
Rails . Depot, products 6,
, ,
line_items 10.1, 1:

384 III Rails


. ,
.

22.1.
, db/migrate,
Ruby.
( ) .
, , , .
(UTC).
, , , , ,
.
, ,
, .
, ,
.
db/migrate Depot :
depot> dir db\migrate
20121130000001_create_products.rb
20121130000002_create_carts.rb
20121130000003_create_line_items.rb
20121130000004_add_quantity_to_line_items.rb
20121130000005_combine_items_in_cart.rb
20121130000006_create_orders.rb
20121130000007_add_order_id_to_line_item.rb
20121130000008_create_users.rb

, (
) .
Depot , , .
, , , ,
( --skip-migration).
, discount
yyyyMMddhhmmss_create_discounts.rb:

depot> rails generate model discount


invoke active_record
db/migrate/20121113133549_create_discounts.rb
create app/models/discount.rb
invoke test_unit
create test/models/discount_test.rb
create test/fixtures/discounts.yml

22 385

depot> rails generate migration add_price_column


invoke active_record
create db/migrate/20121113133814_add_price_column.rb

, , ,
. , .


Rake- db:migrate:
depot> rake db:migrate

, ,
Rails.
schema_migrations, Rails.
, version,
.
rake db:migrate
schema_migrations. , .
, db/migrate,
, ( ) . , schema_
migrations.
, . , .
,
. ,
. ,
. ,
, . ,
, .
, .
,
rake db:migrate VERSION=:
depot> rake db:migrate VERSION=20121130000009

,
.

386 III Rails


, schema_migrations,
. Rails , , .
, schema_migrations ,
, . , , .
:
depot> rake db:migrate:redo STEP=3

redo ,
.
STEP=.

22.2.
Rails- ActiveRecord::Migration.
up() down():
class SomeMeaningfulName < ActiveRecord::Migration
def up
# ...
end
def down
# ...
end
end



, . , 20121130000017_some_meaningful_
name.rb. .
up()
, down() .
. , e_mail orders:
class AddEmailToOrders < ActiveRecord::Migration
def up
add_column :orders, :e_mail, :string
end

end

def down
remove_column :orders, :e_mail
end

22 387

, down() , up()?
.
Rails
. , add_column(), ,
remove_column().
up() change() down():
class AddEmailToOrders < ActiveRecord::Migration
def change
add_column :orders, :e_mail, :string
end
end

, ?


add_column .
, e_mail
:string. ? -
.
, Rails . , , ,
SQLite 3, , Postgres.
. SQLite 3, , ,
Postgres. Rails ,
,
. SQLite 3,
:string varchar(255). Postgres
char varying(255).
:binary, :boolean,
:date, :datetime, :decimal, :float, :integer, :string, :text, :time
:timestamp.
Rails , .22.1 22.2. ,
, , :integer,
SQLite 3 integer, Oracle
number(38).
22.1. (1)
db2

mysql

openbase

oracle

:binary

blob(32768)

blob

object

blob

:boolean

decimal(1)

tinyint(1)

boolean

number(1)

388 III Rails


22.1 ()
db2

mysql

openbase

oracle

:date

date

date

date

date

:datetime

timestamp

datetime

datetime

date

:decimal

decimal

decimal

decimal

decimal

:float

float

float

float

number

:integer

int

int(11)

integer

number(38)

:string

varchar(255)

varchar(255)

char(4096)

varchar2(255)

:text

clob(32768)

text

text

clob

:time

time

time

time

date

:timestamp

timestamp

datetime

timestamp

date

22.2. ( 2)
postgresql

sqlite

sqlserver

sybase

:binary

bytea

blob

image

image

:boolean

boolean

boolean

bit

bit

:date

date

date

date

datetime

:datetime

timestamp

datetime

datetime

datetime

:decimal

decimal

decimal

decimal

decimal

:float

float

float

float(8)

float(8)

:integer

integer

integer

int

int

:string

(. 1)

varchar(255)

varchar(255)

varchar(255)

:text

text

text

text

text

:time

time

datetime

time

time

:timestamp

timestamp

datetime

datetime

timestamp

. 1: character varying(256)
, , ;
.
:. .
null: true false
false, , , not null ( ). : -
presence: true, .

22 389

limit:
. , .
default:
. ,
.
, . ,
, ,
:
add_column :orders, :placed_at, :datetime, default: Time.now

:precision
:scale. :precision ,
:scale ,
( :scale ). :precision, 5,
:scale, 0, 99999
+99999. :precision, 5, :scale, 2, 999,99
+999,99.
:precision :scale . - .
, :
add_column
add_column
add_column
add_column

:orders,
:orders,
:orders,
:orders,

:attn, :string, limit: 100


:order_type, :integer
:ship_class, :string, null: false, default: 'priority'
:amount, :decimal, precision: 8, scale: 2


, , . Rails
. , e_mail
, . rename_column():
class RenameEmailColumn < ActiveRecord::Migration
def change
rename_column :orders, :e_mail, :customer_email
end
end

390 III Rails


rename_column() ,
up() down() .
, , . ,
.


change_column(). ,
add_column(), .
, order_type integer,
string. , ,
, 123, ,
"123". , , "new" "existing".

:
def up
change_column :orders, :order_type, :string
end

. , , down():
def down
change_column :orders, :order_type, :integer
end

, "new",
down() , "new"
. , .
, ,
down . Rails
, :
class ChangeOrderTypeToString < ActiveRecord::Migration
def up
change_column :orders, :order_type, :string, null: false
end
def down
raise ActiveRecord::IrreversibleMigration
end
end

ActiveRecord::IrreversibleMigration ,
Rails , change().

22 391

22.3.

. , :
class CreateOrderHistories < ActiveRecord::Migration
def change
create_table :order_histories do |t|
t.integer :order_id, null: false
t.text :notes
t.timestamps
end
end
end

create_table() ( ,
) . (
, .)
, ,
.
drop_table() , add_table() . drop_table() , .
,
add_column,
, , .
, id.
, Rails
id .
.
timestamps created_at ( ) updated_
at ( ) timestamp.
, ,
Rails
.

,
create_table , .
force: true, . ,
, ,
.
temporary: true , , .

392 III Rails


, , , .
options: "xxxx" . CREATE TABLE,
. SQLite 3 ,
. , MySQL id.
:
create_table :tickets, options: "auto_increment = 10000" do |t|
t.text :description
t.timestamps
end

DDL- MySQL:
CREATE TABLE "tickets" (
"id" int(11) default null auto_increment primary key,
"description" text,
"created_at" datetime,
"updated_at" datetime
) auto_increment = 10000;

:options MySQL . Rails MySQL


ENGINE=InnoDB,
InnoDB.
:options, ; , .
,
ENGINE=InnoDB. , InnoDB,
MySQL, .
,
, ,
.


, , . rename_table():
class RenameOrderHistories < ActiveRecord::Migration
def change
rename_table :order_histories, :order_notes
end
end

22 393

,
.

, rename_table
.
, , 4 order_
histories - :
def up
create_table :order_histories do |t|
t.integer :order_id, null: false
t.text :notes
t.timestamps
end

end

order = Order.find :first


OrderHistory.create(order_id: order, notes: "test")

, 7 order_histories order_
notes. OrderHistory
OrderNote.
. , 4:
OrderHistory,
.
(Tim Lucas) ,
. ,
, OrderHistory:
class CreateOrderHistories < ActiveRecord::Migration

class Order < ActiveRecord::Base; end


class OrderHistory < ActiveRecord::Base; end
def change
create_table :order_histories do |t|
t.integer :order_id, null: false
t.text :notes
t.timestamps
end

end

end

order = Order.find :first


OrderHistory.create(order: order_id, notes: "test")

394 III Rails


, -
, ., , .


(, , ) . ,
, , , , . , ,
add_index():
class AddCustomerNameIndexToOrders < ActiveRecord::Migration
def change
add_index :orders, :name
end
end

add_index unique: true,


,
.
index__on_.
, name: "_".
:name,
.
,
, add_index , .
.
remove_index().


Rails , (, , id), , .
.
Rails ,
.
.
Rails-
Rails id.
,
( ).
:primary_key create_table:

22 395
create_table :tickets, primary_key: :number do |t|
t.text :description
t.timestamps
end

number
:
$ sqlite3 db/development.sqlite3 ".schema tickets"
CREATE TABLE tickets ("number" INTEGER PRIMARY KEY AUTOINCREMENT
NOT NULL, "description" text DEFAULT NULL, "created_at" datetime
DEFAULT NULL, "updated_at" datetime DEFAULT NULL);


, . , Rails :
( , ).

,
. Rails ,
. Rails,
id :
create_table :authors_books, id: false do |t|
t.integer :author_id, null: false
t.integer :book_id, null: false
end

22.4.
Rails- , ,
.
. .

SQL
, . , , ,
. Rails :

396 III Rails


options add_column(),
execute().
options execute()
, SQL,
,
.
, .
,
:
def foreign_key(from_table, from_column, to_table)
constraint_name = "fk_#{from_table}_#{to_table}"
execute %{
CREATE TRIGGER #{constraint_name}_insert
BEFORE INSERT ON #{from_table}
FOR EACH ROW BEGIN
SELECT
RAISE(ABORT, "constraint violation: #{constraint_name}")
WHERE
(SELECT id FROM #{to_table} WHERE
id = NEW.#{from_column}) IS NULL;
END;
}
execute %{
CREATE TRIGGER #{constraint_name}_update
BEFORE UPDATE ON #{from_table}
FOR EACH ROW BEGIN
SELECT
RAISE(ABORT, "constraint violation: #{constraint_name}")
WHERE
(SELECT id FROM #{to_table} WHERE
id = NEW.#{from_column}) IS NULL;
END;
}
execute %{
CREATE TRIGGER #{constraint_name}_delete
BEFORE DELETE ON #{to_table}
FOR EACH ROW BEGIN
SELECT
RAISE(ABORT, "constraint violation: #{constraint_name}")
WHERE
(SELECT id FROM #{from_table} WHERE
#{from_column} = OLD.id) IS NOT NULL;
END;
}
end

up() , :
def up
create_table ... do
end

22 397

end

foreign_key(:line_items, :product_id, :products)


foreign_key(:line_items, :order_id, :orders)

foreign_
key() . lib foreign_key().
, :
module MigrationHelpers
def foreign_key(from_table, from_column, to_table)
constraint_name = "fk_#{from_table}_#{to_table}"
execute %{
CREATE TRIGGER #{constraint_name}_insert
BEFORE INSERT ON #{from_table}
FOR EACH ROW BEGIN
SELECT
RAISE(ABORT, "constraint violation: #{constraint_name}")
WHERE
(SELECT id FROM #{to_table} WHERE id = NEW.#{from_column}) IS NULL;
END;
}
execute %{
CREATE TRIGGER #{constraint_name}_update
BEFORE UPDATE ON #{from_table}
FOR EACH ROW BEGIN
SELECT
RAISE(ABORT, "constraint violation: #{constraint_name}")
WHERE
(SELECT id FROM #{to_table} WHERE id = NEW.#{from_column}) IS NULL;
END;
}
execute %{
CREATE TRIGGER #{constraint_name}_delete
BEFORE DELETE ON #{to_table}
FOR EACH ROW BEGIN
SELECT
RAISE(ABORT, "constraint violation: #{constraint_name}")
WHERE
(SELECT id FROM #{from_table} WHERE #{from_column} = OLD.id) IS NOT NULL;
END;
}
end
end


:
require "migration_helpers"

class CreateLineItems < ActiveRecord::Migration


extend MigrationHelpers

398 III Rails


require , extend
MigrationHelpers
. ,
.
( ,
1,
.)


, . say_with_time():
def up
say_with_time " ..." do
Person.all.each do |p|
p.update_attribute :price, p.lookup_master_price
end
end
end

say_with_time() ,
,
.

22.5.
. DDL, ,
. Rails
, DDL.
,
:
class ExampleMigration < ActiveRecord::Migration
def change
create_table :one do ...
end
create_table :two do ...
end
end
end
1

https://github.com/matthuhiggins/foreigner

22 399

up() , one two,


down() .
, ?
, one two.
,
,
, one .
, :
, , Rails .
,
one. , ,
. , ,
.
, , .
,
. ?
.
, . ,
. ,
. ,
,
, :
depot> RAILS_ENV=production rake db:migrate

, ,
.
, .

22.6.


Active Record, , , Rails-.
, , - , orders
city.
, , .
, , ,
. (private)
- .

400 III Rails


def run_with_index(*columns)
connection.add_index(:orders, *columns)
begin
yield
ensure
connection.remove_index(:orders, *columns)
end
end


:
def get_city_statistics
run_with_index(:city) do
# ..
end
end


Depot
, ,
.
, ;
; ; SQL
, .
Rails.
. ,
Rails .
Rails -.

23

:
Rails-;
Rails-;
.


, HTML.
.
Rails-
.
,
Rails- . ,
cron.
, Rails-, , Rails-, .
- , .
, Rails . , Rails .
,
, Rails ,

402 III Rails



.

23.1. ,
Active Record
, .
, Active Record
.
( , ,
, , Rails). .
, Active Record
SQLite 3.
id ,
:
require "active_record"
ActiveRecord::Base.establish_connection(adapter: "sqlite3",
database: "db/development.sqlite3")
class Order < ActiveRecord::Base
end
order = Order.find(1)
order.name = "Dave Thomas"
order.save

( ,
). Active Record
, .
, , , Rails
:
require "config/environment.rb"
order = Order.find(1)
order.name = "Dave Thomas"
order.save

, Ruby config/environment.rb
, . ,
require RUBYLIB.
RAILS_ENV ,
.

23 , 403

,
,
rails console 14, - ....
require. . , ,
, Rails - Rails. .

23.2. ,
Active Support
Active Support , Rails. , ,
Rails, , Rails. ,
Rails-
, ,
Rails. ,
, , -
, ,
, Rails.
, , .

(core-ext)
Active Support Ruby
. .
Array: second(), third(), fourth(), fifth() forty_two().
first() last(), Ruby.
CGI: escape_skipping_slashes(). ,
escape() ,
.
Class: ,
, ,
( ). , , .
Date: yesterday(), future?(), next_month() , .
Enumerable: group_by(), sum(), each_with_object(), index_by(), many?()
exclude?().

404 III Rails


File: atomic_write() path().
Float: round().
Hash: diff(), deep_merge(), except(), stringify_keys(), symbolize_
keys(), reverse_merge() slice().
, .
Integer: ordinalize(), multiple_of?(), months(), years(). .
Numeric.
Kernel: debugger(), breakpoint(), silence_warnings(), enable_
warnings().
Module: , , , , ,
.
Numeric: bytes(), kilobytes(), megabytes() ..; seconds(), minutes(),
hours() ..
Object: blank?(), present?(), duplicable?(), instance_values(),
instance_variable_names(), returning() try().
String: exclude?(), pluralize(), singularize(), camelize(), titleize(),
un-der-score(), dasherize(), demodulize(), parameterize(),
tableize(), clas-sify(), humanize(), foreign_key(), constantize(),
squish(), mb_chars(), at?(), from(), to(), first(), last(), to_time(),
to_date() try().
Time: yesterday(), future?(), advance() , .
, . , . ,
, ,
Rails-.
. , , .
, , Ruby.
1, Rails. :
2.years.ago
[1,2,3,4].sum
5.gigabytes
"man".pluralize
String.methods.sort
- , , , ,
http://as.rubyonrails.org/

23 , 405

, -
, Rails .

Active Support
, Ruby, Active Support
. ,
, , Rails,
.
Benchmarkable:
.
Cache::Store: -
, .
Callbacks: .
Concern and Dependencies:
.
Configurable: Hash.
Deprecation: ,
.
:
?
, , 5.months + 30.minutes,
.
, ? ,
, . ,
.
Active Support Ruby, , string ,
. Ruby
Rails-. Active Support Rails, , 5.months
Rails-. , Ruby.
, Active Support
, .

Duration: , ago() since().


Gzip: compress() decompress() .

406 III Rails


HashWithIndifferentAccess: :
params[:key] params['key'].
I18n: .
Inflections:
.
JSON:
JavaScript Object Notation.
LazyLoadHooks:
.
MessageEncryptor: ,
- .
MessageVerifier: ( ).
MultiByte: (
Ruby 1.8.7).
Notifications: API.
OptionMerger: -.
OrderedHash and OrderedOptions: ( Ruby 1.8.7).
Railtie: ,
.
Rescueable: .
SecureRandom: ,
cookie- HTTP.
StringInquirer: .
TestCase: gem- Ruby
gem- .
Time TimeWithZone: .
49 ( )
, , , TimeWithZone,

API. , .
require "active_support/time"
Time.zone = 'Eastern Time (US & Canada)'
puts Time.zone.now

, ,
, ,

23 , 407

(, require "active_support/basic_object" require


"active_support/core_ext")
require "active_support/all".

Action View
Active Support,
. , Active Support,
Rails, , , Action View,
-.

Action View. Action View
:
require "action_view"
require "action_view/helpers"
include ActionView::Helpers::DateHelper
puts distance_of_time_in_words_to_now(Time.parse("December 25"))

, Active Support,
.


, ,
Active Support, Action View Active Record .
,
,
, cron.
, Active Resource , ,
. ,
, Rails-.
,
Rails.

Rails

24

XML HTML;
;
;
-.

Rails. ,
. , Rails, , .
,
. Atom, HTML, rake db:migrate, bundle install rails
server Depot.
,
, ,
.
, .
,
.
,
, . , Bundler, . , ,
Rack Rake.

24 Rails 409

24.1. XML Builder


Builder , (, XML). Builder- (
.xml.builder) Ruby, XML
Builder.
Builder-,
XML, :
rails40/depot_t/app/views/products/index.xml.builder
xml.div(class: "productlist") do
xml.timestamp(Time.now)

end

@products.each do |product|
xml.product do
xml.productname(product.title)
xml.price(product.price, currency: "USD")
end
end

, 12.2
2: Atom- Atom,
, Atom Builder.
( ),
:
<div class="productlist">
<timestamp>2013-01-29 09:42:07 -0500</timestamp>
<product>
<productname>CoffeeScript</productname>
<price currency="USD">36.0</price>
</product>
<product>
<productname>Programming Ruby 1.9</productname>
<price currency="USD">49.5</price>
</product>
<product>
<productname>Rails Test Prescriptions</productname>
<price currency="USD">43.75</price>
</product>
</div>

, Builder
XML-. , xml.price, <price>,
. , ,

410 III Rails


,
tag!():
xml.tag!("id", product.id)

Builder XML.
, ,
XML-.
Builder.
HTML XML, HTML
. .

24.2. HTML ERB


ERB- HTML-.
,
.
html.erb-:
<h1>, !</h1>
<p>
?
</p>

, ,
.
:
<h1>, !</h1>
<p>
<%= Time.now %>
</p>

, (JSP),
, . ERB
<%= %>,
to_s(), HTML
.
:
<h1>, !</h1>
<p>
<%= require 'date'
DAY_NAMES = %w{ Sunday Monday Tuesday Wednesday
Thursday Friday Saturday }
today = Date.today
DAY_NAMES[today.wday]
%>
</p>

24 Rails 411

-
, -
. 21.5
, -.
, . ,
, .
:
<% require 'date'
DAY_NAMES = %w{ Sunday Monday Tuesday Wednesday
Thursday Friday Saturday }
today = Date.today
%>
<h1>, !</h1>
<p>
<%= DAY_NAMES[today.wday] %>.
<%= DAY_NAMES[(today + 1).wday] %>.
</p>

JSP . ,
, .
. .
(
-). ,
-.
HTML- ,
Ruby-. <%...%>
. HTML . <% %> HTML
.
:
<% 3.times do %>
!<br/>
<% end %>

<%=...%>, HTML,
. , .
HTML, , HTML-, , <em>hello</em>, ,
<em>hello</em>, hello. Rails
-. .
raw() .
.

412 III Rails


row()
HTML-, HTML-.
sanitize() . , HTML, : <form> <script>
, on= , javascript:,
.
Depot HTML
( raw()).
.
- ,
, sanitize().

gem-, Rails.
, .

24.3.
Bundler
, .
gem-, .
,
,
gem-, .
, , .
, ,
.
, ,
, .
, , , ,
.
Bundler1 , Gemfile,
.
. Gemfile
Depot:
rails40/depot_u/Gemfile
source 'https://rubygems.org'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'

http://gembundler.com/

24 Rails 413
gem 'rails', '4.0.0'
# Use sqlite3 as the database for Active Record
gem 'sqlite3'
group :production do
gem 'mysql2'
end
# Use SCSS for stylesheets
gem 'sass-rails', '~> 4.0.0'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
# Use CoffeeScript for .js.coffee assets and views
gem 'coffee-rails', '~> 4.0.0'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
# gem 'therubyracer', platforms: :ruby
# Use jquery as the JavaScript library
gem 'jquery-rails'
gem 'jquery-ui-rails'
# Turbolinks makes following links in your web application faster.
# Read more: https://github.com/rails/turbolinks
gem 'turbolinks'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 1.2'
group :doc do
# bundle exec rake doc:rails generates the API under doc/api.
gem 'sdoc', require: false
end
# Use ActiveModel has_secure_password
gem 'bcrypt-ruby', '~> 3.0.0'
# Use unicorn as the app server
# gem 'unicorn'
# Use Capistrano for deployment
gem 'rvm-capistrano', group: :development
# Use debugger
# gem 'debugger', group: [:development, :test]

, gem-
gem-. ,
gem-.
, Rails . : . ,

414 III Rails


,
Rails.
gem-, ,
gem-, .
, :development (), :test () :production (), . :require,
require ,
gem-.
sass-rails ,
. Gemfile , . >=
, , , Gemfile
,
.
~> . , , ,
, . ~> 3.1.4
, 3.1 3.1.4.
~> 3.0 , 3.
Gemfile - Gemfile.lock.
: bundle install bundle update.
.
, Gemfile.lock. :
GEM
remote: https://rubygems.org/
specs:
actionmailer (4.0.0)
actionpack (= 4.0.0)
mail (~> 2.5.3)
actionpack (4.0.0)
activesupport (= 4.0.0)
builder (~> 3.1.0)
erubis (~> 2.7.0)
rack (~> 1.5.2)
rack-test (~> 0.6.2)
activemodel (4.0.0)
activesupport (= 4.0.0)
builder (~> 3.1.0)

bundle install Gemfile.lock


gem-,
. ,
, ,

.

24 Rails 415

bundle update ( ) gem- Gemfile.


lock. gem-,
Gemfile,
, bundle update, gem-, . gem-,
Bundler gem-, , -, , .
Bundler , ,
,
gem-, Gemfile.lock. .

24.4. -
Rack
Rails -. -: WEBRick,
Ruby, Phusion Passenger, - Apache HTTP.
, Mongrel, Lighttpd,Unicorn
Thin.
, Rails , -.
Rails , Rails 2.3 gem-
Rack.
, Rails Rack, Rack ( ) Passenger,
Passenger Apache httpd.

rails server, config.ru, Rack:
rails40/depot_u/config.ru
# This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment', __FILE__)
run Rails.application

Rails-
:
rackup


rails server.
Rack , Rack-:

416 III Rails


rails40/depot_u/app/store.rb
require 'builder'
require 'active_record'
ActiveRecord::Base.establish_connection(
adapter: 'sqlite3',
database: 'db/development.sqlite3')
class Product < ActiveRecord::Base
end
class StoreApp
def call(env)
x = Builder::XmlMarkup.new :indent=>2
x.declare! :DOCTYPE, :html
x.html do
x.head do
x.title 'Pragmatic Bookshelf'
end
x.body do
x.h1 'Pragmatic Bookshelf'
Product.all.each do |product|
x.h2 product.title
x << " #{product.description}\n"
x.p product.price
end
end
end

end

end

response = Rack::Response.new(x.target!)
response['Content-Type'] = 'text/html'
response.finish

, . ,
, active_
record builder.
Product . ,
Rails-,
.
. ,
call().
env, .
HTML, ,
Builder, , finish().
rackup-,
:
rails40/depot_u/store.ru
require 'rubygems'
require 'bundler/setup'

24 Rails 417
require './app/store'
use Rack::ShowExceptions
map '/store' do
run StoreApp.new
end

Bundler,
gem-. store.
(middleware) ,
Rack,
, - . Rack
Rails, , .
, Rack Rails-, , rake middleware.
URI store .
rackup:
rackup store.ru

rackup , 3000
9292. -p.

, .24.1.

. 24.1. ,

418 III Rails


Rack- Rails , . , Rails
.
, , .
:
rails40/depot_u/config/routes.rb
require './app/store'

Depot::Application.routes.draw do
match 'catalog' => StoreApp.new, via: :all
get 'admin' => 'admin#index'
controller :sessions do
get 'login' => :new
post 'login' => :create
delete 'logout' => :destroy
end
get "sessions/create"
get "sessions/destroy"

resources :users
resources :products do
get :who_bought, on: :member
end

end

scope '(:locale)' do
resources :orders
resources :line_items
resources :carts
root 'store#index', as: 'store', via: :all
end

, Rails.
,
.

24.5. Rake
Rake ,
. , ,
. Rakefile, .
db:setup. ,
, Rake --trace --dry-run:

24 Rails 419
$ rake --trace --dry-run db:setup
(in /home/rubys/work/depot)
** Invoke db:setup (first_time)
** Invoke db:create (first_time)
** Invoke db:load_config (first_time)
** Invoke rails_env (first_time)
** Execute (dry run) rails_env
** Execute (dry run) db:load_config
** Execute (dry run) db:create
** Invoke db:schema:load (first_time)
** Invoke environment (first_time)
** Execute (dry run) environment
** Execute (dry run) db:schema:load
** Invoke db:seed (first_time)
** Invoke db:abort_if_pending_migrations (first_time)
** Invoke environment
** Execute (dry run) db:abort_if_pending_migrations
** Execute (dry run) db:seed
** Execute (dry run) db:setup


.
16, .
,
raketasks. Rails
, .
, Ruby lib/tasks.

:
rails40/depot_u/lib/tasks/db_backup.rake
namespace :db do
desc " "
task :backup => :environment do
backup_dir = ENV['DIR'] || File.join(Rails.root, 'db', 'backup')
source = File.join(Rails.root, 'db', "production.db")
dest = File.join(backup_dir, "production.backup")
makedirs backup_dir, :verbose => true

end

end

require 'shellwords'
sh "sqlite3 #{Shellwords.escape source} .dump > #{Shellwords.escape dest}"

. db.
. . rake --tasks, ,
, Rails.

420 III Rails


, ,
. , rails console.
, , Ruby. ( db/backup,
DIR),
backup ( ) , , sqlite3 dump.

24.6. Rails-
Rails- Gemfile.lock. , .
, ,
.
, Rails .
, . , ,
RubyGems.org1, gem- , gem-
Documentation, Homepage.
actionmailer
Rails, . 13 :
actionpack
Rails, . 20 Action Dispatch Action Controller
activemodel
Active Record Active Resource
activerecord
Rails, . 19 Active Record
activesupport
Rails, . 23.2 , Active
Support
rails

railties
Rails,
25.4, RailsPlugins.org
ansi
ANSI,
gem- turn
http://rubygems.org

24 Rails 421

arel
; Active Record
atomic
Atomic, , atomic-
bcrypt-ruby
- ; Active Model
builder
XML-; . 24.1
XML Builder
capistrano
; . 16.2
2: Capistrano
coffee-script
JS CoffeeScript
erubis
ERb, Rails; . 24.2 HTML ERb
execjs
JavaScript Ruby;
coffee-script highline IO
hike
; sprockets
i18n
; . 15 :
jquery-rails
jQuery jQuery-ujs
jbuilder
DSL- JSON-, , -
json
JSON- RFC 4627
mail
; . 13 :
mime-types
, mail
multi-json
JSON-
mysql
, Active Record; .
MySQL 16

422 III Rails


minitest
,
TDD, BDD,
net-scp

net-sftp

net-ssh

net-ssh-gateway
SSH
polyglot

rack
; . 24.5
Rack
rack-test
API ; . 20,

rake
; . 24.5 Rake
sass
CSS3
sass-rails
Sass
sprockets

JavaScript
thread_safe
Ruby-

tilt
Ruby; sprockets
turn
Test::Unit
sqlite3
, Active Record
thor
, rails

24 Rails 423

treetop
, mail
tzinfo

uglifier
JavaScript


Rails, ,
, -
.
, Rakefile, Gemfile Gemfile.lock .
, Rails, Rails, ,
.


Rails

25

:
;
.

, Rails .
Rails gem-, Rails.
,
gem-, Rails,
gem-, .
Rails gem-
.
. ,
!
,
.

25.1.
Active Merchant
1 12, , . , ,

25 Rails 425

.
Rails, gem-, .
, gem-, : Gemfile.
gem-,
, .
; .
rails40/depot_v/Gemfile
gem 'activemerchant', '~> 1.31'
gem 'haml', '~> 4.0'
gem 'kaminari', '~> 0.14'

,
, , ,
.
gem-, . Active
Merchant1.
, bundle.
depot> bundle install

root-.
bundle . gem-, , .
, ,
, gem-, .
gem-
: . Rails ,
, , , ,
gem-, . ,
. ,
Depot.
, script:
rails40/depot_v/script/creditcard.rb
credit_card = ActiveMerchant::Billing::CreditCard.new(
number: '4111111111111111',

http://www.activemerchant.org/

426 III Rails


month: '8',
year: '2009',
first_name: 'Tobias',
last_name: 'Luetke',
verification_value: '123'

)
puts " #{credit_card.number}? #{credit_card.valid?}"

.
ActiveMerchant::Billing::CreditCard, valid?(). .
$ rails runner script/creditcard.rb
4111111111111111? false

: . , require
,
gem- Gemfile.
,
Depot. , Orders.
, . , ,
valid?(). - , , authorize() capture(),

. , ,
.
: Gemfile.
, gem- Gemfile Rails.
: ,
Bundler,
.
. ,
gem-, Rails.

25.2.
Haml
,
Depot,
:

25 Rails 427
rails40/depot_u/app/views/store/index.html.erb
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<h1><%= t('.title_html') %></h1>
<% cache ['store', Product.latest] do %>
<% @products.each do |product| %>
<% cache ['entry', product] do %>
<div class="entry">
<%= image_tag(product.image_url) %>
<h3><%= product.title %></h3>
<%= sanitize(product.description) %>
<div class="price_line">
<span class="price"><%= number_to_currency(product.price) %></span>
<%= button_to t('.add_html'), line_items_path(product_id: product),
remote: true %>
</div>
</div>
<% end %>
<% end %>
<% end %>

. HTML Ruby,
<% %>. ,
HTML
.
, , Rails-.
, .
HTML
Rails, Ruby. ,
,
.
, ,
, ,
Ruby-. HTML HTML Abstraction Markup Language (Haml).
, :
$ del app\views\store\index.html.erb

:
rails40/depot_v/app/views/store/index.html.haml
- if notice
%p#notice= notice
%h1= t('.title_html')

428 III Rails


- cache ['store', Product.latest] do
- @products.each do |product|
- cache ['entry', product] do
.entry
= image_tag(product.image_url)
%h3= product.title
= sanitize(product.description)
.price_line
%span.price= number_to_currency(product.price)
= button_to t('.add_html'), line_items_path(product_id: product),
remote: true

, : .html.haml. , ERB, Haml.


,
. ,
, .
Ruby,
.
(%) HTML.
(=) Ruby-,
. ,
HTML-.
(.) (#) , , class id.
. ,
div.
, , . button_to()
.
, Haml . if,
. h1, h1
div, div , h3, span,
button_to().
, -,
t(), image_tag() button_to(). Haml
, ERB.
: ,
ERB, , Haml.
gem- Haml, , ,
. ,
. , , , .25.1.

25 Rails 429

. 25.1. , Haml

, , , . , , , ERB, Haml-.
, .
, , , , .
, , Rails.

25.3
, . ,
, , ,
.
kaminari. Rails,
.
.
, . , ,
. script.
rails40/depot_v/script/load_orders.rb
Order.transaction do
(1..100).each do |i|

430 III Rails


Order.create(name: "Customer #{i}", address: "#{i} Main Street",
email: "customer-#{i}@example.com", pay_type: "Check")
end
end

. ,
, . ,
. -
, .
- require . Rails :
rails runner script/load_orders.rb

, ,
.
paginate(), ,
:
rails40/depot_v/app/controllers/orders_controller.rb
def index

@orders = Order.order('created_at desc').page(params[:page])


End

:
rails40/depot_v/app/views/orders/index.html.erb
<h1>Listing orders</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Address</th>
<th>Email</th>
<th>Pay type</th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<% @orders.each do |order| %>
<tr>
<td><%= order.name %></td>
<td><%= order.address %></td>
<td><%= order.email %></td>
<td><%= order.pay_type %></td>
<td><%= link_to 'Show', order %></td>

25 Rails 431
<td><%= link_to 'Edit', edit_order_path(order) %></td>
<td><%= link_to 'Destroy', order, method: :delete,
data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Order', new_order_path %>
<p><%= paginate @orders %></p>

, , !
,
.
, :per_page (.25.2).

. 25.2.


, , , -
.

432 III Rails


gem-, , ,
, (Active Merchant Capistrano)
(kaminari), (Haml)
(mysql).
, , .

25.4.
RailsPlugins.org
. , .
, ,
Rails, . ,
jQuery Rails ,
, Prototype. prototype-rails1. , acts_as_tree2,
.
, rails_xss3,
Rails
.

.
devise4 authlogic5
.
Depot , , . , : -
,
,
.
Rails. , datamapper6 Active Record. cucumber7,



3

4

5

6

7

1
2

https://github.com/rails/prototype-rails#readme
https://github.com/rails/acts_as_tree#readme
https://github.com/rails/rails_xss
https://github.com/plataformatec/devise#readme
https://github.com/binarylogic/authlogic#readme
http://datamapper.org/
http://cukes.info/

25 Rails 433

rspec1 webrat2 ,
.
airbrake3 exception_notification4
.
,
. : ,
, .
, ,
. , Rails Guides5
6.



3

4

5

6

1
2

http://rspec.info/
https://github.com/brynary/webrat#readme
https://airbrakeapp.com/pages/home
https://github.com/rails/exception_notification#readme
http://guides.rubyonrails.org/plugins.html
http://api.rubyonrails.org/classes/Rails/Railtie.html

26

:
Rails: , , , ,
;
.

! .
I Rails, , Rails
(, , ) Ruby.
II ,
Capistrano. , Rails, .
I II ,
, III .
III
.
.

. . ,
Rails, .
, , , Rails.

26 435

REST- HTML-.

.
, Rails gem- : Rails-
, Rails , ,
, .

,
. Ruby on Rails1 ,
.
, ,
,
,
.
, , wiki ,
.
Pragmatic Bookshelf , Ruby Rails2. , Ruby Rails, (Agile Practices); , ;
, .
: http://www.pragprog.com/categories.
, Ruby on Rails, ,
!

http://rubyonrails.org/
http://www.pragprog.com/categories/ruby_and_rails

1
2


:method, 371
:options, 392
:order, 304
:partial, 380, 381
:select, 304
:string, 387

db/migrate, 384
delete_all, 312
delete, 312
Depot, -
209
176

177
destroy_all, 313
destroy, 313
down, 386
DRb, 350

Action View356
Active Record289
313
294
SQL301
310
295
296, 297
319, 322
312
301
ActiveRecord::Migration, 386
add_index, 394
adjust_balance_and_save, 320
after_destroy, 224
AssetTagHelper, 372

B
belongs_to, 297

create_table, 391
create, 312
create!, 312

F
form_for, 180

L
layout, 376
link_to_if, 371
link_to_unless, 371

437

method_missing, 334

update_all, 311
update_attribute, 311
update, 311
up, 386

P
params, 216, 361

R
RecordNotFound, 301
redirect_to, 337, 344
rename_table, 392
render337
render, 357, 381
380
RESTful326
REST (Representational State Transfer)

333
rhtml-337
rjs-338

S
save, 311, 312
save!, 312
schema_info, 385
script/console, 223
send_xxx, 337
session_store, 349
skip_after_filter, 355
skip_before_filter, 355
skip_filter, 355

T
tail, 264
template_root, 357
transaction, 319

V
version, 385

Y
yield :layout, 377

219


384, 386, 387, 391, 394
319

313

334


265

341
341
338
335

438

337

286
285

374
377
375

327, 328

327, 328

334

386
384

SQL395
394
384
391
387
391
,
297

285

310
,
296
,
297
, 177

394, 395

285
343
344
367
367

369

Depot, -176, 177,


209
265

264, 265

-249

264, 265
265
327
328


297
296
297

347, 348, 349, 351


284
285
285
285
285

439

,
387

395
391, 394
394
285
301
387
319
322

312

363
219, 353
353

355
353, 354
352
352
361
, 379
379
381
380
381

337
Builder337
rhtml337
rjs338
367
357
357

Yellow Fade Technique163

, ,

Rails 4.
-









.
.
.
.
.
.
.

, 192102, -, . (. ), . 3, , . 7.
005-93, 2; 95 3005 .
26.02.14. 70100/16. . . . 36,120. 1500.

. 180004, , . , 34.

. , . , . ,
. , .

-:

-
:
-.
,
? ,
, ?
JavaScript Backbone Knockout ?
-, CoffeeScript Sass,
?
?
Git?
. , -
-,
, .

Node.js
Node.js ,
Google JavaScript- V8.
,
,
-, .
-, , .
Node Twisted
Python EventMachine Ruby. JavaScript,
, .
Node.
, , -
. ,
Node .

. , .

jQuery
-?
, jQuery , !
jQuery
JavaScript,
- RIA-. jQuery
DOM,
DOM, AJAX.

, Head First O'Reilly ,
.

Backbone.js
Backbone javascript-
javascript-, , , gmail twitter. ,
.
, Backbone. MVC , ,
Backbone;
Backbone.js AMD ( RequireJS),
, ,
Backbone jQuery Mobile, .

Rails.

, , ,
,
-.

, ,
Ruby on Rails . ,
Ruby on Rails,

.

?
!
?
?
? ,

?
!


!

www.piter.com/ePartners

www.piter.com,
,

( www.piter.com)
!
.
10% ,
, - c
. ,
, 5%
.
, , 500 ,
. Web.Money.
:
http://www.piter.com/book.phtml?978538800282
http://www.piter.com/book.phtml?978538800282&refer=0000
, 0000


WWW.PITER.COM