Professional Documents
Culture Documents
Ruby on Rails
Rails
Web
Michael Hartl
................................................................................................................... 1
1.1.
1.2.
1.3.
1.4.
20
1.5.
29
1.6.
33
1.7.
33
.......................................................................................................................................... 35
2.1.
35
2.2.
38
2.3.
49
2.4.
58
2.5.
59
Git
............................................................................................................................... 63
3.1.
63
3.2.
65
3.3.
73
3.4.
78
3.5.
87
3.6.
88
3.7.
90
Rails
Ruby ........................................................................................................................... 97
4.1.
97
4.2.
100
4.3.
106
4.4. Ruby
115
4.5.
123
4.6.
123
........................................................................................................................................ 125
5.1.
125
5.2. Sass
140
5.3.
148
5.4.
154
5.5.
157
5.6.
158
........................................................................................................................................ 161
6.1.
161
6.2.
170
6.3.
183
6.4.
189
6.5.
190
............................................................................................................................................... 193
7.1.
193
7.2.
206
7.3.
213
7.4.
223
7.5.
231
7.6.
234
7.7.
235
.................................................................................................................................... 239
8.1.
239
8.2.
250
8.3.
263
8.4.
265
8.5.
288
8.6.
289
vi -
Asset Pipeline
.................................................................................................................. 293
9.1.
293
9.2.
303
9.3.
315
9.4.
327
9.5.
334
9.6.
336
10
.................................................................................................................... 339
10.1.
339
10.2.
364
10.3.
386
10.4.
388
10.5.
388
10.6.
391
11
.................................................................................................................................. 393
11.1.
393
11.2.
402
11.3.
412
11.4.
431
11.5.
442
11.6.
444
12
12.1.
...................................................................................................................................... 447
452
12.2.
461
12.3.
485
12.4.
493
12.5.
493
- vii
Ruby
Rails
Ruby
Web
Web
Ru-
by on Rails
Ruby on Rails Tutorial
Michael Hartl
Ruby on Rails Tutorial
Ruby is a delightful computer language explicitly designed to make programmers happy. This philosophy influenced
David Heinemeier Hansson to pick Ruby when implementing the Rails web framework. Ruby on Rails, as its often
called, makes building custom web applications faster and easier than ever before. In the past few years, the Ruby on
Rails Tutorial has become the leading introduction to web development with Rails.
In our interconnected world, computer programming and web application development are rapidly rising in importance, and I am excited to support Ruby on Rails in China. Although it is important to learn English, which is the international language of programming, its often helpful at first to learn in your native language. It is for this reason
that I am grateful to Andor Chen for producing the Chinese-language edition of the Ruby on Rails Tutorial book.
Ive never been to China, but I definitely plan to visit some day. I hope Ill have the chance to meet some of you when
I do!
Best wishes and good luck,
Michael Hartl
Author
The Ruby on Rails Tutorial
ix
CD Baby
PHP
Ruby on Rails
Michael Hartl
Ruby on Rails Tutorial
Rails
Test-driven Development
Rails
Rails
TDD
Rails
TDD
Git Bitbucket Heroku
1.
xi
Ruby on Rails
Prochazka
Debra Williams Cauley
RailsSpace
Aurelius
Aure
RailsSpace
Ruby
David Heinemeier Hansson Yehuda Katz Carl Lerche Jeremy Kemper Xavier Noria Ryan Bates Geoffrey Grosenbach Peter
Cooper Matt Aimonetti Mark Bates Gregg Pollack Wayne E. Seguin Amy Hoy Dave Chelimsky Pat
Maddox Tom Preston-Werner Chris Wanstrath Chad Fowler Josh Susser Obie Fernandez Ian McFarland
Steven Bristol Pratik Naik Sarah Mei Sarah Allen Wolfram Arnold Alex Chaffee Giles Bowkett Evan
Dorn Long Nguyen James Lindenbaum Adam Wiggins Tikhon Bernstam Ron Evans Wyatt Greene
Miles Forrest Pivotal Labs
Heroku
thoughtbot
GitHub
xiii
Michael Hartl
SoftCover
Rails
Rails
Ruby on
Ruby
Insoshi
2011
Y Combinator
Ruby Hero
Ruby
RSpec
Rails
Rails
Rails
xv
Ruby on Rails Tutorial: Learn Web Development with Rails (Third Edition)
Michael Hartl
Michael Hartl
MIT
Beerware
/*
* ---------------------------------------------------------------------------* "THE BEERWARE LICENSE" (Revision 43):
* Michael Hartl wrote this code. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return.
* ---------------------------------------------------------------------------*/
xvii
Ruby on Rails
Rails
Web
Ruby on Rails Web
Ruby Rails HTML CSS
Web
Rails
MVC
Web
Web
Web
Ru-
REST
by
1
Web
hello_app
ple_app
toy_app
1.3
3
sam-
12
Rails
Web
Web
1.1.1
Web
Web
Rails
1.1
Rails
1.2
Rails
MiniTest
RSpec Cucumber Capybara Factory Girl
Guard Spork
Rails
Web
Ruby on Rails
Rails
1.
RubyTest
1.2
hello_app
http://railstutorial-china.org
Git
1.4
1.5
Rails
1.2
toy_app
URI
URL
sample_app
Rails
integration test
Ruby
5
Test-driven Development
4
3
10
11
1.2
12
Rails
Rails
scaffold
TDD
15
generate scaffold
Rails
Rails
2
3
Rails
Web
1.1.
Ruby on Rails
Rails
Airbnb Basecamp
Rails
Web
Disney Github
Rails
Rails
2004
Rails
Hulu Kickstarter Shopify Twitter
Yellow Pages
Web
ENTP thoughtbot Pivotal Labs Hashrocket
HappyFunCorp
Rails
Rails
Ruby
Domain-specific Language
DSL
URL
Rails
HTML
Rails
Web
Web
Ruby
MIT
Rails
Web
Rails
Web
REST
Rails
2. URI
Locator
2-
Web
URL
David
Uniform Resource
Heinemeier Hansson
Rails
Rails
Merb
Merb
API
Rails
gem
gem
IRC
Rails
1.1.1.
MiniTest
Unix
HTML
CSS
HTML
Rails
JavaScript
Ruby
SQL
Rails
Web
Rails
Ruby
Ruby
Chris Pine
Web
Learn to Program
Daniel Kehoe
Peter Cooper
Ruby
Ruby
Learn Ruby on Rails
Ruby
Ruby
Rails
Try Ruby
Rails
Code School
Tealeaf Academy
Rails
Thinkful
Ryan Bates
RailsCasts
RailsApps
Rails
Rails
Rails
Rails
1.1.2.
Unix
3.
Rails
https://selfstore.io/products/13
1.1.
-3
1.2
Windows
Unix
1.2.1
Linux
Rails
Web
rails server
1.3.2
$ rails server
Unix
production.rb
config/environments/production.rb
IDE
/home/ubuntu/workspace/sample_app/
production.rb
/home/ubuntu/workspace/sample_app/config/environments/production.rb
config/environments/production.rb
shell
Ruby
Rails
RED
4-
GREEN
1.2.
Rails
Ruby
Rails
Development Environment
Integrated
IDE
1.1
IDE
Rails
Web
Windows
4
InstallRails.com
1.2.1.
Rails
Cloud9
Rails
Cloud9
Ruby RubyGems
Git
IDE
Web
1.1
IDE
1.2.2
1.1
4.
5.
Windows
foo
Rails
Rails
IDE
InstallRails
Rails
def foo
1.2.
-5
1.
Cloud9
2.
Go to your Dashboard
3.
4.
rails-tutorial
5.
rails_tutorial
Rails
Create
6. Cloud9
Start editing
1.2
Cloud9
Ruby
Code Editor (Ace) Ace
Tabs
6-
1.3
Soft
Save
1.3
1.2.2.
Cloud9
Rails
gem
IDE
1.1
1.1
Rails
1.1
Rails
RubyGems
Rails
Rails
1.3.
hello, world!
Rails
1.2.1
6. Cloud9
hello, world
1.3.4
rails new
Cloud9 IDE
1.5
Rails
workspace
Rails
1.3.
-7
1.2
1.2
cd
Unix
mkdir
1.3
1.2
workspace
Rails
$ cd
$ mkdir workspace
$ cd workspace/
#
#
#
workspace
workspace
1.3
Windows
Unix
Mac OS X
Unix
Unix
Linux
shell
Bash
mkdir
mv
cp
cd
GUI
shell
Unix
1.1
Unix
Conquering the Command Line
Mark Bates
1.1
Unix
ls
$ ls -l
mkdir <dirname>
$ mkdir workspace
cd <dirname>
$ cd workspace/
$ cd ..
$cd ~
$ cd
$ cd ~/workspace/
IDE
Rails
8-
mv <source> <target>
$ mv README.rdoc README.md
cp <source> <target>
$ cp README.rdoc README.md
rm <file>
$ rm README.rdoc
rmdir <directory>
$ rmdir workspace/
rm -rf <directory>
$ rm -rf tmp/
cat <file>
$ cat ~/.ssh/id_rsa.pub
1.3
4.2.0
1.1
Rails
1.3
Rails
1.3
Rails
rails new
$ cd ~/workspace
$ rails _4.2.0_ new hello_app
create
create README.rdoc
create Rakefile
create config.ru
create .gitignore
create Gemfile
create app
create app/assets/javascripts/application.js
create app/assets/stylesheets/application.css
create app/controllers/application_controller.rb
.
.
.
create test/test_helper.rb
create tmp/cache
create tmp/cache/assets
create vendor/assets/javascripts
create vendor/assets/javascripts/.keep
create vendor/assets/stylesheets
create vendor/assets/stylesheets/.keep
run bundle install
Fetching gem metadata from https://rubygems.org/..........
Fetching additional metadata from https://rubygems.org/..
Resolving dependencies...
Using rake 10.3.2
Using i18n 0.6.11
.
.
.
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.
run bundle exec spring binstub --all
* bin/rake: spring inserted
* bin/rails: spring inserted
rails new
1.3
bundle install
1.3.1
rails new
1.4
Rails
Rails
1.2
Pipeline
5.2.1
CSS
app/assets
Asset Pipeline
Asset
JavaScript
1.3.
-9
1.4
1.2
Rails
Rails
/
app/
app/assets
CSS
bin/
config/
db/
doc/
lib/
lib/assets
log/
public/
bin/rails
test/
tmp/
10 -
CSS
JavaScript
JavaScript
/
vendor/
gem
vendor/assets
CSS
JavaScript
README.rdoc
Rakefile
rake
Gemfile
gem
Gemfile.lock
gem
config.ru
Rack
.gitignore
Git
gem
1.3.1. Bundler
Rails
Bundler
rails new
gem
4
1.4
Bundler
Bundler
1.4
1.5
Ruby
Refresh File Tree
hello_app
gem
1.3
bundle install
Gemfile
Ruby
1.5
Gemfile
source 'https://rubygems.org'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '4.2.0'
# Use sqlite3 as the database for Active Record
gem 'sqlite3'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0.0.beta1'
# 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'
# 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', '~> 2.0'
# bundle exec rake doc:rails generates the API under doc/api.
gem 'sdoc', '~> 0.4.0', group: :doc
1.3.
- 11
gem
gem
gem
gem
Bundler
gem 'sqlite3'
gem
Rails
12 -
uglifier
Asset Pipeline
Bundler
Gemfile
1.5
4.0.0
4.1
coffee-rails
~> 4.0.0
>=
4.0.0
4.0
gem
Gemfile
4.0.1
4.1
gem
~>
1.4
Gemfile
1.5
sqlite3
7.1.1
Heroku
1.5
1.5
Gemfile
Ruby gem
source 'https://rubygems.org'
gem
gem
gem
gem
gem
gem
gem
gem
'rails',
'sass-rails',
'uglifier',
'coffee-rails',
'jquery-rails',
'turbolinks',
'jbuilder',
'sdoc',
'4.2.0'
'5.0.0.beta1'
'2.5.3'
'4.1.0'
'4.0.0.beta2'
'2.3.0'
'2.2.3'
'0.4.0', group: :doc
1.3.
- 13
gem
gem
gem
gem
end
'sqlite3',
'byebug',
'web-console',
'spring',
'1.3.9'
'3.4.0'
'2.0.0.beta3'
'1.1.3'
Gemfile
1.5
bundle install
gem
$ cd hello_app/
$ bundle install
Fetching source index for https://rubygems.org/
.
.
.
bundle install
1.3
bundle install
1.3.1
Rails
rails server
1.6
Cloud9
1.7
IP
8
Rails
$IP
Cloud9
echo $IP
1.6
$PORT
IP
echo $PORT
Rails
$ cd ~/workspace/hello_app/
$ rails server
=> Booting WEBrick
=> Rails application starting on http://localhost:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
1.7
IDE
Rails
$ cd ~/workspace/hello_app/
$ rails server -b $IP -p $PORT
=> Booting WEBrick
=> Rails application starting on http://0.0.0.0:8080
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
rails server
1.6
1.7
Ctrl-C
http://localhost:3000/
7.
3.1
8.
14 -
install
80
bundle
bundle install
IDE
Share
Application
1.8
1.9
1.6
About your applications environment
1.10
Rails
Rails
1.3.4
1.3.
- 15
1.7
1.8
16 -
Rails
1.9
rails server
Rails
1.10
1.3.
- 17
1.3.3.
Rails
1.11
1.4
app/
trollers
logic
Rails
business logic
models
MVC
views
con-
domain
Web
GUI
1.11
Rails
Rails
request
MVC
Web
view
model
Rails
HTML
Ruby
HTML
2.2.2
3.2
18 -
MVC
MVC
6.1
1.3.4
MVC
MVC
7.1.2
hello, world!
Rails
hello, world!
1.9
ApplicationController
hello
ApplicationController
2
$ ls app/controllers/*_controller.rb
hello
render
1.8
hello, world!
Ruby
4
1.8
ApplicationController
app/controllers/application_controller.rb
hello
Rails
1.11
Rails
1.11
2.2.2
URL
1.10
URL
http://www.example.com/
/
1.9
welcome
config/routes.rb
Rails
index
1.10
troller
hello
Rails
ApplicationCon-
1.1.2
1.9
config/routes.rb
Rails.application.routes.draw do
.
.
.
# You can have the root of your site routed with "root"
# root 'welcome#index'
.
.
.
end
1.3.
- 19
1.10
config/routes.rb
Rails.application.routes.draw do
.
.
.
# You can have the root of your site routed with "root"
root 'application#hello'
.
.
.
end
1.8
1.10
1.12
1.4.
Git
Rails
20 -
hello, world!
hello, world!
1.12
Rails
Git
Git
Linus Torvalds
Linux
Scott Chacon
Git
9
Pro Git
Git
Rails
1.4.3
Git
1.5
1.4.1.
1.2.1
Rails.com
IDE
Git
Git
IDE
Install-
Git
$
$
$
$
git
git
git
git
config
config
config
config
--global
--global
--global
--global
Git
co
Git
co
checkout
checkout
git co
$ git init
Initialized empty Git repository in /home/ubuntu/workspace/hello_app/.git/
git add -A
$ git add -A
.gitignore
rails new
Rails
10
.gitignore
status
staging area
$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
9.
10.
http://git-scm.com/book/zh
https://selfstore.io/products/46
3.7
1.4.
Git
- 21
new
new
new
new
new
.
.
.
file:
file:
file:
file:
file:
.gitignore
Gemfile
Gemfile.lock
README.rdoc
Rakefile
commit
Git
-m
Git
-m
Git
1.4.4
git push
log
$ git log
commit df0a62f3f091e53ffa799309b3e32c27b0b38eb4
Author: Michael Hartl <michael@michaelhartl.com>
Date:
Wed August 20 19:44:43 2014 +0000
Initialize repository
q
1.4.2.
Git
app/controllers/
$ ls app/controllers/
application_controller.rb concerns/
$ rm -rf app/controllers/
$ ls app/controllers/
ls: app/controllers/: No such file or directory
Unix
ls
$ git status
On branch master
22 -
app/controllers/
rm
-rf
app/controllers/application_controller.rb
no changes added to commit (use "git add" and/or "git commit -a")
checkout
-f
$ git checkout -f
$ git status
# On branch master
nothing to commit (working directory clean)
$ ls app/controllers/
application_controller.rb concerns/
1.4.3. Bitbucket
Git
Git
Git
Bitbucket
Bitbucket
GitHub
1.4 GitHub
Git
GitHub
Bitbucket
1.4
Bitbucket
Bitbucket
Bitbucket
GitHub
Bitbucket
GitHub
Web
Web
Bitbucket
GitHub
Github
Bitbucket
Bitbucket
1.
Bitbucket
2.
IDE
cat
1.11
1.11
bucket
Bit-
Git
- 23
1.4.
3.
Manage account
1.13
1.13
1.11
SSH
cat
$ cat ~/.ssh/id_rsa.pub
Create
This is a private repository
Command line > I have an existing project
1.12
you sure you want to continue connecting (yes/no)?
1.12
1.14
Create repository
>
1.12
Are
yes
Bitbucket
Git
-u
Bitbucket
git set upstream
Bitbucket
hello_app
1.15
24 -
<username>
1.14
Bitbucket
1.15
Bitbucket
1.4.
Git
- 25
1.4.4.
1.4.3
README.rdoc
Bitbucket
1.16
rdoc
Markdown
README
Bitbucket
README.rdoc
README.md
Git
1.16
11
Bitbucket
README
Git
master
checkout
topic branch
11.
26 -
Git
Atlassian
SourceTree
-b
master
* modify-README
git branch
checkout -b modify-README
git
modify-README
co
1.4
git co -b modify-README
12
README
RDoc
.md
Markdown
Bitbucket
Git
Unix mv
$ git mv README.rdoc README.md
README.md
1.13
README
1.13
README.md
$ git status
On branch modify-README
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed:
README.md
git add -A
1.4.1.2
git mv
12.
Pro Git
Git
git commit
-a
Git
1.4.
Git
- 27
$ git commit -a -m
2 files changed, 5
delete mode 100644
create mode 100644
-a
git add -A
Git
Git
Git
Shiny new commit styles
Git
Git
git branch -d
git branch -D
#
$
$
$
$
$
$
-D
README
1.4.3
28 -
Bitbucket
origin master
git push
$ git push
Bitbucket
Markdown
1.17
1.17
README
Markdown
1.5.
Rails
13
Rails
Rails
Phusion Passenger
Engine Yard
Rails Machine
Heroku
Heroku
13.
14.
Rails
Git
Apache
Nginx 14 Web
Engine Yard Cloud
Heroku
Rails
Web
Ninefold
Rails
1.4
Git
Heroku
1.5.4
Engine X
1.5.
- 29
1.5.1.
Heroku
PostgreSQL15
Heroku
pg
Rails
PostgreSQL
group :production do
gem 'pg',
'0.17.1'
gem 'rails_12factor', '0.0.2'
end
rails_12factor
Gemfile
Heroku
gem
1.14
1.14
gem
Gemfile
source 'https://rubygems.org'
gem
gem
gem
gem
gem
gem
gem
gem
'rails',
'sass-rails',
'uglifier',
'coffee-rails',
'jquery-rails',
'turbolinks',
'jbuilder',
'sdoc',
group
gem
gem
gem
gem
end
'4.2.0'
'5.0.0.beta1'
'2.5.3'
'4.1.0'
'4.0.0.beta2'
'2.3.0'
'2.2.3'
'0.4.0', group: :doc
:development, :test do
'sqlite3',
'1.3.9'
'byebug',
'3.4.0'
'web-console', '2.0.0.beta3'
'spring',
'1.1.3'
group :production do
gem 'pg',
'0.17.1'
gem 'rails_12factor', '0.0.2'
end
bundle install
gem
pg
rails_12factor
1.14
gem
pg
gem
rails_12factor
Gemfile.lock
15.
post-gres-cue-ell
Postgres
16.
SQLite
PostgreSQL
30 -
3.1
16
Heroku
Heroku
Heroku
$ heroku version
IDE
heroku
Heroku
Heroku Toolbelt
heroku
Heroku
SSH
$ heroku login
$ heroku keys:add
heroku create
Heroku
1.15
1.15
Heroku
$ heroku create
Creating damp-fortress-5769... done, stack is cedar
http://damp-fortress-5769.herokuapp.com/ | git@heroku.com:damp-fortress-5769.git
Git remote heroku added
heroku
1.5.2. Heroku
Git
Heroku
7.5
1.5.3. Heroku
heroku create
1.15
IDE
heroku open
1.18
1.12
1.5.
- 31
1.18
Heroku
1.5.4. Heroku
Heroku
$ heroku rename rails-tutorial-hello
Heroku
hwpcbmze.herokuapp.com
seyjhflo.herokuapp.com
jhyicevg.herokuapp.com
Ruby
('a'..'z').to_a.shuffle[0..7].join
17
Heroku
Heroku
Heroku
Heroku
Heroku
17.
32 -
Heroku
Github Pages
http://railstutorial-china.org
1.6.
Rails
Facebook
Ruby on Rails
http://railstutorial-china.org/
Ruby on Rails
Rails
1.6.1.
Ruby on Rails
Ruby
Web
Rails
rails
Rails
rails new
rails server
hello, world!
Git
Bitbucket
Heroku
1.7.
1.
hello
1.8
hello, world!
Hola, mundo!
hello
2.
world!
18.
hola, mundo!
Rails
1.8
1.10
ASCII
18
1.19
goodbye
goodbye
goodbye,
1.20
1.6.
- 33
1.19
1.20
34 -
hello
Hola, mundo!
goodbye, world!
Rails
Ruby on Rails
Web
1.2
URL
Rails
Rails
REST
users
microposts
Twitter
3
Rails
2.1.
rails new
1.3
$ cd ~/workspace
$ rails _4.2.0_ new toy_app
$ cd toy_app/
rails new
Rails
Rails
1.2.1
IDE
2.1
2.1
Gemfile
source 'https://rubygems.org'
gem
gem
gem
gem
gem
gem
gem
gem
'rails',
'sass-rails',
'uglifier',
'coffee-rails',
'jquery-rails',
'turbolinks',
'jbuilder',
'sdoc',
group
gem
gem
gem
gem
end
'4.2.0'
'5.0.0.beta1'
'2.5.3'
'4.1.0'
'4.0.0.beta2'
'2.3.0'
'2.2.3'
'0.4.0', group: :doc
:development, :test do
'sqlite3',
'1.3.9'
'byebug',
'3.4.0'
'web-console', '2.0.0.beta3'
'spring',
'1.1.3'
35
group :production do
gem 'pg',
'0.17.1'
gem 'rails_12factor', '0.0.2'
end
2.1
1.5.1
1.14
--without production
gem
gem
Git
$ git init
$ git add -A
$ git commit -m "Initialize repository"
Bitbucket
Create
2.1
36 -
Bitbucket
2.1
1.8
1.9
Heroku
$ git commit -am "Add hello"
$ heroku create
$ git push heroku master
1.5
7.5
Heroku
1.18
Web
2.1.1
data model
2.1.2
2.1.1.
id
email
integer
name
string
string
2.2
2.2
6.1.1
2.2
users
id
name
2.1.2.
id
2
content
text
user_id
2.3
2.3
1.
2.
Rails
string
Heroku
text
2.1.
- 37
user_id
2.3.3
11
2.2.
2.1.1
Users Resource
read
ate
scaffold
User
Web
update
rails generate
cre-
HTTP
delete
Rails
Rails
scaffold
User
3.
38 -
Users
name:string
id
email:string
2.2
Rails
primary key
Rake
2.1
migrate
6.1.1
Gemfile
bundle exec
Rake
rake
IDE
bundle exec
bundle exec
Web
$ rails server -b $IP -p $PORT
1.7
`rails server`
1.3.2
IDE
IDE
2.1
Unix
Rake
make
Unix
Rake
Linux
Ruby
Mac OS X
make
Ruby
Web
make
Rails
Rake
rake db:migrate
rake -T db
$ bundle exec rake -T db
Rake
$ bundle exec rake -T
2.2.1.
URL http://localhost:3000/
4. rails
Rails
1.9
/users
/users/
bundle exec
2.2.
- 39
new
2.1
URL
2.1
URL
URL
/users
index
/users/1
show
/users/new
new
/users/1/edit
edit
2.4
http://localhost:3000
ID
ID
2.4
/users
2.5
IDE
Create User
URL
40 -
2.6
/users/1
flash message
2.2
id 7.1
7.4.2
2.5
2.6
/users/new
/users/1
2.2.
- 41
2.7
2.8
Update User
6
9.1
2.7
/users/1/edit
2.9
2.10
JavaScript Rails
42 -
JavaScript
9.4
2.10
7.1
2.8
2.9
/users
2.2.
- 43
2.10
2.2.2. MVC
MVC
1.
1.3.3
MVC
2.11
/users
2. Rails
/users
UsersController
3. index
index
User.all
4.
5.
@users
6.
7.
Ruby
8.
HTML
HTML
5
HTML
5.
44 -
index
Web
Apache
Nginx
2.11
Rails
MVC
1
Rails
3.2
URL
URL
2.1
2.2
:users
Symbol
2.2
4.3.3
Rails
config/routes.rb
Rails.application.routes.draw do
resources :users
.
.
.
end
/users
1.10
2.2.
- 45
# root 'welcome#index'
root 'application#hello'
ApplicationController
hello
UsersController
ApplicationController
2.3
index
hello
UsersController
2.3
config/routes.rb
Rails.application.routes.draw do
resources :users
root 'users#index'
.
.
.
end
UsersController
2.2.1
4.4
2.4
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def index
.
.
.
end
def show
.
.
.
end
def new
.
.
.
end
def edit
.
.
.
46 -
2.4
Ruby
2.3.4
end
def create
.
.
.
end
def update
.
.
.
end
def destroy
.
.
.
end
end
index
create
update
show
new
edit
2.2.1
destroy
2.2
2.2
REST
Rails
Roy Fielding
show
HTTP
HTTP
URL
GET
/users
index
GET
/users/1
show
GET
/users/new
new
POST
/users
create
GET
/users/1/edit
edit
PATCH
/users/1
update
ID
DELETE
/users/1
destroy
ID
2000
update
3.3
2.2
6.
chitectures
2.2
2.2
REST
HTTP
REST
ID
ID
2.2.
- 47
2.2
REST
update
POST
GET
PATCH
Rails
REST
REST
REST
resource
Rails
DELETE
3.3
REST
create
CRUD
delete
3.2
HTTP
HTTP
REST
REST
12
index
2.5
index
2.5
app/controllers/users_controller.rb
@users = User.all
4
2.6
Active Record
2.11
3
at-users
2.3.4
@users
5
4.4
Rails
User.all
2.6
app/models/user.rb
class User < ActiveRecord::Base
end
@users
2.7
@users
7. Rails
48 -
2.7
index.html.erb
instance variable
HTML
PUT
HTTP
PATCH
2.7
app/views/users/index.html.erb
<h1>Listing users</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th colspan="3"></th>
</tr>
</thead>
<% @users.each do |user| %>
<tr>
<td><%= user.name %></td>
<td><%= user.email %></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 %>
</table>
<br>
<%= link_to 'New User', new_user_path %>
HTML
2.2.3.
Rails
2.3.
2.2
Rails
REST
2.3.
- 49
2.3.1.
rails generate scaffold
2.3
Spring
2.2
$ bundle exec rake db:migrate
== CreateMicroposts: migrating ===============================================
-- create_table(:microposts)
-> 0.0023s
== CreateMicroposts: migrated (0.0026s) ======================================
generate Micropost
8.
50 -
2.2.1
Rails
2.8
2.3
MicropostsController
URL
2.8
resources :micropsts
Rails
config/routes.rb
Rails.application.routes.draw do
resources :microposts
resources :users
.
.
.
end
2.3
2.8
REST
HTTP
URL
GET
/microposts
index
GET
/microposts/1
show
GET
/microposts/new
new
POST
/microposts
create
GET
/microposts/1/edit
edit
PATCH
/microposts/1
update
ID
DELETE
/microposts/1
destroy
ID
MicropostsController
ID
ID
2.9
ostsController
UsersController
2.4
Microp-
REST
MicropostsController
app/controllers/microposts_controller.rb
2.9
9.
2.8
Ruby
2.3.
- 51
.
.
end
def new
.
.
.
end
def edit
.
.
.
end
def create
.
.
.
end
def update
.
.
.
end
def destroy
.
.
.
end
end
/microposts/new
2.12
user_id
2.2.1
52 -
2.13
2.12
2.13
/microposts
2.3.
- 53
2.3.2.
Rails
tion
140
IDE
2.10
validaTwitter
2.10
app/models/micropost.rb
140
app/models/micropost.rb
class Micropost < ActiveRecord::Base
validates :content, length: { maximum: 140 }
end
6.2
140
2.14
Rails
7.3.3
2.14
2.3.3.
Rails
association
2.11
2.12
2.11
app/models/user.rb
54 -
2.12
app/models/micropost.rb
class Micropost < ActiveRecord::Base
belongs_to :user
validates :content, length: { maximum: 140 }
end
microposts
2.15
user_id
Rails
Active Record
2.15
11
12
Twitter
Rails
console
rails console
User.first
first_user
10
$ rails console
>> first_user = User.first
=> #<User id: 1, name: "Michael Hartl", email: "michael@example.org",
created_at: "2014-07-21 02:01:31", updated_at: "2014-07-21 02:01:31">
>> first_user.microposts
=> [#<Micropost id: 1, content: "First micropost!", user_id: 1, created_at:
"2014-07-21 02:37:37", updated_at: "2014-07-21 02:37:37">, #<Micropost id: 2,
content: "Second micropost", user_id: 1, created_at: "2014-07-21 02:38:54",
updated_at: "2014-07-21 02:38:54">]
>> micropost = first_user.microposts.first
# Micropost.first would also work.
=> #<Micropost id: 1, content: "First micropost!", user_id: 1, created_at:
"2014-07-21 02:37:37", updated_at: "2014-07-21 02:37:37">
>> micropost.user
=> #<User id: 1, name: "Michael Hartl", email: "michael@example.org",
created_at: "2014-07-21 02:01:31", updated_at: "2014-07-21 02:01:31">
>> exit
exit
Ctrl-D
first_user.microposts
ID
Ctrl-C
Ctrl-d
user_id
first_user
12
10.
11.
Active Record
11
11
>>
Ruby
Ctrl-D
2.3.
- 55
2.3.4.
Rails
Object-oriented Programming
OOP
OOP
4.4
<
ActiveRecord::Base
2.13
Active Record
2.14
User
Micropost
2.16
ActiveRecord::Base
Ruby
2.13 User
app/models/user.rb
class User < ActiveRecord::Base
.
.
.
end
2.14 Mcropost
app/models/micropost.rb
class Micropost < ActiveRecord::Base
.
.
.
end
2.16
2.15
croposts Controller
ActionController::Base
ApplicationController
ActionController::Base
2.17
UsersController
app/controllers/users_controller.rb
2.15
56 -
Rails
2.16
2.17
Action Pack
UsersController
ApplicationController
Mi-
2.16 MicropostsController
app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
.
.
.
end
2.17 ApplicationController
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
.
.
.
end
2.17
UsersController
MicropostsController
ActionController::Base
UsersController
HTTP
MicropostsController
HTML Rails
ApplicationController
8.4
ApplicationController
2.3.5.
Bitbucket
$ git status
$ git add -A
2.3.
- 57
1.5
Heroku
2.1
ate
Heroku
Heroku
PostgreSQL
2.18
2.18
2.4.
Rails
58 -
Rails
hreoku cre-
MVC
REST
Web
2.4.1.
Web
Rails
MVC
Rails
Web
REST
URL
Rails
Rails
Rails
Rails
2.5.
1.
2.18
2.19
2.
2.19
FILL_IN
name
2.20
2.18
app/models/micropost.rb
2.5.
- 59
2.19
app/models/user.rb
class User < ActiveRecord::Base
has_many :microposts
validates FILL_IN, presence: true
validates FILL_IN, presence: true
end
2.19
60 -
2.20
2.5.
- 61
Rails
HTML
Rails
automated testing
3.1.
2
sample_app
Rails
3.1
3.1
$ cd ~/workspace
$ rails _4.2.0_ new sample_app
$ cd sample_app/
2.1
IDE
Gemfile
2.1
1.5
test
2.1
gem
gem
gem
3.2
3.7
11.66
Gemfile
3.2
source 'https://rubygems.org'
gem
gem
gem
gem
gem
gem
gem
'rails',
'sass-rails',
'uglifier',
'coffee-rails',
'jquery-rails',
'turbolinks',
'jbuilder',
1.
IDE
'4.2.0'
'5.0.0.beta1'
'2.5.3'
'4.1.0'
'4.0.0.beta2'
'2.3.0'
'2.2.3'
Goto Anything
Gemfile
workspace
Gemfile Gemfile.lock
rm -rf hello_app/ toy_app/
1.1
Bitbucket
63
gem 'sdoc',
group
gem
gem
gem
gem
end
:development, :test do
'sqlite3',
'1.3.9'
'byebug',
'3.4.0'
'web-console', '2.0.0.beta3'
'spring',
'1.1.3'
group
gem
gem
gem
end
:test do
'minitest-reporters', '1.0.5'
'mini_backtrace',
'0.1.3'
'guard-minitest',
'2.3.1'
group :production do
gem 'pg',
'0.17.1'
gem 'rails_12factor', '0.0.2'
end
bundle install
production
Gemfile
--without
gem
gem
PostgreSQL
pg gem
SQLite Heroku
SQLite
PostgreSQL
Gemfile
Rails
bundle update
gem
gem
$ bundle update
Git
$ git init
$ git add -A
$ git commit -m "Initialize repository"
README
RDoc
Markdown
3.3
2.
Bundler
3.
configure postgresql <your system>
64 -
bundle install
rails postgresql setup
IDE
PostgreSQL
<your system>
install
Ubuntu
README
3.3
1.4.4
-m
-a
Bitbucket
$ git remote add origin git@bitbucket.org:<username>/sample_app.git
$ git push -u origin --all #
1.8
Heroku
hello, world!
1.9
Heroku
$ git commit -am "Add hello"
$ heroku create
$ git push heroku master
1.5
7.5
Heroku
1.18
Heroku
$ heroku logs
Heroku
7.5
Unicorn
3.2.
Rails
HTML Rails
2
1.3
app/controllers
1.4.4
Git
Rails
MVC
C
REST
1.4
1.3.3
app/views
Git
3.2.
- 65
master
static-pages
3.2.1.
generate
2
StaticPages
home
help
about
3.3
3.4
3.4
$ rails generate controller StaticPages home help
create app/controllers/static_pages_controller.rb
route get 'static_pages/help'
route get 'static_pages/home'
invoke erb
create
app/views/static_pages
create
app/views/static_pages/home.html.erb
create
app/views/static_pages/help.html.erb
invoke test_unit
create
test/controllers/static_pages_controller_test.rb
invoke helper
create
app/helpers/static_pages_helper.rb
invoke
test_unit
create
test/helpers/static_pages_helper_test.rb
invoke assets
invoke
coffee
create
app/assets/javascripts/static_pages.js.coffee
invoke
scss
create
app/assets/stylesheets/static_pages.css.scss
rails generate
rails g
Rails
3.1
Rails
3.1
Rails
$ rails server
$ rails s
$ rails console
$ rails c
$ rails generate
$ rails g
$ bundle install
$ bundle
$ rake test
$ rake
Git
66 -
generate
$
$
$
$
git
git
git
git
status
add -A
commit -m "Add a Static Pages controller"
push -u origin static-pages
static-pages
Bitbucket
$ git push
3.4
StaticPages
static_pages_controller.rb
Rails
Ruby
Ruby
4.4
underscore
3.1
Rails
3.1
Rails
Rails
Rails
3.4
2.2
rails generate
2.3
routes.rb
rails destroy
Rails
6
$ rails generate model User name:string email:string
3.2.
- 67
config/routes.rb
3.4
URL
Rails
home
3.5
config
help
home
config/routes.rb
Rails.application.routes.draw do
get 'static_pages/home'
get 'static_pages/help'
.
config
3.1
3.1
68 -
2.11
1.3.4
help
3.5
.
.
end
get 'static_pages/home'
home
/static_pages/home
GET HTTP
get
home
3.2
/static_pages/home
1.3.2
$ rails server -b $IP -p $PORT
/static_pages/home
GET
`rails server`
3.2
3.2
/static_pages/home
3.2 GET
HTTP
GET
Chrome Firefox
Apache
POST
PATCH
DELETE
Safari
Nginx
REST
Web
Rails
Web
3.2.
- 69
Rails
HTTP
REST
GET
HTTP
http://www.google.com
POST
POST
POST
Rails
POST
HTTP
POST
PATCH
GET
GET
http://www.wikipedia.org
DELETE
POST
Web
Rails
Rails
3.6
REST
2
REST
3.6
3.4
app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
def home
end
def help
end
end
class
static_pages_controller.rb
troller
StaticPagesCon-
home
<
2.3.4
StaticPagesController
Rails
help
def
ApplicationController
4.4
def home
end
def help
end
Ruby
Ruby
/static_pages/home
MVC
V
ApplicationController
home
/static_pages/home
home
HTML
70 -
.erb
3.4
3.7
Rails
home
Rails
1.3.3
3.4
home.html.erb
StaticPagesController
Rails
.html
3.7
app/views/static_pages/home.html.erb
<h1>StaticPages#home</h1>
<p>Find me in app/views/static_pages/home.html.erb</p>
help
3.8
3.8
app/views/static_pages/help.html.erb
<h1>StaticPages#help</h1>
<p>Find me in app/views/static_pages/help.html.erb</p>
h1
p
3.2.2.
3.4
Rails
HTML
3.9
Rails
3.10
3.9
HTML
app/views/static_pages/home.html.erb
<h1>Sample App</h1>
<p>
This is the home page for the
<a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</p>
3.10
HTML
app/views/static_pages/help.html.erb
<h1>Help</h1>
<p>
Get help on the Ruby on Rails Tutorial at the
<a href="http://www.railstutorial.org/#help">Rails Tutorial help section</a>.
To get help on this sample app, see the
<a href="http://www.railstutorial.org/book"><em>Ruby on Rails Tutorial</em>
book</a>.
</p>
3.3
3.4
3.2.
- 71
3.3
3.4
72 -
3.3.
Rails
Driven Development
TDD
TDD
TDD
Test-
TDD
3.3
3.3
1.
regression
2.
3.
TDD
HTML
4.
Rails
3.3.
- 73
3.3.1.
3.3
Rails
Ruby
rails generate controller
Rails
3.4
$ ls test/controllers/
static_pages_controller_test.rb
3.11
3.11
GREEN
test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionController::TestCase
test "should get home" do
get :home
assert_response :success
end
test "should get help" do
get :help
assert_response :success
end
end
3.4
GET
get
:success
3.2
HTTP
200 OK
test "should get home" do
get :home
assert_response :success
end
home
GET
rake
bundle exec
5. 2.2
bundle exec
74 -
IDE
2.1
3.12
GREEN
GREEN
3.7.1
1
MiniTest
Spring
Rails
Ruby
3.7.3
Guard
3.3.2.
3.3
TDD
3.3.3
3.4.3
3.11
3.13
3.13
RED
test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionController::TestCase
test "should get home" do
get :home
assert_response :success
end
test "should get help" do
get :help
assert_response :success
end
test "should get about" do
get :about
assert_response :success
end
end
help
6.
home
about
rake test
3.7.1
3.3.
- 75
3.14
RED
3.3.3.
RED
GREEN
3.15
RED
3.5
3.16
about
3.16
RED
config/routes.rb
Rails.application.routes.draw do
get 'static_pages/home'
get 'static_pages/help'
get 'static_pages/about'
.
.
.
end
Rails
GET
/static_pages/about
about
3.17
RED
3.6
3.18
about
app/controllers/static_pages_controller.rb
3.18
7.
76 -
RED
3.7.2
Rails
3.2.1
home
home.html.erb
app/views/static_pages
about.html.erb
Unix touch
$ touch app/views/static_pages/about.html.erb
touch
IDE
1.3.1
about.html.erb
3.19
3.19
GREEN
app/views/static_pages/about.html.erb
<h1>About</h1>
<p>
The <a href="http://www.railstutorial.org/"><em>Ruby on Rails
Tutorial</em></a> is a
<a href="http://www.railstutorial.org/book">book</a> and
<a href="http://screencasts.railstutorial.org/">screencast series</a>
to teach web development with
<a href="http://rubyonrails.org/">Ruby on Rails</a>.
This is the sample application for the tutorial.
</p>
rake test
3.20
GREEN
3.5
3.3.
- 77
3.5
/static_pages/about
3.3.4.
3.4.3
3.4.
<title>
Search-Engine Optimization
SEO
<
ple App
rails new
78 -
3.2
$ mv app/views/layouts/application.html.erb layout_file
3.2
URL
/static_pages/home
"Home"
/static_pages/help
"Help"
/static_pages/about
"About"
3.4.1.
3.21
3.21
HTML
<!DOCTYPE html>
<html>
<head>
<title>Greeting</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
HTML
body
head
doctype
title
Greeting
HTML
Hello, world!
assert_select
3.2
assert_select
3.13
HTML
Sample App
3.22
RED
test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionController::TestCase
8. HTML
9. Rails
<!DOCTYPE html>
doctype
HTML
HTML5
MiniTest
3.4.
- 79
3.6
RED
3.23
RED
3.4.2.
3.21
3.9
3.24
3.24
HTML
RED
app/views/static_pages/home.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Home | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
<h1>Sample App</h1>
<p>
This is the home page for the
<a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</p>
</body>
</html>
3.6
80 -
10
HTML
3.6
3.25
HTML
3.25
3.26
RED
app/views/static_pages/help.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Help | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
<h1>Help</h1>
<p>
Get help on the Ruby on Rails Tutorial at the
<a href="http://www.railstutorial.org/#help">Rails Tutorial help
section</a>.
To get help on this sample app, see the
<a href="http://www.railstutorial.org/book"><em>Ruby on Rails
Tutorial</em> book</a>.
</p>
</body>
</html>
10.
Google Chrome
Safari
Chrome
3.4.
- 81
3.26
HTML
GREEN
app/views/static_pages/about.html.erb
<!DOCTYPE html>
<html>
<head>
<title>About | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
<h1>About</h1>
<p>
The <a href="http://www.railstutorial.org/"><em>Ruby on Rails
Tutorial</em></a> is a
<a href="http://www.railstutorial.org/book">book</a> and
<a href="http://screencasts.railstutorial.org/">screencast series</a>
to teach web development with
<a href="http://rubyonrails.org/">Ruby on Rails</a>.
This is the sample application for the tutorial.
</p>
</body>
</html>
GREEN
3.27
GREEN
3.4.3.
Ruby
Rails
Rails
HTML
HTML
Rails
provide
Home
3.28
home.html.erb
3.28
Ruby
app/views/static_pages/home.html.erb
82 -
DRY
GREEN
DRY
Ruby
.html.erb
ERb
HTML
11
ERb
provide
Rails
<%= %>
"Home"
:title
13
yield
Ruby
12
Ruby
<%= %>
ERb
3.29
GREEN
GREEN
3.30
3.30
Ruby
3.31
GREEN
app/views/static_pages/help.html.erb
<% provide(:title, "Help") %>
<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
</head>
11.
Haml
12.
Rails
13.
Ruby
content_for
Rails
provide
Asset Pipeline
Rails
3.4.
- 83
<body>
<h1>Help</h1>
<p>
Get help on the Ruby on Rails Tutorial at the
<a href="http://www.railstutorial.org/#help">Rails Tutorial help
section</a>.
To get help on this sample app, see the
<a href="http://www.railstutorial.org/book"><em>Ruby on Rails
Tutorial</em> book</a>.
</p>
</body>
</html>
3.31
Ruby
GREEN
app/views/static_pages/about.html.erb
<% provide(:title, "About") %>
<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
<h1>About</h1>
<p>
The <a href="http://www.railstutorial.org/"><em>Ruby on Rails
Tutorial</em></a> is a
<a href="http://www.railstutorial.org/book">book</a> and
<a href="http://screencasts.railstutorial.org/">screencast series</a>
to teach web development with
<a href="http://rubyonrails.org/">Ruby on Rails</a>.
This is the sample application for the tutorial.
</p>
</body>
</html>
ERb
<% provide(:title, "The Title") %>
<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
Contents
</body>
</html>
title
Rails
84 -
body
application.html.erb
3.4
$ mv layout_file app/views/layouts/application.html.erb
Ruby
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
3.32
3.32
GREEN
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
<%= stylesheet_link_tag
'application', media: 'all',
'data-turbolinks-track' => true %>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
</head>
<body>
<%= yield %>
</body>
</html>
/static_pages/home
home.html.erb
<%=
HTML
yield %>
Rails
<%= stylesheet_link_tag ... %>
<%= javascript_include_tag "application", ... %>
<%= csrf_meta_tags %>
Rails
JavaScript
csrf_meta_tags
3.28
3.30
3.31
3.33
3.33
Asset Pipeline
5.2.1
Cross-Site Request Forgery
HTML
3.34
CSRF
HTML
3.35
GREEN
app/views/static_pages/home.html.erb
<% provide(:title, "Home") %>
<h1>Sample App</h1>
<p>
This is the home page for the
<a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
3.4.
- 85
sample application.
</p>
3.34
HTML
GREEN
app/views/static_pages/help.html.erb
<% provide(:title, "Help") %>
<h1>Help</h1>
<p>
Get help on the Ruby on Rails Tutorial at the
<a href="http://www.railstutorial.org/#help">Rails Tutorial help section</a>.
To get help on this sample app, see the
<a href="http://www.railstutorial.org/book"><em>Ruby on Rails Tutorial</em>
book</a>.
</p>
3.35
HTML
GREEN
app/views/static_pages/about.html.erb
<% provide(:title, "About") %>
<h1>About</h1>
<p>
The <a href="http://www.railstutorial.org/"><em>Ruby on Rails
Tutorial</em></a> is a
<a href="http://www.railstutorial.org/book">book</a> and
<a href="http://screencasts.railstutorial.org/">screencast series</a>
to teach web development with
<a href="http://rubyonrails.org/">Ruby on Rails</a>.
This is the sample application for the tutorial.
</p>
3.36
GREEN
3.4.4.
1.3.4
routes.rb
2.2.2
hello
3.1
get
root 'static_pages#home'
86 -
3.5
3.37
static_pages/home
static_pages#home
home
3.7
GET
/static_pages/home
3.37
config/routes.rb
Rails.application.routes.draw do
root 'static_pages#home'
get 'static_pages/help'
get 'static_pages/about'
end
3.7
3.5.
Rails
3.2
Git
$ git add -A
$ git commit -m "Finish static pages"
3.5.
- 87
1.4.4
$ git checkout master
$ git merge static-pages
1.4.3
Bitbuck-
et
$ git push
Heroku
$ bundle exec rake test
$ git push heroku
3.5.1.
Rails
gem
config/routes.rb
Rails
HTML
Ruby
ERb
Rails
3.6.
#
$
#
$
$
$
$
88 -
add -A
commit -m "Add a Contact page (solves exercise 3.2)"
push -u origin static-pages-exercises
checkout master
1.
3.22
setup
Ruby on
3.38
2.2.2
4.2.2
14
3.39
3.38
3.38
3.13
/static_pages/contact
GREEN
test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionController::TestCase
def setup
@base_title = "Ruby on Rails Tutorial Sample App"
end
test "should get home" do
get :home
assert_response :success
assert_select "title", "Home | #{@base_title}"
end
test "should get help" do
get :help
assert_response :success
assert_select "title", "Help | #{@base_title}"
end
test "should get about" do
get :about
assert_response :success
assert_select "title", "About | #{@base_title}"
end
end
3.39
app/views/static_pages/contact.html.erb
<% provide(:title, "Contact") %>
<h1>Contact</h1>
<p>
Contact the Ruby on Rails Tutorial about the sample app at the
<a href="http://www.railstutorial.org/#contact">contact page</a>.
</p>
14.
5.3.1
3.6.
- 89
3.7.
3.7.1
3.7.2
3.7.3
3.7.1. MiniTest
Rails
3.40
3.2
minitest-reporters gem
3.40
test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require "minitest/reporters"
Minitest::Reporters.use!
class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical
# order.
fixtures :all
# Add more helper methods to be used by all tests here...
end
IDE
3.8
3.8
rails new
15.
MiniTest
90 -
IDE
Ruby
4.2.2
15
3.7.2.
IDE
gem
Rails
3.2
rvm
IDE
er
mini_backtrace gem
Ruby Version Manag-
3.41
3.41
RVM
config/initializers/backtrace_silencers.rb
# Be sure to restart your server when you modify this file.
# You can add backtrace silencers for libraries that you're using but don't
# wish to see in your backtraces.
Rails.backtrace_cleaner.add_silencer { |line| line =~ /rvm/ }
# You can also remove all the silencers if you're trying to debug a problem
# that might stem from framework code.
# Rails.backtrace_cleaner.remove_silencers!
3.7.3.
Guard
rake test
Guard
stat-
Guard
ic_pages_controller_test.rb
Guard
home.html.erb
3.2
Guard
static_pages_controller_test.rb
guard gem
Guard
3.42
3.42
Guardfile
3.7.
- 91
resource_tests(matches[1])
end
watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches|
["test/controllers/#{matches[1]}_controller_test.rb"] +
integration_tests(matches[1])
end
watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches|
integration_tests(matches[1])
end
watch('app/views/layouts/application.html.erb') do
'test/integration/site_layout_test.rb'
end
watch('app/helpers/sessions_helper.rb') do
integration_tests << 'test/helpers/sessions_helper_test.rb'
end
watch('app/controllers/sessions_controller.rb') do
['test/controllers/sessions_controller_test.rb',
'test/integration/users_login_test.rb']
end
watch('app/models/micropost.rb') do
['test/models/micropost_test.rb', 'test/models/user_test.rb']
end
watch(%r{app/views/users/*}) do
resource_tests('users') +
['test/integration/microposts_interface_test.rb']
end
end
# Returns the integration tests corresponding to the given resource.
def integration_tests(resource = :all)
if resource == :all
Dir["test/integration/*"]
else
Dir["test/integration/#{resource}_*.rb"]
end
end
# Returns the controller tests corresponding to the given resource.
def controller_test(resource)
"test/controllers/#{resource}_controller_test.rb"
end
# Returns all tests for the given resource.
def resource_tests(resource)
integration_tests(resource) << controller_test(resource)
end
Guard
92 -
Rails
Spring
Guard
Spring
IDE
spring/
Git
.gitignore
Git
3.9
.gitignore
3.11
3.10
3.43
3.9
3.7.
- 93
3.10
3.11
94 -
.gitignore
3.43
#
#
#
#
#
#
.gitignore
Spring
Spring
Spring
3.4
Spring
3.4
Unix
process
Linux
Unix
OS X
ps
aux
$ ps aux
|
Unix
ps
grep
ID
kill
pid
kill
pid
$ kill -9 12241
ps aux | grep server
Rails
spring
pid
spring
Spring
$ spring stop
pkill
spring
3.7.
- 95
$ pkill -9 -f spring
ps aux
kill -9 <pid>
pkill -9 -f <name>
Guard
1.3.2
Rails
3.42
guard>
Guard
96 -
Spring
Ctrl-D
Guard
3.42
Guard
3
Rails
Ruby
Rails
Rails
Rails
Ruby
Ruby
Ruby
Ruby
Ruby
4.1.
Ruby
Rails
Ruby
Rails
4.1
3.32
4.1
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
<%= stylesheet_link_tag
'application', media: 'all',
'data-turbolinks-track' => true %>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
</head>
<body>
<%= yield %>
</body>
</html>
Rails
application.css
stylesheet_link_tag
Rails API
Rails
Rails
Rails
helper
Ruby
4.1
97
provide
<% provide(:title, "Home") %>
<h1>Sample App</h1>
<p>
This is the home page for the
<a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</p>
provide
full_title
full_title
full_title
app/helpers/application_helper.rb
4.2
module ApplicationHelper
#
def full_title(page_title = '')
base_title = "Ruby on Rails Tutorial Sample App"
if page_title.empty?
base_title
else
"#{page_title} | #{base_title}"
end
end
end
4.3
4.3
full_title
app/views/layouts/application.html.erb
GREEN
1.
app/helper/static_pages_helper.rb
app/helper/application_helper.rb
98 -
Rails
Ruby
full_title
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= stylesheet_link_tag
'application', media: 'all',
'data-turbolinks-track' => true %>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
</head>
<body>
<%= yield %>
</body>
</html>
Home
"Home"
4.4
@base_title
3.38
4.4
setup
RED
test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionController::TestCase
test "should get home" do
get :home
assert_response :success
assert_select "title", "Ruby on Rails Tutorial Sample App"
end
test "should get help" do
get :help
assert_response :success
assert_select "title", "Help | Ruby on Rails Tutorial Sample App"
end
test "should get about" do
get :about
assert_response :success
assert_select "title", "About | Ruby on Rails Tutorial Sample App"
end
end
4.5
RED
4.6
4.6
GREEN
app/views/static_pages/home.html.erb
4.1.
- 99
<h1>Sample App</h1>
<p>
This is the home page for the
<a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</p>
4.7
GREEN
4.2
Rails
Ruby
4.2.
Ruby
Rails
Rails
Ruby
irb
Ruby
Rails
IDE
2.3.3
4.4.4
nano
irb
4.8
.irbrc
$ nano ~/.irbrc
4.8
4.8
irb
irb
~/.irbrc
IRB.conf[:PROMPT_MODE] = :SIMPLE
IRB.conf[:AUTO_INDENT_MODE] = false
$ rails console
Loading development environment
>>
Rails
7.1.1
Ctrl-C
2.
100 -
Ctrl-D
Ctrl-W
Rails
Ruby
Ctrl-E
nano
Ruby API
API
String
Ruby
4.2.1.
#
Ruby
Ruby
#
def full_title(page_title = '')
.
.
.
end
$ rails console
>> 17 + 42
#
=> 59
4.2.2.
Web
$ rails console
>> ""
#
=> ""
>> "foo"
#
=> "foo"
"
+
>> "foo" + "bar"
=> "foobar"
"foo"
"bar"
"foobar"
#{}
3.
foo bar
4.
Perl
PHP
foobar FUBAR
Jargon File
foo
"foo $bar"
4.2.
- 101
>>
=>
>>
=>
first_name = "Michael"
"Michael"
"#{first_name} Hartl"
"Michael Hartl"
"Michael"
>>
=>
>>
=>
>>
=>
>>
=>
#
#
first_name
first_name = "Michael"
"Michael"
last_name = "Hartl"
"Hartl"
first_name + " " + last_name
"Michael Hartl"
"#{first_name} #{last_name}"
"Michael Hartl"
"#{first_name} Hartl"
#
#
" "
puts
Ruby
>> puts "foo"
foo
=> nil
put ess
puts
puts "foo"
nil
nil
puts
\n
puts
puts "foo"
Ruby
>>
=>
>>
=>
'foo'
"foo"
'foo' + 'bar'
"foobar"
Ruby
>> '#{foo} bar'
=> "\#{foo} bar"
102 -
Rails
Ruby
Ruby
\n
>> '\n'
=> "\\n"
Ruby
>> 'Newlines (\n) and tabs (\t) both use the backslash character \.'
=> "Newlines (\\n) and tabs (\\t) both use the backslash character \\."
Ruby
4.2.3.
nil
Ruby
4.4.2
length
>> "foobar".length
=> 6
>>
=>
>>
=>
length
true
Ruby
false
s = "foobar"
if s.empty?
"The string is empty"
else
"The string is nonempty"
end
"The string is nonempty"
elsif
5.
empty?
"foobar".empty?
false
"".empty?
true
empty?
>>
>>
>>
>>
>>
>>
=>
else + if
Ruby
4.2.
- 103
>>
>>
>>
>>
>>
>>
>>
=>
if s.nil?
"The variable is nil"
elsif s.empty?
"The string is empty"
elsif s.include?("foo")
"The string includes 'foo'"
end
"The string includes 'foo'"
&&
||
>> x = "foo"
=> "foo"
>> y = ""
=> ""
>> puts "Both strings are empty" if x.empty? && y.empty?
=> nil
>> puts "One of the strings is empty" if x.empty? || y.empty?
"One of the strings is empty"
=> nil
>> puts "x is not empty" if !x.empty?
"x is not empty"
=> nil
nil
Ruby
to_s
>> nil.to_s
=> ""
chain
>> nil.empty?
NoMethodError: undefined method `empty?' for nil:NilClass
>> nil.to_s.empty?
#
=> true
nil
>>
=>
>>
=>
>>
=>
empty?
nil.to_s
"foo".nil?
false
"".nil?
false
nil.nil?
true
if
unless
104 -
Rails
Ruby
false
Ruby
bang bang
>> !!nil
=> false
Ruby
>> !!0
=> true
4.2.4.
home
3.6
full_title
4.2
string_message
>> def string_message(str = '')
>>
if str.empty?
>>
"It's an empty string!"
>>
else
>>
"The string is nonempty."
>>
end
>> end
=> :string_message
>> puts string_message("foobar")
The string is nonempty.
>> puts string_message("")
It's an empty string!
>> puts string_message
It's an empty string!
def string_message(str =
'')
str
Ruby
str
Ruby
return
4.2.
- 105
str
the_function_argument
>> def string_message(the_function_argument = '')
>>
if the_function_argument.empty?
>>
"It's an empty string!"
>>
else
>>
"The string is nonempty."
>>
end
>> end
=> nil
>> puts string_message("")
It's an empty string!
>> puts string_message("foobar")
The string is nonempty.
4.2.5.
6
full_title
4.2
4.9
full_title
app/helpers/application_helper.rb
4.9
module ApplicationHelper
#
def full_title(page_title = '')
base_title = "Ruby on Rails Tutorial Sample App"
if page_title.empty?
base_title
else
"#{page_title} | #{base_title}"
end
end
end
#
#
#
#
#
#
module ApplicationHelper
include
Ruby
full_title
Rails
4.3.
Web
Rails
Ruby
6.
Rails
full_title
URL
Rails
Rails
106 -
Rails
Ruby
Obie Fernandez
Rails
4.3.1.
4.3.3
Rails
has_many
2.3.3
11.1.3
split
split
>> "fooxbarxbazx".split('x')
=> ["foo", "bar", "baz"]
Ruby
1
>>
=>
>>
=>
>>
=>
>>
=>
>>
=>
a = [42, 8, 17]
[42, 8, 17]
a[0]
42
a[1]
8
a[2]
17
a[-1]
17
# Ruby
Ruby
Ruby
>>
=>
>>
=>
>>
=>
>>
=>
>>
=>
a
[42, 8, 17]
a.first
42
a.second
8
a.last
17
a.last == a[-1]
true
==
==
>>
=>
>>
=>
x = a.length
3
x == 3
true
second
7.
Rails
!=
Ruby
length
Ruby
Rails
Rails
Ruby
4.3.
- 107
>>
=>
>>
=>
>>
=>
>>
=>
x == 1
false
x != 1
true
x >= 1
true
x < 1
false
length
>>
=>
>>
=>
>>
=>
>>
=>
>>
=>
>>
=>
>>
=>
a
[42, 8, 17]
a.empty?
false
a.include?(42)
true
a.sort
[8, 17, 42]
a.reverse
[17, 8, 42]
a.shuffle
[17, 42, 8]
a
[42, 8, 17]
a
bang
>>
=>
>>
=>
>>
=>
a
[42, 8, 17]
a.sort!
[8, 17, 42]
a
[8, 17, 42]
push
>>
=>
>>
=>
>>
=>
<<
a.push(6)
#
6
[42, 8, 17, 6]
a << 7
#
7
[42, 8, 17, 6, 7]
a << "foo" << "bar"
#
[42, 8, 17, 6, 7, "foo", "bar"]
Ruby
split
>>
=>
>>
=>
108 -
join
a
[42, 8, 17, 7, "foo", "bar"]
a.join
#
"428177foobar"
Rails
Ruby
bang
to_a
range
>> 0..9
=> 0..9
>> 0..9.to_a
#
to_a
9
NoMethodError: undefined method `to_a' for 9:Fixnum
>> (0..9).to_a
#
to_a
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
0..9
>>
=>
>>
=>
# %w
-1
>>
=>
>>
=>
>>
=>
a = (0..9).to_a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
a[2..(a.length-1)]
[2, 3, 4, 5, 6, 7, 8, 9]
a[2..-1]
[2, 3, 4, 5, 6, 7, 8, 9]
#
#
-1
>> ('a'..'e').to_a
=> ["a", "b", "c", "d", "e"]
4.3.2.
block
Ruby
each
{ |i| puts 2 * i }
each
|i|
each
Ruby
i
each
4.3.
- 109
do..end
>> (1..5).each do |number|
?>
puts 2 * number
>>
puts '-'
>> end
2
4
6
8
10
=> 1..5
number
map
8.
110 -
closure
Rails
Ruby
%w
>>
=>
>>
=>
4.3.3
Ruby
Rails
4.4
test "should get home" do
get :home
assert_response :success
assert_select "title", "Ruby on Rails Tutorial Sample App"
end
do
1.5.4
test
Ruby
('a'..'z').to_a.shuffle[0..7].join
>> ('a'..'z').to_a
#
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
>> ('a'..'z').to_a.shuffle
#
=> ["c", "g", "l", "k", "h", "z", "s", "i", "n", "d", "y", "u", "t", "j", "q",
"b", "r", "o", "f", "e", "w", "v", "m", "a", "x", "p"]
>> ('a'..'z').to_a.shuffle[0..7]
#
8
=> ["f", "w", "i", "a", "h", "p", "c", "x"]
>> ('a'..'z').to_a.shuffle[0..7].join #
=> "mznpybuj"
4.3.3.
Hash
Perl
>>
=>
>>
=>
>>
=>
>>
=>
>>
=>
user = {}
# {}
{}
user["first_name"] = "Michael"
#
"first_name"
"Michael"
user["last_name"] = "Hartl"
#
"last_name"
"Hartl"
user["first_name"]
#
"Michael"
user
#
{"last_name"=>"Hartl", "first_name"=>"Michael"}
"Michael"
"Hartl"
4.3.
- 111
{}
9
Ruby
Ruby
Rails
Symbol
:name
10
>> "name".split('')
=> ["n", "a", "m", "e"]
>> :name.split('')
NoMethodError: undefined method `split' for :name:Symbol
>> "foobar".reverse
=> "raboof"
>> :foobar.reverse
NoMethodError: undefined method `reverse' for :foobar:Symbol
Ruby
Rails
>> :foo-bar
NameError: undefined local variable or method `bar' for main:Object
>> :2foo
SyntaxError
user
>>
=>
>>
=>
>>
=>
9.
Ruby 1.9
10.
112 -
Rails
Ruby
Ruby 1.9
>>
=>
>>
=>
>>
=>
JavaScript
Rails
:name
name:
"Michael Hartl" }
:name
name:
{ :name
:name
4.10
4.10
>>
=>
>>
=>
>>
=>
>>
=>
params = {}
#
params parameters
{}
params[:user] = { name: "Michael Hartl", email: "mhartl@example.com" }
{:name=>"Michael Hartl", :email=>"mhartl@example.com"}
params
{:user=>{:name=>"Michael Hartl", :email=>"mhartl@example.com"}}
params[:user][:email]
"mhartl@example.com"
Rails
each
:success
7.3
flash
:danger
each
each
inspect
4.3.
- 113
5
>> puts (1..5).to_a.inspect
#
[1, 2, 3, 4, 5]
>> puts :name, :name.inspect
name
:name
>> puts "It worked!", "It worked!".inspect
It worked!
"It worked!"
inspect
>> p :name
:name
4.3.4.
'puts :name.inspect'
CSS
4.1
4.1
Rails
Ruby
#
stylesheet_link_tag('application', media: 'all',
'data-turbolinks-track' => true)
stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track' => true
media
#
stylesheet_link_tag 'application', { media: 'all',
'data-turbolinks-track' => true }
stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track' => true
data-turbolinks-track
data-turbolinks-track: true
4.3.3
11.
114 -
Rails
Ruby
puts
nil
Katarzyna Siwek
11
Ruby
stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track' => true
12
Ruby
80
13
Rails 4.0
%>
<%=
Turbolink
ERb
HTML
4.11
?body=1
CSS
Rails
CSS
4.11
CSS
HTML
http://localhost:3000/assets/application.css
CSS
4.4. Ruby
Ruby
Ruby
ming
Object-Oriented Program-
OOP
4.4.1.
>>
=>
>>
=>
s = "foobar"
"foobar"
s.class
String
class
\n
12.
13.
80
View > Wrap Column > 78
IDE
Sublime Text
1.5
TextMate
View > Ruler > 80
4.4. Ruby
- 115
named constructor
14
new
>>
=>
>>
=>
>>
=>
s = String.new("foobar")
"foobar"
s.class
String
s == "foobar"
true
>>
=>
>>
=>
>>
=>
>>
=>
h = Hash.new
{}
h[:foo]
nil
h = Hash.new(0)
{}
h[:foo]
0
Hash.new
:foo
new
nil
new
class method
instance
length
instance method
4.4.2.
superclass
>>
=>
>>
=>
>>
=>
>>
=>
>>
=>
s = String.new("foobar")
"foobar"
s.class
#
s
String
s.class.superclass
#
String
Object
s.class.superclass.superclass # Ruby 1.9
BasicObject
s.class.superclass.superclass.superclass
nil
String
4.1
cObject
Ruby
Object
Object
Ruby
14.
116 -
BasicObject
Rails
Ruby
Ruby 1.9.3
BasicObject
Basi-
BasicObject
Ruby
4.1
Ruby
String
Word
palindrome?
true
>>
=>
>>
=>
>>
=>
w = Word.new
#<Word:0x22d0b20>
w.palindrome?("foobar")
false
w.palindrome?("level")
true
Word
Word
String
4.12
Word
4.12
Word
# Word
true
String
# self
4.4. Ruby
- 117
>>
=>
>>
=>
>>
=>
Ruby
s = Word.new("level")
"level"
s.palindrome?
true
s.length
5
Word
palindrome?
3.2
Word
# Word
Word
"level"
palindrome?
# Word
String
>>
=>
>>
=>
>>
=>
s.class
Word
s.class.superclass
String
s.class.superclass.superclass
Object
4.2
4.2
4.12
Word
4.12
15
self
Word
Word
Ruby
self
self == self.reverse
self.
self == reverse
15.
118 -
Ruby
Rails
self
Ruby
RailsTips
4.4.3.
palindrome?
String
palindrome?
>> "level".palindrome?
NoMethodError: undefined method `palindrome?' for "level":String
Ruby
>>
>>
>>
>>
>>
>>
=>
>>
=>
Ruby
class String
#
def palindrome?
self == self.reverse
end
end
nil
"deified".palindrome?
true
true
"deified"
Ruby
Rails
Web
blank
Rails
Rails
irb
Rails
>>
=>
>>
=>
>>
=>
>>
=>
blank?
Ruby
"".blank?
true
"
".empty?
false
"
".blank?
true
nil.blank?
true
empty
Rails
nil
Rails
nil
blank
blank?
String
Object
8.4
Ruby
4.4.4.
3.18
class StaticPagesController < ApplicationController
def home
end
def help
end
4.4. Ruby
- 119
def about
end
end
StaticPagesController
tionController
home
help
about
Applica-
Rails
Rails
16
>>
=>
>>
=>
>>
=>
>>
=>
>>
=>
>>
=>
>>
=>
controller = StaticPagesController.new
#<StaticPagesController:0x22855d0>
controller.class
StaticPagesController
controller.class.superclass
ApplicationController
controller.class.superclass.superclass
ActionController::Base
controller.class.superclass.superclass.superclass
ActionController::Metal
controller.class.superclass.superclass.superclass.superclass
AbstractController::Base
controller.class.superclass.superclass.superclass.superclass.superclass
Object
4.3
>> controller.home
=> nil
home
nil
home
StaticPagesController.new
Rails
Rails
Ruby
16.
120 -
Rails
Rails
Ruby
Rails
Ruby
Ruby
2005
Rails
Ruby
Ruby on Rails
Rails
4.3
4.4.5.
User
Ruby
example_user.rb
4.13
4.13
User
example_user.rb
class User
attr_accessor :name, :email
def initialize(attributes = {})
@name = attributes[:name]
@email = attributes[:email]
end
def formatted_email
4.4. Ruby
- 121
"#{@name} <#{@email}>"
end
end
getter
Rails
attribute accessors
@name
setter
2.2.2
3.6
Ruby
@
nil
initialize
User.new
Ruby
initialize
attributes
def initialize(attributes = {})
@name = attributes[:name]
@email = attributes[:email]
end
attributes
nil
4.3.3
nil
:name
attributes[:name]
attributes[:email]
formatted_email
@name
def formatted_email
"#{@name} <#{@email}>"
end
@name
formatted_email
require
>>
=>
>>
=>
>>
=>
>>
=>
>>
=>
>>
=>
require './example_user'
#
example_user
true
example = User.new
#<User:0x224ceec @email=nil, @name=nil>
example.name
#
nil
attributes[:name]
nil
example.name = "Example User"
#
nil
"Example User"
example.email = "user@example.com"
#
nil
"user@example.com"
example.formatted_email
"Example User <user@example.com>"
.
Unix
attr_accessor
"Example User"
122 -
Rails
'./example_user'
Ruby
Ruby
formatted_email
nil
Ruby
@name
ini-
4.3.4
tialize
>>
=>
>>
=>
mass assignment
Rails
4.5.
Ruby
5
example_user.rb
4.4.5
$ rm example_user.rb
Bitbucket
$
$
$
$
$
Heroku
git status
git commit -am "Add a full_title helper"
git push
bundle exec rake test
git push heroku
4.5.1.
Ruby
Ruby
Ruby
def
Ruby
class
Ruby
Ruby
Ruby
Ruby
deified
4.6.
1.
2.
split
4.14
4.15
shuffle
shuffle
join
String
4.5.
- 123
person1
3.
params
params[:child]
4.
person2
person3
params[:father]
person3
merge
Ruby API
4.14
def string_shuffle(s)
s.?('').?.?
end
string_shuffle("foobar")
"oobfra"
4.15
>>
>>
>>
>>
>>
>>
=>
124 -
String
class String
def shuffle
self.?('').?.?
end
end
"foobar".shuffle
"borafo"
Rails
Ruby
params[:mother]
params[:father][:first]
{ "a" => 100, "b" => 200 }.merge({ "b" => 300 })
>>
>>
>>
>>
=>
person1
:first
shuffle
:last
person2
Ruby
4.1
1
CSS
Sass 5.2
Pipeline
5.4
5.1
TDD
Rails
Asset
5.3.1
5.3.4
5.1.
Web
Twitter
Web
CSS
Bootstrap
Web
CSS
Web
mockup
3.2
Web
LOGO
5.1
5.7
Rails LOGO
1.
2.
Colm Tuite
Bootstrap
Mockingbird
125
5.1
Git
$ git checkout master
$ git checkout -b filling-in-layout
5.1.1.
application.html.erb
4.3
HTML
5.1
CSS
5.2
5.1
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track' => true %>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
126 -
3.4.1
html>
HTML5
JavaScript
Rails
IE
HTML5
<!DOCTYPE
HTML5 shim
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
</script>
<![endif]-->
<!--[if lt IE 9]>
IE
Rails
if lt IE 9
[if lt IE 9]
IE
IE9
HTML5 shim
header
Firefox
Chrome
LOGO
Safari
div
5.1.
- 127
</ul>
</nav>
</div>
</header>
header
header
navbar-fixed-top
CSS
navbar
navbar-inverse
HTML
ID
CSS
ID
Bootstrap
5.1.2
header
5.1.2
ID
Bootstrap
div
<div class="container">
div
HTML
header
HTML5
CSS
div
nav
container
div
section
header
Bootstrap
div
ERb
Rails
5.3.3
Web
CSS IDlogo
3.2.2
LOGO
Rails
div
HTML
ul
<nav>
<ul class="nav navbar-nav pull-right">
<li><%= link_to "Home",
'#' %></li>
<li><%= link_to "Help",
'#' %></li>
<li><%= link_to "Log in", '#' %></li>
</ul>
</nav>
128 -
Ruby
named route
Rails
3. CSS
link_to
li
nav
ul
right
Bootstrap
5.1.2
Bootstrap
Rails
nav
navbar-nav
pull-
CSS
4
ERb
<nav>
<ul class="nav navbar-nav pull-right">
<li><a href="#">Home</a></li>
<li><a href="#">Help</a></li>
<li><a href="#">Log in</a></li>
</ul>
</nav>
div
<div class="container">
<%= yield %>
</div>
container
Bootstrap
yield
3.4.3
5.1.3
home.html.erb
5.2
5.2
app/views/static_pages/home.html.erb
<div class="center jumbotron">
<h1>Welcome to the Sample App</h1>
<h2>
This is the home page for the
<a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</h2>
<%= link_to "Sign up now!", '#', class: "btn btn-lg btn-primary" %>
</div>
<%= link_to image_tag("rails.png", alt: "Rails logo"),
'http://rubyonrails.org/' %>
link_to
4.
CSS
jumbotron
btn
Bootstrap
HTML
btn-lg
btn-primary
3.4.1
5.1.
- 129
link_to
image_tag
alt
rails.png
Ruby on Rails
http://rubyonrails.org/images/
IDE Unix
app/assets/images/
rails.png
curl
$ curl -O http://rubyonrails.org/images/rails.png
$ mv rails.png app/assets/images/
image_tag
Rails
image_tag
Asset Pipeline
HTML
app/assets/images/
5.2
Rails
src
images
Rails
assets
JavaScript CSS
assets
app/assets/images
alt
5.2
5.
OS X
img
6.
130 -
Homebrew
CSS
<img />
curl
5.2
HTML
5.1.2. Bootstrap
CSS
CSS
HTML
Bootstrap
CSS
CSS
Bootstrap
HTML5
Web
CSS
Bootstrap
bootstrap-sass
Bootstrap
Rails
LESS
bootstrap-sass
LESS
Bootstrap
gem
5.3
Sass
Sass
5.3
bootstrap-sass
Gemfile
source 'https://rubygems.org'
gem 'rails',
gem 'bootstrap-sass',
.
.
.
'4.2.0'
'3.2.0.0'
bundle install
Bootstrap
$ bundle install
rails generate
CSS
CSS
CSS
$ touch app/assets/stylesheets/custom.css.scss
touch
3.3.3
app/assets/stylesheets/
Asset Pipeline
application.css
Asset Pipeline
custom.css.scss
.css
CSS
Sass
5.2.2
.scss
Sassy CSS
Sass
bootstrap-sass gem
@import
CSS
5.4
Bootstrap
Sprockets
5.4
Bootstrap
CSS
app/assets/stylesheets/custom.css.scss
@import "bootstrap-sprockets";
@import "bootstrap";
7.
8.
Asset Pipeline
LESS
less-rails-bootstrap gem
gem
5.1.
- 131
Bootstrap CSS
Web
5.3
Bootstrap CSS
CSS
5.4
5.5
5.5
CSS
app/assets/stylesheets/custom.css.scss
@import "bootstrap-sprockets";
@import "bootstrap";
/* universal */
html {
overflow-y: scroll;
}
body {
padding-top: 60px;
}
section {
overflow: auto;
132 -
5.5
CSS
/* ... */
rails server
Ctrl-C
LOGO
5.3
CSS
}
textarea {
resize: vertical;
}
.center {
text-align: center;
}
.center h1 {
margin-bottom: 10px;
}
5.4
5.5
CSS
CSS
ID
HTML
body {
padding-top: 60px;
}
60
Bootstrap 2.0
header
navbar-inverse
navbar-fixed-top
Bootstrap
CSS
5.1.
- 133
.center {
text-align: center;
}
.center
5.7
text-align: center;
#
.center
.center
ID
5.2
Bootstrap
5.6
5.5
5.6
app/assets/stylesheets/custom.css.scss
@import "bootstrap-sprockets";
@import "bootstrap";
.
.
.
/* typography */
h1, h2, h3, h4, h5, h6 {
line-height: 1;
}
h1 {
font-size: 3em;
letter-spacing: -2px;
margin-bottom: 30px;
text-align: center;
}
h2 {
font-size: 1.2em;
letter-spacing: -1px;
margin-bottom: 30px;
text-align: center;
font-weight: normal;
color: #777;
}
p {
font-size: 1.1em;
line-height: 1.7em;
}
134 -
div
5.5
sample app
LOGO
5.7
ID
5.7
CSS
LOGO
LOGO
app/assets/stylesheets/custom.css.scss
@import "bootstrap-sprockets";
@import "bootstrap";
.
.
.
/* header */
#logo {
float: left;
margin-right: 10px;
font-size: 1.7em;
color: #fff;
text-transform: uppercase;
letter-spacing: -1px;
padding-top: 9px;
font-weight: bold;
}
#logo:hover {
5.1.
- 135
color: #fff;
text-decoration: none;
}
color: #fff;
LOGO
#ffffff
CSS
HTML
3
#fff
3
white
HTML
#fff
16
#ffffff
5.7
5.6
5.6
LOGO
5.1.3.
5.1
IE
Rails
5.8
5.8
HTML shim
HTML
HTML shim
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track' => true %>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
136 -
HTML shim
Rails
render
<%= %>
Ruby
_shim.html.erb
HTML shim
5.9
5.9
HTML shim
app/views/layouts/_shim.html.erb
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
</script>
<![endif]-->
render
5.10
5.10
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
<%= link_to "sample app", '#', id: "logo" %>
<nav>
<ul class="nav navbar-nav pull-right">
<li><%= link_to "Home",
'#' %></li>
<li><%= link_to "Help",
'#' %></li>
<li><%= link_to "Log in", '#' %></li>
</ul>
</nav>
</div>
</header>
9.
Rails
shared
`layouts`
shared
shared
shared
5.1.
- 137
_footer.html.erb
10
5.11
5.11
app/views/layouts/_footer.html.erb
<footer class="footer">
<small>
The <a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
by <a href="http://www.michaelhartl.com/">Michael Hartl</a>
</small>
<nav>
<ul>
<li><%= link_to "About",
'#' %></li>
<li><%= link_to "Contact", '#' %></li>
<li><a href="http://news.railstutorial.org/">News</a></li>
</ul>
</nav>
</footer>
link_to
header
footer
HTML5
HTML shim
5.12
5.12
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<%= stylesheet_link_tag "application", media: "all",
"data-turbolinks-track" => true %>
<%= javascript_include_tag "application", "data-turbolinks-track" => true %>
<%= csrf_meta_tags %>
<%= render 'layouts/shim' %>
</head>
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
</div>
</body>
</html>
5.13
10.
div
138 -
footer
footer
.footer
5.7
Bootstrap
5.7
5.13
CSS
app/assets/stylesheets/custom.css.scss
.
.
.
/* footer */
footer {
margin-top: 45px;
padding-top: 5px;
border-top: 1px solid #eaeaea;
color: #777;
}
footer a {
color: #555;
}
footer a:hover {
color: #222;
}
footer small {
float: left;
}
5.1.
- 139
footer ul {
float: right;
list-style: none;
}
footer ul li {
float: left;
margin-left: 15px;
}
5.2. Sass
Asset Pipeline
Rails
Asset Pipeline
CSS
CSS JavaScript
Asset Pipeline
Sass
Rails
Rails
11
public/
Rails 3.0
public/stylesheets
public/javascripts
public/images
http://example.com/stylesheets
Rails 3.0
Rails
app/assets
lib/assets
vendor/assets
$ ls app/assets/
images/ javascripts/ stylesheets/
5.1.2
custom.css.scss
custom.css.scss
app/assets/stylesheets
11.
140 -
Michael Erasmus
Asset Pipeline
Rails
Rails
ets gem
Sprock-
CSS
JavaScript
5.14
5.14
app/assets/stylesheets/application.css
/*
* 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 bottom of the compiled file so the styles you add here take
* precedence over styles defined in any styles defined in the other CSS/SCSS
* files in this directory. It is generally better to create a new file per
* style scope.
*
*= require_tree .
*= require_self
*/
CSS
Sprockets
/*
.
.
.
*= require_tree .
*= require_self
*/
*= require_tree .
app/assets/stylesheets
CSS
CSS
*= require_self
application.css
Rails
Asset Pipeline
CSS
Rails
5.2. Sass
Rails
Rails
.coffee
.erb
ERb
Script
3.4.3
JavaScript
ERb 5.2.2
RailsCast
Sass
.scss
Sass
CoffeeScript
CoffeeScript
Coffee-
foobar.js.coffee
CoffeeScript
foobar.js.erb.coffee
CoffeeScript
ERb
CoffeeScript
Asset Pipeline
CSS
JavaScript
Asset Pipeline
JavaScript
cation.css
appli-
CSS
JavaScript
application.js
5.2.2.
Sass
CSS
CSS
5.1.2
Sass
7.1.1
.scss
SCSS
12
CSS
SCSS
Rails
tom.css.scss
Asset Pipeline
Sass
CSS
SCSS
CSS
Bootstrap
.scss
Sass
5.5
.center
SCSS
cus-
.center h1
.center {
text-align: center;
}
.center h1 {
12. Sass
142 -
.sass
CSS
margin-bottom: 10px;
}
Sass
.center {
text-align: center;
h1 {
margin-bottom: 10px;
}
}
h1
.center
5.7
#logo {
float: left;
margin-right: 10px;
font-size: 1.7em;
color: #fff;
text-transform: uppercase;
letter-spacing: -1px;
padding-top: 9px;
font-weight: bold;
}
#logo:hover {
color: #fff;
text-decoration: none;
}
LOGO
ID #logo
hover
#logo
SCSS
&
#logo {
float: left;
margin-right: 10px;
font-size: 1.7em;
color: #fff;
text-transform: uppercase;
letter-spacing: -1px;
padding-top: 9px;
font-weight: bold;
&:hover {
color: #fff;
text-decoration: none;
}
}
SCSS
CSS
Sass
&:hover
#logo:hover
5.13
5.2. Sass
footer {
margin-top: 45px;
padding-top: 5px;
border-top: 1px solid #eaeaea;
color: #777;
a {
color: #555;
&:hover {
color: #222;
}
}
small {
float: left;
}
ul {
float: right;
list-style: none;
li {
float: left;
margin-left: 15px;
}
}
}
5.13
Sass
5.13
CSS
5.6
h2 {
.
.
.
color: #777;
}
.
.
.
footer {
.
.
.
color: #777;
}
#777
$light-gray: #777;
SCSS
144 -
$light-gray: #777;
.
.
.
h2 {
.
.
.
color: $light-gray;
}
.
.
.
footer {
.
.
.
color: $light-gray;
}
$light-gray
#999
Bootstrap
Less
Less
Bootstrap
Sass
Less
Sass
bootstrap-sass gem
Sass
Bootstrap
@gray-light: #777;
bootstrap-sass gem
SCSS
$gray-light
$light-gray
h2 {
.
.
.
color: $gray-light;
}
.
.
.
footer {
.
.
.
color: $gray-light;
}
Sass
Sass
5.15
white
Bootstrap Less
#fff
footer
5.15
SCSS
app/assets/stylesheets/custom.css.scss
5.2. Sass
@import "bootstrap-sprockets";
@import "bootstrap";
/* mixins, variables, etc. */
$gray-medium-light: #eaeaea;
/* universal */
html {
overflow-y: scroll;
}
body {
padding-top: 60px;
}
section {
overflow: auto;
}
textarea {
resize: vertical;
}
.center {
text-align: center;
h1 {
margin-bottom: 10px;
}
}
/* typography */
h1, h2, h3, h4, h5, h6 {
line-height: 1;
}
h1 {
font-size: 3em;
letter-spacing: -2px;
margin-bottom: 30px;
text-align: center;
}
h2 {
font-size: 1.2em;
letter-spacing: -1px;
margin-bottom: 30px;
text-align: center;
font-weight: normal;
color: $gray-light;
146 -
}
p {
font-size: 1.1em;
line-height: 1.7em;
}
/* header */
#logo {
float: left;
margin-right: 10px;
font-size: 1.7em;
color: white;
text-transform: uppercase;
letter-spacing: -1px;
padding-top: 9px;
font-weight: bold;
&:hover {
color: white;
text-decoration: none;
}
}
/* footer */
footer {
margin-top: 45px;
padding-top: 5px;
border-top: 1px solid $gray-medium-light;
color: $gray-light;
a {
color: $gray;
&:hover {
color: $gray-darker;
}
}
small {
float: left;
}
ul {
float: right;
list-style: none;
li {
float: left;
margin-left: 15px;
}
}
}
5.2. Sass
Sass
Sass
5.15
5.3.
#
<a href="/static_pages/about">About</a>
Rails
/about
/static_pages/about
Rails
<%= link_to "About", about_path %>
about_path
about_path
URL
URL
5.1
URL
3.4.4
8
5.1
URL
URL
root_path
/about
about_path
/help
help_path
/contact
contact_path
/signup
signup_path
/login
login_path
5.3.1.
3.22
5.16
5.16
@base_title
RED
test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionController::TestCase
test "should get home" do
get :home
assert_response :success
assert_select "title", "Ruby on Rails Tutorial Sample App"
148 -
5.16
end
test "should get help" do
get :help
assert_response :success
assert_select "title", "Help | Ruby on Rails Tutorial Sample App"
end
test "should get about" do
get :about
assert_response :success
assert_select "title", "About | Ruby on Rails Tutorial Sample App"
end
test "should get contact" do
get :contact
assert_response :success
assert_select "title", "Contact | Ruby on Rails Tutorial Sample App"
end
end
5.16
5.17
RED
3.3
5.19
contact
5.18
5.18
5.20
RED
config/routes.rb
Rails.application.routes.draw do
root 'static_pages#home'
get 'static_pages/help'
get 'static_pages/about'
get 'static_pages/contact'
end
5.19
RED
app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
.
.
.
def contact
end
end
5.20
GREEN
app/views/static_pages/contact.html.erb
5.3.
- 149
5.21
GREEN
5.3.2. Rails
Rails
routes.rb
config/
URL
3.4.4
hello_app
1.10
root 'application#hello'
toy_app
2.3
root 'users#index'
sample_app
3.37
root 'static_pages#home'
root_path
URL
root_url
URL
_path
HTTP
URL
5.18
get
get 'static_pages/help'
/help
help
GET
help
/static_pages/
help_path
/help
help_path -> '/help'
help_url -> 'http://www.example.com/help'
5.18
150 -
5.22
help_url
5.22
config/routes.rb
Rails.application.routes.draw do
root
'static_pages#home'
get 'help'
=> 'static_pages#help'
get 'about'
=> 'static_pages#about'
get 'contact' => 'static_pages#contact'
end
5.3.3.
link_to
5.22
_header.html.erb
LOGO
5.23
5.23
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
<%= link_to "sample app", root_path, id: "logo" %>
<nav>
<ul class="nav navbar-nav pull-right">
<li><%= link_to "Home",
root_path %></li>
<li><%= link_to "Help",
help_path %></li>
<li><%= link_to "Log in", '#' %></li>
</ul>
</nav>
</div>
</header>
_footer.html.erb
5.24
5.24
app/views/layouts/_footer.html.erb
<footer class="footer">
<small>
The <a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
by <a href="http://www.michaelhartl.com/">Michael Hartl</a>
5.3.
- 151
</small>
<nav>
<ul>
<li><%= link_to "About",
about_path %></li>
<li><%= link_to "Contact", contact_path %></li>
<li><a href="http://news.railstutorial.org/">News</a></li>
</ul>
</nav>
</footer>
3
5.8
5.8
/about
5.3.4.
site_layout
$ rails generate integration_test site_layout
invoke test_unit
create
test/integration/site_layout_test.rb
Rails
_test
HTML
152 -
/about
1.
2.
3.
Rails
assert_template
5.25
13
5.25
GREEN
test/integration/site_layout_test.rb
require 'test_helper'
class SiteLayoutTest < ActionDispatch::IntegrationTest
test "layout links" do
get root_path
assert_template 'static_pages/home'
assert_select "a[href=?]", root_path, count: 2
assert_select "a[href=?]", help_path
assert_select "a[href=?]", about_path
assert_select "a[href=?]", contact_path
end
end
5.25
assert_select
href
Rails
HTML
<a href="/about">...</a>
LOGO
assert_select "a[href=?]", root_path, count: 2
5.23
assert_select
5.2
assert_select
HTML
5.2
assert_select
HTML
assert_select "div"
<div>foobar</div>
13.
Ruby
MiniTest
5.3.
- 153
HTML
assert_select "div", "foobar"
<div>foobar</div>
assert_select "div.nav"
<div class="nav">foobar</div>
assert_select "div#profile"
<div id="profile">foobar</div>
assert_select "div[name=yo]"
<div name="yo">hey</div>
<a href="/">foo</a>
<a href="/">foo</a>
Rake
5.26
5.25
GREEN
5.27
GREEN
5.4.
5.4.1.
3.2
generate
Rails
new
new
generate
5.28
5.28
new
154 -
REST
invoke
invoke
create
invoke
create
assets
coffee
app/assets/javascripts/users.js.coffee
scss
app/assets/stylesheets/users.css.scss
new
5.29
5.30
5.32
5.31
GREEN
5.30
app/controllers/users_controller.rb
5.31
new
app/views/users/new.html.erb
<h1>Users#new</h1>
<p>Find me in app/views/users/new.html.erb</p>
5.32
GREEN
test/controllers/users_controller_test.rb
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
test "should get new" do
get :new
assert_response :success
end
end
5.4.2.
URL
URL
/users/new
5.22
/signup
5.1
get '/signup'
5.33
5.33
config/routes.rb
Rails.application.routes.draw do
root
'static_pages#home'
get 'help'
=> 'static_pages#help'
5.4.
- 155
get 'about'
=> 'static_pages#about'
get 'contact' => 'static_pages#contact'
get 'signup' => 'users#new'
end
get 'signup'
signup_path
5.34
5.34
app/views/static_pages/home.html.erb
<div class="center jumbotron">
<h1>Welcome to the Sample App</h1>
<h2>
This is the home page for the
<a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</h2>
<%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
</div>
<%= link_to image_tag("rails.png", alt: "Rails logo"),
'http://rubyonrails.org/' %>
5.35
5.35
app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<p>This will be a signup page for new users.</p>
156 -
5.9
5.9
/signup
5.5.
Git
$
$
$
$
$
Bitbucket
$ git push
Heroku
$ git push heroku
5.10
5.5.
- 157
5.10
5.5.1.
HTML5
LOGO
Rails
CSS
ID
Bootstrap
Sass
Asset Pipeline
CSS
Rails
5.6.
3
1.
5.2.2
5.13
CSS
5.15
SS
2.
5.25
3.
5.36
get
full_title
5.37
158 -
SC-
5.38
FILL_IN
5.38
assert_equal
==
5.36
test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
.
.
.
class ActiveSupport::TestCase
fixtures :all
include ApplicationHelper
.
.
.
end
5.37
full_title
test/integration/site_layout_test.rb
GREEN
require 'test_helper'
class SiteLayoutTest < ActionDispatch::IntegrationTest
test "layout links" do
get root_path
assert_template 'static_pages/home'
assert_select "a[href=?]", root_path, count: 2
assert_select "a[href=?]", help_path
assert_select "a[href=?]", about_path
assert_select "a[href=?]", contact_path
get signup_path
assert_select "title", full_title("Sign up")
end
end
5.38
full_title
test/helpers/application_helper_test.rb
require 'test_helper'
class ApplicationHelperTest < ActionView::TestCase
test "full title helper" do
assert_equal full_title,
FILL_IN
assert_equal full_title("Help"), FILL_IN
end
end
5.6.
- 159
5.4
7
8
6
9.2.1
10
Rails
10
Rails
6.1
6.1
Web
Rails
Web
Clearance Authlogic
OpenID
OAuth
Rails
Rails
Devise
CanCan
Rails
6.3
6.1.
6.1
161
6.1
Rails
model MVC
1.3.3
Rails
Active
Record
Active Record
Language
SQL
Rails
Ruby
DDL
SQL
Active Record
PostgreSQL
Heroku
SQLite
1.5
Rails
Git
$ git checkout master
$ git checkout -b modeling-users
1. Active Record
2. SQL
162 -
ess-cue-ell
Active Record
Structured Query
migration
Data Definition Language
sequel
Martin Fowler
6.1.1.
User
4.4.5
name
Rails
User
4.4.5
name
3
6.3
4.13
Ruby
at-
tr_accessor
class User
attr_accessor :name, :email
.
.
.
end
Rails
Rails
users
6.3
name
6.2
6.3
name
6.4
6.2
Active Record
users
6.3
new
5.28
$ rails generate controller Users new
3.
10
6.1.
- 163
generate model
name
6.1
6.1
$ rails generate model User name:string email:string
invoke active_record
create
db/migrate/20140724010738_create_users.rb
create
app/models/user.rb
invoke
test_unit
create
test/models/user_test.rb
create
test/fixtures/users.yml
Users
name:string
email:string
Rails
3.4
User
5.28
generate
generate
name
users
6.2
6.2.5
users
6.2
db/migrate/[timestamp]_create_users.rb
change
t
create_table
create_table
table
string
change
6.2
create_table
Rails
users
name
User
Rails
updated_at
6.1.3
6.4
6.3
4.
164 -
6.4
rake
6.2
2.1
2.2
SQLite 5
ment.sqlite3
velopment.sqlite3
6.5
velopment.sqlite3
id
db/developdb/de-
SQLite
Download
2.2
6.5
db/de-
IDE
6.4
Rails
users
SQLite
Rake
db:rollback
$ bundle exec rake db:rollback
5. SQLite
ess-cue-ell-ite
sequel-ite
6.1.
- 165
drop_table
3.1
change
create_table
users
drop_table
drop_table
up
change
down
Rails
6.1.2.
6.1
6.2
db/development.sqlite3
6.5
updated_at
users
id
name
6.1
app/models/
user.rb
6.3
6.3
app/models/user.rb
class User < ActiveRecord::Base
end
class User < ActiveRecord::Base
4.4.2
User
ActiveRecord::Base
ActiveRecord::Base
ActiveRecord::Base
6.1.3.
4
Rails
4.4.5
4.4.4
User.new
4.13
Rails
Rails
>> User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
User.new
nil
Active Record
166 -
User
4.4.5
Active Record
created_at
Active Record
user
6.2
valid?
>> user.valid?
true
User.new
user.valid?
user
save
>> user.save
(0.2ms) begin transaction
User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE LOWER("users".
"email") = LOWER('mhartl@example.com') LIMIT 1
SQL (0.5ms) INSERT INTO "users" ("created_at", "email", "name", "updated_at)
VALUES (?, ?, ?, ?) [["created_at", "2014-09-11 14:32:14.199519"],
["email", "mhartl@example.com"], ["name", "Michael Hartl"], ["updated_at",
"2014-09-11 14:32:14.199519"]]
(0.9ms) commit transaction
=> true
save
true
false
6.2
user.save
Rails
"users"
SQL
INSERT INTO
SQL
SQL
Active Record
SQL
id
created_at
updated_at
nil
>> user
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-07-24 00:57:46", updated_at: "2014-07-24 00:57:46">
id
6.1.5
User
4.4.5
>>
=>
>>
=>
user.name
"Michael Hartl"
user.email
"mhartl@example.com"
6. 12.3.3
7.
"2014-07-24 00:57:46"
Coordinated Universal Time
UTC
GMT
UTC
1970
ITU
ITU
CUT
ITU
CUT
TUC
UTC
6.1.
- 167
>> user.updated_at
=> Thu, 24 Jul 2014 00:57:46 UTC +00:00
Active Record
User.create
>> User.create(name: "A Nother", email: "another@example.org")
#<User id: 2, name: "A Nother", email: "another@example.org", created_at:
"2014-07-24 01:05:24", updated_at: "2014-07-24 01:05:24">
>> foo = User.create(name: "Foo", email: "foo@bar.com")
#<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2014-07-24
01:05:42", updated_at: "2014-07-24 01:05:42">
User.create
true
foo
false
create
destroy
>> foo.destroy
=> #<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2014-07-24
01:05:42", updated_at: "2014-07-24 01:05:42">
destroy
create
destroy
>> foo
=> #<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2014-07-24
01:05:42", updated_at: "2014-07-24 01:05:42">
Active Record
6.1.4.
Active Record
foo
>> User.find(1)
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-07-24 00:57:46", updated_at: "2014-07-24 00:57:46">
User.find
ID
ID
Active Record
ID
>> User.find(3)
ActiveRecord::RecordNotFound: Couldn't find User with ID=3
6.1.3
Active Record
ID
Active Record
168 -
find
ActiveRecord::RecordNotFound
6.2.5
first
>> User.first
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-07-24 00:57:46", updated_at: "2014-07-24 00:57:46">
first
all
>> User.all
=> #<ActiveRecord::Relation [#<User id: 1, name: "Michael Hartl",
email: "mhartl@example.com", created_at: "2014-07-24 00:57:46",
updated_at: "2014-07-24 00:57:46">, #<User id: 2, name: "A Nother",
email: "another@example.org", created_at: "2014-07-24 01:05:24",
updated_at: "2014-07-24 01:05:24">]>
User.all
ActiveRecord::Relation
4.3.1
6.1.5.
4.4.5
>> user
#
user
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-07-24 00:57:46", updated_at: "2014-07-24 00:57:46">
>> user.email = "mhartl@example.net"
=> "mhartl@example.net"
>> user.save
=> true
reload
reload
>>
=>
>>
=>
>>
=>
user.email
"mhartl@example.net"
user.email = "foo@bar.com"
"foo@bar.com"
user.reload.email
"mhartl@example.net"
6.1.3
8.
12.4.1
Ruby
Ruby
6.1.
- 169
>>
=>
>>
=>
user.created_at
"2014-07-24 00:57:46"
user.updated_at
"2014-07-24 01:37:32"
9
update_attributes
>>
=>
>>
=>
>>
=>
update_attributes
true
6.3
update_attributes
>>
=>
>>
=>
update_attribute
6.2.
name
6.1
email
name
name
Active Record
2.3.2
6.3.2
7.3
6.2.1.
3.3
TDD
TDD
6.1
6.4
6.4
test/models/user_test.rb
9. update_attributes
170 -
update
update_attribute
require 'test_helper'
class UserTest < ActiveSupport::TestCase
# test "the truth" do
#
assert true
# end
end
setup
@user
setup
@user
valid?
6.5
6.5
GREEN
test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com")
end
test "should be valid" do
assert @user.valid?
end
end
assert
6.5
6.6
@user.valid?
true
false
GREEN
5.3.4
rake test:integration
6.2.2.
name
7.3.3
name
6.5
@user
name
6.7
name
test/models/user_test.rb
6.7
assert_not
RED
require 'test_helper'
class UserTest < ActiveSupport::TestCase
6.2.
- 171
def setup
@user = User.new(name: "Example User", email: "user@example.com")
end
test "should be valid" do
assert @user.valid?
end
test "name should be present" do
@user.name = "
"
assert_not @user.valid?
end
end
6.8
RED
validates
presence: true
6.9
5.1.1
name
6.9
presence: true
4.3.4
Rails
GREEN
app/models/user.rb
class User < ActiveRecord::Base
validates :name, presence: true
end
validates
6.9
class User < ActiveRecord::Base
validates(:name, presence: true)
end
10
user
false
true
er-
rors
>> user.errors.full_messages
=> ["Name can't be blank"]
User.new
10.
172 -
Rails
balnk?
4.4.3
>> user.save
=> false
6.7
6.10
GREEN
6.7
6.11
6.12
email
test/models/user_test.rb
6.11
RED
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com")
end
test "should be valid" do
assert @user.valid?
end
test "name should be present" do
@user.name = ""
assert_not @user.valid?
end
test "email should be present" do
@user.email = "
"
assert_not @user.valid?
end
end
email
6.12
GREEN
app/models/user.rb
class User < ActiveRecord::Base
validates :name, presence: true
validates :email, presence: true
end
6.13
GREEN
6.2.
- 173
6.2.3.
50
51
255
6.14
6.14
name
test/models/user_test.rb
RED
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com")
end
.
.
.
test "name should not be too long" do
@user.name = "a" * 51
assert_not @user.valid?
end
test "email should not be too long" do
@user.email = "a" * 256
assert_not @user.valid?
end
end
51
>>
=>
>>
=>
"a" * 51
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
("a" * 51).length
51
6.14
6.15
RED
6.16
6.16
name
app/models/user.rb
174 -
GREEN
maximum
6.17
GREEN
6.2.4.
name
51
user@example.com
%w[]
4.3.2
user@example,com
addresses
user@example.com
6.18
6.18
GREEN
test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com")
end
.
6.2.
- 175
.
.
test "email validation should accept valid addresses" do
valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org
first.last@foo.jp alice+bob@baz.cn]
valid_addresses.each do |valid_address|
@user.email = valid_address
assert @user.valid?, "#{valid_address.inspect} should be valid"
end
end
end
assert
assert @user.valid?, "#{valid_address.inspect} should be valid"
inspect
4.3.3
user_at_foo.org
6.19
each
6.18
user@example,com
6.19
RED
test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com")
end
.
.
.
test "email validation should reject invalid addresses" do
invalid_addresses = %w[user@example,com user_at_foo.org user.name@example.
foo@bar_baz.com foo@bar+baz.com]
invalid_addresses.each do |invalid_address|
@user.email = invalid_address
assert_not @user.valid?, "#{invalid_address.inspect} should be invalid"
end
end
end
6.20
RED
176 -
11
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
VALID_EMAIL_REGEX
6.1
6.1
/\A[\w+\-.]@[a-z\d\-.]\.[a-z]+\z/i
/
\A
[\w+\-.]+
@
[a-z\d\-.]+
\.
[a-z]+
\z
/
i
6.1
lar
11.
12.
6.6
Rubu12
Rubular
Rubular
Rubular
6.1
6.1
\A
Rubular
Rubular
\z
"Michael Hartl"@example.com
Rubular
Michael Lovitt
6.2.
- 177
6.6
Rubular
6.21
6.21
GREEN
app/models/user.rb
class User < ActiveRecord::Base
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX }
end
VALID_EMAIL_REGEX
Ruby
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX }
foo@bar..com
6.5
6.22
GREEN
178 -
6.2.5.
validates
:unique
User.new
13
Ruby
6.23
6.23
RED
test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com")
end
.
.
.
test "email addresses should be unique" do
duplicate_user = @user.dup
@user.save
assert_not duplicate_user.valid?
end
end
@user.dup
@user
@user
@user
duplicate_user
uniqueness: true
6.24
6.23
6.24
GREEN
app/models/user.rb
class User < ActiveRecord::Base
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: true
end
foo@bar.com
14
FOO@BAR.COM
FoO@BAr.coM
6.25
db/test.sqlite3
13.
foo@bar.com
14.
about.com
Foo@bar.com
ISP
Riley Moses
6.2.
- 179
6.25
RED
test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com")
end
.
.
.
test "email addresses should be unique" do
duplicate_user = @user.dup
duplicate_user.email = @user.email.upcase
@user.save
assert_not duplicate_user.valid?
end
end
upcase
4.3.2
:uniqueness
true
:case_sensitive
6.26
6.26
GREEN
app/models/user.rb
class User < ActiveRecord::Base
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
end
true
180 -
case_sensitive: false
Rails
:uniqueness
true
6.27
GREEN
Active Record
1. Alice
alice@wonderland.com
2. Alice
3.
2
4.
Rails
email
6.2
6.2
email
6.2
7
email
"foobar"
"foobar"
"foobar"
Rails
6.1.1
migration
6.2
6.28
15
6.28
db/migrate/[timestamp]_add_index_to_users_email.rb
class AddIndexToUsersEmail < ActiveRecord::Migration
def change
add_index :users, :email, unique: true
end
end
15.
users
6.2
Rails
6.2.
- 181
add_index
Rails
users
unique: true
6.1
6.29
fixture
6.29
RED
test/fixtures/users.yml
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/
# FixtureSet.html
one:
name: MyString
email: MyString
two:
name: MyString
email: MyString
6.30
6.30
GREEN
test/fixtures/users.yml
# empty
Foo@ExAMPle.CoM foo@example.com
foo@example.com
16
callback
Foo@ExAMPle.CoM
Active Record
before_save
6.31
8.4
6.31
email
app/models/user.rb
class User < ActiveRecord::Base
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
16. Rails
182 -
Rails API
GREEN
6.31
downcase
6.31
self.email = self.email.downcase
self
self
reverse
palindrome
4.4.2
self.email = email.downcase
self
email = email.downcase
8.4
Alice
Rails
6.2
6.1.4
6.3.
name
4.3.3
Ruby
8
6.3.1.
Rails
has_secure_password
6.3.
- 183
password_digest
17
password
password_confirmation
authenticate
false
has_secure_password
password_digest
gest
di-
18
6.7
password_digest
6.7
password_digest
6.7
to_users
users
Rails
add_password_digest_to_users
$ rails generate migration add_password_digest_to_users password_digest:string
password_digest:string
users
password_digest:string
6.1
name:string
email:string
Rails
6.32
users
password_digest
db/migrate/[timestamp]_add_password_digest_to_users.rb
6.32
password_digest
users
17.
18.
Rails
Andy Philips
184 -
has_secure_password
bcrypt
bcrypt
bcrypt
file
bcrypt gem
Gem-
6.3.
- 185
6.33
bcrypt gem
6.33
Gemfile
source 'https://rubygems.org'
gem 'rails',
gem 'bcrypt',
.
.
.
'4.2.0'
'3.1.7'
bundle install
$ bundle install
6.3.2.
password_digest
has_secure_password
bcrypt
6.34
has_secure_password
6.34
RED
app/models/user.rb
class User < ActiveRecord::Base
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
end
6.34
6.35
RED
RED
6.3.1
has_secure_password
6.25
password
password_confirmation
@user
def setup
@user = User.new(name: "Example User", email: "user@example.com")
end
6.36
6.36
GREEN
test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com",
password: "foobar", password_confirmation: "foobar")
end
.
.
.
end
6.37
GREEN
6.3.4
6.3.3.
Rails
6
6.38
6.38
RED
test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com",
password: "foobar", password_confirmation: "foobar")
end
.
.
.
test "password should have a minimum length" do
@user.password = @user.password_confirmation = "a" * 5
assert_not @user.valid?
end
end
186 -
maximum
password_confirmation
6.16
6.39
6.39
GREEN
app/models/user.rb
class User < ActiveRecord::Base
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }
end
6.40
GREEN
6.3.4.
7.1
has_secure_password
authenticate
7
create
6.1.3
rails console
$ rails console
>> User.create(name: "Michael Hartl", email: "mhartl@example.com",
?>
password: "foobar", password_confirmation: "foobar")
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-09-11 14:26:42", updated_at: "2014-09-11 14:26:42",
password_digest: "$2a$10$sLcMI2f8VglgirzjSJOln.Fv9NdLMbqmR4rdTWIXY1G...">
db/development.sqlite3
SQLite
6.8
19
19.
6.7
1
7
users
2
$ rm -f development.sqlite3
$ bundle exec rake db:migrate
6.3.
- 187
6.8
SQLite
db/development.sqlite3
password_digest
6.39
has_secure_password
bcrypt
20
has_secure_password
6.3.1
authenticate
password_digest
>> user.authenticate("not_the_right_password")
false
>> user.authenticate("foobaz")
false
user.authenticate
false
>> user.authenticate("foobar")
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-07-25 02:58:28", updated_at: "2014-07-25 02:58:28",
password_digest: "$2a$10$YmQTuuDNOszvu5yi7auOC.F4G//FGhyQSWCpghqRWQW...">
20. bcrypt
188 -
authenticate
authenticate
authenticate
nil
21
false
>> !!user.authenticate("foobar")
=> true
6.4.
name
password
6.3
Git
$ bundle exec rake test
$ git add -A
$ git commit -m "Make a basic User model (including secure passwords)"
Heroku
heroku run
6.4.1.
Active Record
Active Record
21. 4.2.3
!!
6.4.
- 189
has_secure_password
6.5.
3
1.
6.31
6.41
reload
before_save
6.41
before_save
2.
3.
email.downcase!
6.2.4
foo@bar..com
6.43
6.41
assert_equal
6.21
6.19
6.31
test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com",
password: "foobar", password_confirmation: "foobar")
end
.
.
.
test "email addresses should be unique" do
duplicate_user = @user.dup
duplicate_user.email = @user.email.upcase
@user.save
assert_not duplicate_user.valid?
end
test "email addresses should be saved as lower-case" do
mixed_case_email = "Foo@ExAMPle.CoM"
@user.email = mixed_case_email
@user.save
assert_equal mixed_case_email.downcase, @user.reload.email
end
test "password should have a minimum length" do
@user.password = @user.password_confirmation = "a" * 5
assert_not @user.valid?
190 -
6.42
end
end
6.42 before_save
app/models/user.rb
GREEN
6.43
GREEN
app/models/user.rb
class User < ActiveRecord::Base
before_save { email.downcase! }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence:
true,
format:
{ with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }
end
6.5.
- 191
7.2
7.4
REST
2.2.2
5.3.4
6
10
7.1.
7.1
193
7.1
7.2
7.2
lorem ipsum
12
7.2
7.1.1.
Rails
7.1
Rails
7.1.2
1. Mockingbird
2.
194 -
7.1
GIMP
http://www.flickr.com/photos/43803060@N00/24308857/
debug
params
7.1
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
.
.
.
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
<%= debug(params) if Rails.env.development? %>
</div>
</body>
</html>
if Rails.env.development?
Rails
7.1
true
Rails.env.development?
Ruby
7.1
Rails
Rails
Rails
$ rails console
Loading development environment
>> Rails.env
=> "development"
>> Rails.env.development?
=> true
>> Rails.env.test?
=> false
Rails
env
Rails.env.test?
true
false
console
3.
RailsCast
7.1.
- 195
>>
=>
>>
=>
Rails.env
"test"
Rails.env.test?
true
Rails
$ rails server --environment production
rake db:migrate
Heroku
Heroku
5
7.2
7.2
app/assets/stylesheets/custom.css.scss
@import "bootstrap-sprockets";
@import "bootstrap";
/* mixins, variables, etc. */
$gray-medium-light: #eaeaea;
@mixin box_sizing {
-moz-box-sizing:
border-box;
-webkit-box-sizing: border-box;
box-sizing:
border-box;
}
.
.
.
/* miscellaneous */
196 -
Sass
.debug_dump {
clear: both;
float: left;
width: 100%;
margin-top: 45px;
@include box_sizing;
}
Sass
box-sizing
mixin
.debug_dump {
.
.
.
@include box_sizing;
}
.debug_dump {
.
.
.
-moz-box-sizing:
border-box;
-webkit-box-sizing: border-box;
box-sizing:
border-box;
}
7.2.1
7.3
7.3
--controller: static_pages
action: home
params
4. Rails
YAML 4
YAML
7.1.2
7.1.
- 197
7.3
7.1.2.
6.3.4
Rails
$ rails console
>> User.count
=> 1
>> User.first
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-08-29 02:58:28", updated_at: "2014-08-29 02:58:28",
password_digest: "$2a$10$YmQTuuDNOszvu5yi7auOC.F4G//FGhyQSWCpghqRWQW...">
6.3.4
ID
Rails
HTTP
REST
2.2
POST
GET
PATCH
DELETE
3.2
REST
ID
/users/1
GET
2.2.1
7.4
198 -
Rails
REST
show
ID
GET
URL
/users/1
URL
7.4
/users/1
config/routes.rb
/users/1
resources :users
7.3
7.3
config/routes.rb
Rails.application.routes.draw do
root
'static_pages#home'
get 'help'
=> 'static_pages#help'
get 'about'
=> 'static_pages#about'
get 'contact' => 'static_pages#contact'
get 'signup' => 'users#new'
resources :users
end
resources :users
URL
/users/1
REST
7.1
URL
5.3.3
2.2
7.1
REST
7.1
7.3
HTTP
URL
GET
/users
index
users_path
GET
/users/1
show
user_path(user)
GET
/users/new
new
new_user_path
POST
/users
create
users_path
GET
/users/1/edit
edit
edit_user_path(user)
PATCH
/users/1
update
user_path(user)
5.
REST
ID
edit
/users/1/edit
edit
7.1.
- 199
HTTP
URL
DELETE
/users/1
destroy
user_path(user)
7.3
7.1.4
7.5
7.5
/users/1
app/views/users/show.html.erb
new.html.erb
5.28
7.4
7.4
app/views/users/show.html.erb
<%= @user.name %>, <%= @user.email %>
@user
show
find
ERb
@user
6.1.4
show
app/controllers/users_controller.rb
7.5
200 -
7.5
@user = User.find(params[:id])
end
def new
end
end
ID
1
"1"
params
ID
6.1.4
User.find(1)
params[:id]
params[:id]
find
/users/1
7.6
bcrypt
Rails
params[:id]
--action: show
controller: users
id: '1'
7.5
User.find(params[:id])
7.6
ID
show
7.1.3.
7.1.2
3.2
byebug gem
Rails 4.2
debugger
gem
7.6
7.1.
- 201
7.6
app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
debugger
end
def new
end
end
/users/1
byebug
Rails
(byebug)
Rails
(byebug) @user.name
"Example User"
(byebug) @user.email
"example@railstutorial.org"
(byebug) params[:id]
"1"
byebug
Ctrl-D
show
debugger
7.7
7.7
app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
end
end
debugger
Rails
byebug
7.1.4. Gravatar
6.
202 -
Avatar
Gravatar
Avatar
Gravatar
URL
11.4
gravatar_for
7.8
Gravatar
7.8
Gravatar
app/views/users/show.html.erb
<% provide(:title, @user.name) %>
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
gravatar_for
Gravatar
MD5
Ruby
URL
Digest
MD5
hexdigest
MD5
hexdigest
6.31
gravatar_for
7.9
7.9
gravatar_for
app/helpers/users_helper.rb
module UsersHelper
#
Gravatar
def gravatar_for(user)
gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}"
image_tag(gravatar_url, alt: user.name, class: "gravatar")
end
end
gravatar_for
img
7.7
example.com
Gravatar
Gravatar
img
gravatar
CSS
alt
user@example.com
7.1.
- 203
7.7
Gravatar
7.8
204 -
update_attributes
6.1.5
$ rails console
>> user = User.first
>> user.update_attributes(name: "Example User",
?>
email: "example@railstutorial.org",
?>
password: "foobar",
?>
password_confirmation: "foobar")
=> true
example@railstutorial.org
LOGO
7.8
aside
7.1
aside
aside
md-4
row col-
Bootstrap
7.10
7.10
show
app/views/users/show.html.erb
<% provide(:title, @user.name) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
</section>
</aside>
</div>
HTML
CSS
Asset Pipeline
SCSS
7.11
Sass
7.9
7.11
app/assets/stylesheets/custom.css.scss
.
.
.
/* sidebar */
aside {
section.user_info {
margin-top: 20px;
}
section {
padding: 10px 0;
7.
.gravatar_edit
7.1.
- 205
margin-top: 20px;
&:first-child {
border: 0;
padding-top: 0;
}
span {
display: block;
margin-bottom: 3px;
line-height: 1;
}
h1 {
font-size: 1.4em;
text-align: left;
letter-spacing: -1px;
margin-bottom: 3px;
margin-top: 0px;
}
}
}
.gravatar {
float: left;
margin-right: 10px;
}
.gravatar_edit {
margin-top: 15px;
}
7.2.
5.9
206 -
7.11
7.10
7.9
CSS
7.10
7.2.
- 207
6.3.4
db:migrate:reset
$ bundle exec rake db:migrate:reset
Web
7.11
7.2.1.
form_for
Rails
form_for
Active Record
new
/signup
form_for
@user
7.12
new
@user
app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
208 -
5.33
7.12
end
def new
@user = User.new
end
end
7.13
7.2.2
7.2
SCSS
7.12
7.14
7.13
app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(@user) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation %>
<%= f.submit "Create my account", class: "btn btn-primary" %>
<% end %>
</div>
</div>
7.14
app/assets/stylesheets/custom.css.scss
.
.
.
/* forms */
input, textarea, select, .uneditable-input {
border: 1px solid #bbb;
width: 100%;
margin-bottom: 15px;
@include box_sizing;
}
input {
7.2.
- 209
7.12
7.2.2.
HTML
7.13
form_for
end
form_for
f
form_for
Rails
@user
<%= f.label :name %>
<%= f.text_field :name %>
HTML
210 -
label
name
ERb
7.15
7.15
7.12
7.13
7.15
ERb
HTML
<label for="user_name">Name</label>
<input id="user_name" name="user[name]" type="text" />
ERb
<%= f.label :password %>
<%= f.password_field :password %>
HTML
<label for="user_password">Password</label>
<input id="user_password" name="user[password]" type="password" />
7.13
type="text"
type="password"
7.2.
- 211
7.13
input
7.4
name
7.3
name
Rails
form
4.4.1
Rails
params
@user
Rails
@user
HTTP
form
User
@user
Ruby
Rails
post
3.2
/users
id
action="/users"
POST
form
<div style="display:none">
<input name="utf8" type="hidden" value="✓" />
<input name="authenticity_token" type="hidden"
212 -
method="post"
Rails
value="NNb6+J/j46LcrgYUC60wQ2titMuJQ5lLqyAbnbAUkdo=" />
</div>
Unicode
authenticity token
✓
Rails
Rails
CSRF
7.3.
7.12
HTML
7.15
7.14
7.14
7.3.1.
7.1.2
7.1
8.
routes.rb
REST
resources :users
URL
/users
7.3
POST
Rails
create
Stack Overflow
7.3.
- 213
create
User.new
form
7.2.2
POST
/users
render
7.16
render
5.1.3
if-else
@user.save
true
6.1.3
false
create
app/controllers/users_controller.rb
7.16
7.3.2
7.16
7.15
7.16
7.15
Web
Rails
params
Web
214 -
7.15
7.16
user
7.16
Rails
7.3.
- 215
/users/1
params
7.1.2
ID
POST
params
params
4.3.3
user
input
Rails
name
7.13
user[email]
user
email
params[:user]
User.new
User.new
4.4.5
7.16
@user = User.new(params[:user])
Rails
@user = User.new(params[:user])
Rails 4.0
7.15
7.16
7.3.2.
4.4.5
@user = User.new(params[:user])
Ruby
#
params
7.16
User.new
admin
true
9.4.1
params[:user]
params
admin='1'
curl
HTTP
User.new
admin='1'
attr_accessible
Rails
Rails 4.0
Rails
strong parameter
params
Rails
params
:user
name
password
word_confirmation
params.require(:user).permit(:name, :email, :password, :password_confirmation)
216 -
pass-
params
:user
user_params
params[:user]
@user = User.new(user_params)
user_params
Ruby
vate
7.17
pripri-
8.4
vate
create
app/controller/users_controller.rb
7.17
user_params
7.17
7.3.3
9.
private
7.4
7.3.
- 217
7.17
7.3.3.
Rails
$ rails console
>> user = User.new(name: "Foo Bar", email: "foo@invalid",
?>
password: "dude", password_confirmation: "dude")
>> user.save
=> false
>> user.errors.full_messages
=> ["Email is invalid", "Password is too short (minimum is 6 characters)"]
errors.full_message
6.2.2
@user
7.16
new
form-control
CSS
Bootstrap
11.3.2
7.18
app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
218 -
7.18
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(@user) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :email %>
<%= f.text_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit "Create my account", class: "btn btn-primary" %>
<% end %>
</div>
</div>
shared/error_messages
mkdir
1.1
Rails
shared/
9.1.1
app/views/shared
$ mkdir app/views/shared
_error_messages.html.erb
7.19
7.19
app/views/shared/_error_messages.html.erb
<% if @user.errors.any? %>
<div id="error_explanation">
<div class="alert alert-danger">
The form contains <%= pluralize(@user.errors.count, "error") %>.
</div>
<ul>
<% @user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
Rails/Ruby
Rails
count
>> user.errors.count
=> 2
7.3.
- 219
any?
>>
=>
>>
=>
empty?
user.errors.empty?
false
user.errors.any?
true
empty?
empty?
4.2.3
true
true
false
any?
false
count
empty?
empty?
any?
pluralize
Ruby
11.2
Action10
View::Helpers::TextHelper
>>
>>
=>
>>
=>
Rails
include ActionView::Helpers::TextHelper
pluralize(1, "error")
"1 error"
pluralize(5, "error")
"5 errors"
pluralize
pluralize
>>
=>
>>
=>
inflector
pluralize(2, "woman")
"2 women"
pluralize(3, "erratum")
"3 errata"
pluralize
"1 error"
"2 errors"
"1 errors"
7.19
CSS ID
ID
CSS
field_with_errors
CSS
error_explanation
div
7.20
5.1.2
Rails
ID
Sass
@extend
SCSS
Bootstrap
has-error
7.20
app/assets/stylesheets/custom.css.scss
.
.
.
/* forms */
.
.
ActionView::Helpers::TextHelper
10.
220 -
Rails API
pluralize
.
#error_explanation {
color: red;
ul {
color: red;
margin: 0 0 30px 0;
}
}
.field_with_errors {
@extend .has-error;
.form-control {
color: $state-danger-text;
}
}
7.18
7.19
7.20
SCSS
7.18
7.18
7.3.4.
Web
7.3.
- 221
Rails
7.4.4
users_signup
7.4.4
Active Record
User
count
$ rails console
>> User.count
=> 0
User.count
sert_select
7.2
as-
5.3.4
HTML
get
get signup_path
users_path
POST
post
7.1
assert_no_difference 'User.count' do
post users_path, user: { name: "",
email: "user@invalid",
password:
"foo",
password_confirmation: "bar" }
end
create
User.new
sert_no_difference
params[:user]
post
7.24
assert_no_difference
as-
'User.count'
User.count
post
before_count = User.count
post users_path, ...
after_count = User.count
assert_equal before_count, after_count
assert_no_difference
Ruby
assert_template
7.21
new
7.21
GREEN
test/integration/users_signup_test.rb
222 -
7.7
require 'test_helper'
class UsersSignupTest < ActionDispatch::IntegrationTest
test "invalid signup information" do
get signup_path
assert_no_difference 'User.count' do
post users_path, user: { name: "",
email: "user@invalid",
password:
"foo",
password_confirmation: "bar" }
end
assert_template 'users/new'
end
end
7.22
GREEN
7.4.
7.19
7.4.
- 223
7.19
7.4.1.
7.17
7.20
224 -
Rails
create
7.20
redirect_to
7.23
7.23 create
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params)
if @user.save
redirect_to @user
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
7.4.
- 225
redirect_to @user
redirect_to user_url(@user)
redirect_to @user
Rails
user_url(@user)
7.4.2.
7.23
Web
Rails
:success
flash message
Rails
7.24
7.24
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params)
if @user.save
flash[:success] = "Welcome to the Sample App!"
redirect_to @user
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
flash
flash
4.3.3
flash
$ rails console
>> flash = { success: "It worked!", danger: "It failed." }
=> {:success=>"It worked!", danger: "It failed."}
>> flash.each do |key, value|
?>
puts "#{key}"
226 -
?>
puts "#{value}"
>> end
success
It worked!
danger
It failed.
HTML
ERb
CSS
alert-success
:success
"success"
ERb
CSS
alert-danger
8.1.4
11
flash[:danger]
Bootstrap
7.19
success
CSS
info
warning
danger
HTML
<div class="alert alert-success">Welcome to the Sample App!</div>
ERb
7.25
7.25
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
.
.
.
<body>
<%= render 'layouts/header' %>
<div class="container">
<% flash.each do |message_type, message| %>
<div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>
<%= yield %>
11.
flash.now
7.4.
- 227
7.4.3.
Rails Tutorial
example@railstutorial.org
7.22
5.1.2
7.21
.success
Bootstrap
db:migrate:reset Rake
7.2
7.23
7.21
228 -
7.22
7.23
7.4.
- 229
$ rails console
>> User.find_by(email: "example@railstutorial.org")
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org",
created_at: "2014-08-29 19:53:17", updated_at: "2014-08-29 19:53:17",
password_digest: "$2a$10$zthScEx9x6EkuLa4NolGye6O0Zgrkp1B6LQ12pTHlNB...">
7.4.4.
7.3.4
7.21
assert_no_difference 'User.count' do
post signup_path, ...
end
assert_difference
assert_difference 'User.count', 1 do
post_via_redirect signup_path, ...
end
assert_no_difference
assert_difference
'User.count'
User.count
assert_difference
1
7.21
post_via_redirect
7.26
7.26
users/show
GREEN
test/integration/users_signup_test.rb
require 'test_helper'
class UsersSignupTest < ActionDispatch::IntegrationTest
.
.
.
test "valid signup information" do
get signup_path
name
= "Example User"
email
= "user@example.com"
password = "password"
assert_difference 'User.count', 1 do
post_via_redirect users_path, user: { name: name,
email: email,
password:
password,
password_confirmation: password }
end
assert_template 'users/show'
end
end
230 -
show
show
7.3
show.html.erb
7.5
7.8
assert_template 'users/show'
7.5.
3
Web
master
$
$
$
$
git
git
git
git
add -A
commit -m "Finish user signup"
checkout master
merge sign-up
7.5.1.
SSL
SSL
12
SSL
SSL
8.4
production.rb
SSL
config
7.27
SSL
7.27
SSL
config/environments/production.rb
Rails.application.configure do
.
.
.
# Force all access to the app over SSL, use Strict-Transport-Security,
# and use secure cookies.
config.force_ssl = true
.
.
.
end
12.
SSL
TLS
SSL
7.5.
- 231
SSL
SSL
Heroku
Heroku
SSL
7.5.2
Heroku
www.example.com
SSL
7.5.2.
SSL
SSL
Unicorn
SSL
Ruby
Web
Heroku
WEBrick
WEBrick
Unicorn
Heroku
Gemfile
Unicorn
Unicorn
Gemfile
7.28
unicorn
unicorn gem
7.28
:production
Unicorn
source 'https://rubygems.org'
.
.
.
group :production do
gem 'pg',
'0.17.1'
gem 'rails_12factor', '0.0.2'
gem 'unicorn',
'4.8.3'
end
Bundler
gem
gem
Bundler
3.1
7.28
Gemfile.lock
$ bundle install
config/unicorn.rb
7.29
13
7.29
Unicorn
config/unicorn.rb
worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3)
timeout 15
preload_app true
before_fork do |server, worker|
Signal.trap 'TERM' do
puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
Process.kill 'QUIT', Process.pid
end
defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
end
13.
232 -
80
7.29
Heroku
Heroku
7.30
Procfile
Gemfile
7.30
Unicorn
Procfile
Unicorn
./Procfile
web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb
14
Unicorn
$
$
$
$
$
$
7.24
https://
14.
SSL
Heroku
7.5.
- 233
7.24
7.5.3. Ruby
Heroku
###### WARNING:
You have not declared a Ruby version in your Gemfile.
To set your Ruby version add this line to your Gemfile:
ruby '2.1.5'
Ruby
Ruby
Heroku
15
Gemfile
7.6.
9
7.1
REST
15.
2.1.5
234 -
Ruby 2.1.4
Ruby 2.1.5
Ruby
7.6.1.
Rails
debug
Sass
CSS
development
Rails
test
production
REST URL
Gravatar
form_for
Active Record
Active Record
Rails
flash
SSL
Unicorn
7.7.
3
1.
7.31
gravatar_for
7.1.4
2.
size
9.3.1
7.18
7.32
3.
7.4.2
7.33
FILL_IN
nil
4. 7.4.2
7.25
HTML
7.34
content_tag
7.31
gravatar_for
app/helpers/users_helper.rb
module UsersHelper
#
Gravatar
def gravatar_for(user, options = { size: 80 })
gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
size = options[:size]
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
image_tag(gravatar_url, alt: user.name, class: "gravatar")
end
end
7.32
test/integration/users_signup_test.rb
7.7.
- 235
require 'test_helper'
class UsersSignupTest < ActionDispatch::IntegrationTest
test "invalid signup information" do
get signup_path
assert_no_difference 'User.count' do
post users_path, user: { name: "",
email: "user@invalid",
password:
"foo",
password_confirmation: "bar" }
end
assert_template 'users/new'
assert_select 'div#<CSS id for error explanation>'
assert_select 'div.<CSS class for field with error>'
end
.
.
.
end
7.33
test/integration/users_signup_test.rb
require 'test_helper'
.
.
.
test "valid signup information" do
get signup_path
name = "Example User"
email = "user@example.com"
password = "password"
assert_difference 'User.count', 1 do
post_via_redirect users_path, user: { name: name,
email: email,
password:
password,
password_confirmation: password }
end
assert_template 'users/show'
assert_not flash.FILL_IN
end
end
content_tag
app/views/layouts/application.html.erb
7.34
<!DOCTYPE html>
<html>
.
.
.
<% flash.each do |message_type, message| %>
236 -
7.7.
- 237
8.1
8.2
8.4
8.4.5
11
12
8.1.
HTTP
HTTP
Web
Rails
Rails
cookie
cookie cookie
cookie
Rails
session
8.2
2
8.4
Rails
session
ID
cookies
REST
cookie
8.2
8.1.1.
new
create
REST
POST
destroy
8.2
DELETE
8.3
HTTP
7.1
1.
2.
Rails
239
new
$ rails generate controller Sessions new
new
create
delete
7.2
8.1
8.1
resources
/login
REST
GET
POST
8.1
config/routes.rb
Rails.application.routes.draw do
root
'static_pages#home'
get
'help'
=> 'static_pages#help'
get
'about'
=> 'static_pages#about'
get
'contact' => 'static_pages#contact'
get
'signup' => 'users#new'
get
'login'
=> 'sessions#new'
post
'login'
=> 'sessions#create'
delete 'logout' => 'sessions#destroy'
240 -
7.3
/logout
DELETE
8.1
resources :users
end
8.1
URL
7.1
8.1
8.1
HTTP
URL
GET
/login
login_path
new
POST
/login
login_path
create
DELETE
/logout
logout_path
destroy
8.1
rake routes
8.1.2.
8.1
8.2
7.11
7.3.3
Active Record
Active Record
8.1.
- 241
8.2
form_for
7.13
@user
form_for
<%= form_for(@user) do |f| %>
.
.
.
<% end %>
@user
form_for
form_for(@user)
/users
POST
URL
7.13
8.1
8.2
form_for
3.
242 -
form_tag
Rails
form_tag
8.2
app/views/sessions/new.html.erb
<% provide(:title, "Log in") %>
<h1>Log in</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(:session, url: login_path) do |f| %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.submit "Log in", class: "btn btn-primary" %>
<% end %>
<p>New user? <%= link_to "Sign up now!", signup_path %></p>
</div>
</div>
8.2
Log in
/login
8.3
8.2.3
8.3
8.1.
- 243
HTML
8.3
8.3
8.2
HTML
8.3
params[:session][:email]
params
7.15
params[:session][:password]
8.1.3.
8.2
8.2
create
create
8.4
new
8.4
8.4
create
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
render 'new'
end
def destroy
end
end
244 -
new
destroy
/login
8.4
8.4
8.4
create
params
8.1.2
:session
Rails
--session:
email: 'user@example.com'
password: 'foobar'
commit: Log in
action: create
controller: sessions
7.15
4.10
params
params[:session]
params[:session][:email]
8.1.
- 245
params[:session][:password]
create
has_secure_password
params
User.find_by
Active Record
6.3.4
6.1.4
authenticate
false
authenticate
8.5
8.5
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
#
else
#
render 'new'
end
end
def destroy
end
end
8.5
6.2.5
downcase
Rails
user && user.authenticate(params[:session][:password])
&&
nil
false
true
8.2
true
8.2
a && b
(nil && [anything]) == false
(true && false) == false
(true && true) == true
246 -
8.1.4.
7.3.3
Active Record
Active Record
8.6
8.6
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
#
else
flash[:danger] = 'Invalid email/password combination' #
render 'new'
end
end
def destroy
end
end
flash[:danger]
7.25
Bootstrap
CSS
8.5
8.6
render
7.24
8.6
8.1.5
8.1.
- 247
8.5
8.6
248 -
8.1.5.
bug
3.3
8.5
8.6
1.
2.
params
3.
post
4.
5.
6.
8.7
8.7
RED
test/integration/users_login_test.rb
require 'test_helper'
class UsersLoginTest < ActionDispatch::IntegrationTest
test "login with invalid information" do
get login_path
assert_template 'sessions/new'
post login_path, session: { email: "", password: "" }
assert_template 'sessions/new'
assert_not flash.empty?
get root_path
assert flash.empty?
end
end
8.8
RED
8.7
flash
flash.now
flash.now
flash.now
8.7
8.9
8.1.
- 249
8.9
GREEN
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
#
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
end
end
8.10
GREEN
8.2.
8.4
4.2.5
8.1.1
Rails
ApplicationCon-
Rails
troller
8.11
ApplicationController
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
include SessionsHelper
end
250 -
Ruby
8.11
8.2.1. log_in
session
Rails
session
8.1.1
session
session[:user_id] = user.id
cookie
sion[:user_id]
ID
8.4
ID
cookie
cookies
sessession
log_in
8.12
8.12 log_in
app/helpers/sessions_helper.rb
module SessionsHelper
#
def log_in(user)
session[:user_id] = user.id
end
end
session
cookie
8.12
session
cookie
cookie
8.4
session hijacking
log_in
create
8.13
cookies
8.13
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
redirect_to user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
4.
8.11
log_in
8.2.
- 251
end
def destroy
end
end
redirect_to user
7.4.1
Rails
user_path(user)
create
8.2
8.2.2
ID
8.2.3
8.2.2.
cur-
ID
rent_user
current_user
ID
redirect_to current_user
find
7.5
User.find(session[:user_id])
6.1.4
find
ID
session[:user_id]
create
find_by
nil
id
User.find_by(id: session[:user_id])
ID
find_by
nil
current_user
def current_user
User.find_by(id: session[:user_id])
end
current_user
User.find_by
Ruby
5.
252 -
memoization
memorization
if @current_user.nil?
@current_user = User.find_by(id: session[:user_id])
else
@current_user
end
4.2.3
||
@current_user
find_by
@current_user
Ruby
8.1
8.1
||=
Ruby
||=
||=
Rails
x = x + 1
Ruby
Java
x += 1
$ rails console
>> x = 1
=> 1
>> x += 1
=> 2
>> x *= 3
=> 6
>> x -= 8
=> -2
>> x /= 2
=> -1
x = x O y
x O=y
O
nil
Ruby
4.2.3
||
>> @foo
=> nil
8.2.
- 253
>>
=>
>>
=>
nil
nil || "bar"
"bar" || "baz"
"bar"
"bar"
nil
false
||
short-circuit evaluation
@foo = @foo || "bar"
x
x
x
x
@foo
=
x
+
1
=
x
*
3
=
x
8
=
x
/
2
= @foo || "bar"
->
->
->
->
->
x = x O y
||
x
+=
1
x
*=
3
x
-=
8
x
/=
2
@foo ||= "bar"
current_user
8.14
8.14
app/helpers/sessions_helper.rb
module SessionsHelper
#
def log_in(user)
session[:user_id] = user.id
end
#
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
end
current_user
6.
||=
Or Equals) Really Does
254 -
Peter Cooper
Ruby Inside
8.2.3.
8.7
Account
Bootstrap
8.16
8.7
3.3
Rails
8.2.4
ERb
if-else
7.
http://www.flickr.com/photos/hermanusbackpackers/3343254977/
8.2.
- 255
logged_in?
current_user
4.2.3
bang
nil
logged_in?
8.15
logged_in?
app/helpers/sessions_helper.rb
8.15
module SessionsHelper
#
def log_in(user)
session[:user_id] = user.id
end
#
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
#
def logged_in?
!current_user.nil?
end
end
true
false
logged_in?
9
<%= link_to "Users",
'#' %>
<%= link_to "Settings", '#' %>
8.1
<%= link_to "Log out", logout_path, method: "delete" %>
HTTP DELETE
Rails
current_user
user_path(current_user)
8.1
8.16
8. Web
256 -
DELETE
Rails
JavaScript
8.16
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
<%= link_to "sample app", root_path, id: "logo" %>
<nav>
<ul class="nav navbar-nav pull-right">
<li><%= link_to "Home", root_path %></li>
<li><%= link_to "Help", help_path %></li>
<% if logged_in? %>
<li><%= link_to "Users", '#' %></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
Account <b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li><%= link_to "Profile", current_user %></li>
<li><%= link_to "Settings", '#' %></li>
<li class="divider"></li>
<li>
<%= link_to "Log out", logout_path, method: "delete" %>
</li>
</ul>
</li>
<% else %>
<li><%= link_to "Log in", login_path %></li>
<% end %>
</ul>
</nav>
</div>
</header>
8.16
dropdown
Bootstrap CSS
Pipeline
dropdown-menu
Bootstrap
8.17
application.js
app/assets/javascripts/application.js
//=
//=
//=
//=
//=
Bootstrap
application.js
JavaScript
Asset
8.17
Bootstrap JavaScript
require jquery
require jquery_ujs
require bootstrap
require turbolinks
require_tree .
10
8.16
8.17
8.8
9.
10.
Bootstrap
IDE
IDE
8.2.
- 257
8.8
8.2.4.
8.7
1.
post
2.
3.
4.
5.
Rails
6.2.5
6.30
create
password_digest
6.3.1
258 -
bcrypt
6.7
digest
has_secure_password
cost
di-
gest
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
?-:
8.4.4
digest
user.rb
8.4.1
digest
User
4.4.1
8.18
8.18
digest
app/models/user.rb
class User < ActiveRecord::Base
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }
#
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
end
digest
8.19
8.19
test/fixtures/users.yml
michael:
name: Michael Example
email: michael@example.com
password_digest: <%= User.digest('password') %>
Ruby
<%= User.digest('password') %>
8.2.
- 259
has_secure_password
password_digest
8.19
password
Rails
'password'
user = users(:michael)
users
users.yml
:michael
8.19
8.20
get
post
POST
GET
8.20
GREEN
test/integration/users_login_test.rb
require 'test_helper'
class UsersLoginTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
.
.
.
test "login with valid information" do
get login_path
post login_path, session: { email: @user.email, password: 'password' }
assert_redirected_to @user
follow_redirect!
assert_template 'users/show'
assert_select "a[href=?]", login_path, count: 0
assert_select "a[href=?]", logout_path
assert_select "a[href=?]", user_path(@user)
end
end
assert_redirected_to @user
follow_redirect!
assert_select
count: 2
8.21
GREEN
260 -
5.25
TESTOPTS="--name test_login_with_valid_information"
test
8.2.5.
create
log_in
8.22
11
8.22
app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
log_in @user
flash[:success] = "Welcome to the Sample App!"
redirect_to @user
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
7.26
8.15
is_logged_in?
ID
true
rent_user
logged_in?
11.
false
current_user
logged_in?
8.23
session
12
is_logged_in?
8.11
cur-
8.15
log_in
8.2.
- 261
8.23
test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
.
.
.
class ActiveSupport::TestCase
fixtures :all
#
true
def is_logged_in?
!session[:user_id].nil?
end
end
8.24
8.24
GREEN
test/integration/users_signup_test.rb
require 'test_helper'
class UsersSignupTest < ActionDispatch::IntegrationTest
.
.
.
test "valid signup information" do
get signup_path
assert_difference 'User.count', 1 do
post_via_redirect users_path, user: { name: "Example User",
email: "user@example.com",
password:
"password",
password_confirmation: "password" }
end
assert_template 'users/show'
assert is_logged_in?
end
end
8.25
GREEN
log_in
is_logged_in?
12.
log_in_as
262 -
8.50
8.3.
8.1
8.16
new
REST
create
destroy
REST
8.13
destroy
8.22
8.4.6
log_in
8.12
ID
delete
session.delete(:user_id)
nil
13
log_in
log_out
8.26
8.26 log_out
app/helpers/sessions_helper.rb
module SessionsHelper
#
def log_in(user)
session[:user_id] = user.id
end
.
.
.
#
def log_out
session.delete(:user_id)
@current_user = nil
end
end
destroy
log_out
8.27
8.27
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def new
end
destroy
13.
@current_user
@current_user
nil
nil
8.3.
- 263
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
redirect_to user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
log_out
redirect_to root_url
end
end
delete
8.20
8.1
DELETE
8.28
8.28
GREEN
test/integration/users_login_test.rb
require 'test_helper'
class UsersLoginTest < ActionDispatch::IntegrationTest
.
.
.
test "login with valid information followed by logout" do
get login_path
post login_path, session: { email: @user.email, password: 'password' }
assert is_logged_in?
assert_redirected_to @user
follow_redirect!
assert_template 'users/show'
assert_select "a[href=?]", login_path, count: 0
assert_select "a[href=?]", logout_path
assert_select "a[href=?]", user_path(@user)
delete logout_path
assert_not is_logged_in?
assert_redirected_to root_url
follow_redirect!
assert_select "a[href=?]", login_path
assert_select "a[href=?]", logout_path,
count: 0
assert_select "a[href=?]", user_path(@user), count: 0
end
end
is_logged_in?
is_logged_in?
264 -
assert
destroy
8.29
GREEN
8.4.
8.2
Bitbucket
8.4.5
GitHub
Twitter
8.4.1.
8.2
Rails
session
ID
cookies
cookie
session
8.2.1
cookies
cookie
cookie
1
3
7.5
Cross-Site Scripting
SSL
XSS
14
cookie
4
6.3
Rails
1.
2.
cookie
3.
4.
5.
cookie
cookie
ID
ID
ID
cookie
authenticate
has_secure_password
8.5
remember_digest
14.
Firesheep
8.9
WIFI
8.4.
- 265
remember_digest
8.9
8.9
6.3.1
users
Rails
Rails
8.30
8.30
db/migrate/[timestamp]_add_remember_digest_to_users.rb
class AddRememberDigestToUsers < ActiveRecord::Migration
def change
add_column :users, :remember_digest, :string
end
end
remember_digest
SecureRandom
Ruby
15
urlsafe_base64
- _
22
64
base64
base64
$ rails console
>> SecureRandom.urlsafe_base64
=> "q5lt38hQDc_959PVoo6b7A"
16
17
base64
22
15.
RailsCast
16.
17.
266 -
64
bcrypt
ID
cookie
URL
base64
safe_base64
url-
10
digest
new_token
8.18
18
digest
8.31
8.31
app/models/user.rb
class User < ActiveRecord::Base
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }
#
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
#
def User.new_token
SecureRandom.urlsafe_base64
end
end
user.remember
remember_digest
8.30
user.remember_token
password
has_secure_password
4.4.5
remember_token
cookie
6.3
password_digest
password
remember_token
attr_accessor
18.
10.1.2
8.4.
- 267
end
end
remember
self
Ruby
remember_token
self
remember_token
before_save
remember
6.31
self.email
update_attribute
6.1.5
User.new_token
User.digest
remember
remember
8.32
8.32
GREEN
app/models/user.rb
class User < ActiveRecord::Base
attr_accessor :remember_token
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }
#
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
#
def User.new_token
SecureRandom.urlsafe_base64
end
#
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
end
8.4.2.
user.remember
ID
cookies
cookie
cookie
value
20
268 -
cookie
session
expires
cookies[:remember_token] = { value:
remember_token,
expires: 20.years.from_now.utc }
Rails
ie
8.2
permanent
Rails
Rails
cookie
20
cook-
cookies.permanent[:remember_token] = remember_token
20.years.from_now
Rails
8.2
cookie
4.4.2
String
Object
20.years.from_now
Ruby
palindrome?
blank?
20.years.from_now
"deified"
"".blank?
cookie
" ".blank?
Rails
nil.blank?
cookies.permanent
true
permanent
Rails
Fixnum
$ rails console
>> 1.year.from_now
=> Sun, 09 Aug 2015 16:48:17 UTC +00:00
>> 10.weeks.ago
=> Sat, 31 May 2014 16:48:45 UTC +00:00
Fixnum
Rails
>>
=>
>>
=>
1.kilobyte
1024
5.megabytes
5242880
5.megabytes
Ruby
Rails
Ruby
session
ID
cookie
cookies[:user_id] = user.id
cookie
cookie
cookies.signed[:user_id] = user.id
ID
signed
ID
permanent
cookies.permanent.signed[:user_id] = user.id
cookie
User.find_by(id: cookies.signed[:user_id])
8.4.
- 269
cookies.signed[:user_id]
ies[:remember_token]
cookie
ID
bcrypt
cook-
remember_digest
8.32
ID
ID
ID
bcrypt
19
BCrypt::Password.new(password_digest) == unencrypted_password
BCrypt::Password.new(remember_digest) == remember_token
==
bcrypt
bcrypt
bcrypt
gem
bcrypt
==
BCrypt::Password.new(remember_digest).is_password?(remember_token)
==
is_password?
authenticated?
has_secure_password
authenticate
8.13
authenticated?
8.33
authenticated?
8.33
app/models/user.rb
19. 6.3.1
270 -
unencrypted password
end
#
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
#
true
def authenticated?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
end
8.33
authenticated?
10
log_in
remember
8.34
8.34
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
remember user
redirect_to user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
log_out
redirect_to root_url
end
end
8.34
remember
cookies
user.remember
cookie
ID
8.35
8.35
app/helpers/sessions_helper.rb
8.4.
- 271
module SessionsHelper
#
def log_in(user)
session[:user_id] = user.id
end
#
def remember(user)
user.remember
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
#
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
#
def logged_in?
!current_user.nil?
end
true
false
#
def log_out
session.delete(:user_id)
@current_user = nil
end
end
8.14
current_user
8.5
session
272 -
cookies
cook-
if (user_id = session[:user_id])
@current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated?(cookies[:remember_token])
log_in user
@current_user = user
end
end
if (user_id = session[:user_id])
==
ID
ID
ID
current_user
ID
user_id
20
8.36
8.36
current_user
app/helpers/sessions_helper.rb
RED
module SessionsHelper
#
def log_in(user)
session[:user_id] = user.id
end
#
def remember(user)
user.remember
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
#
cookie
def current_user
if (user_id = session[:user_id])
@current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated?(cookies[:remember_token])
log_in user
@current_user = user
end
end
end
#
true
false
20.
8.4.
- 273
def logged_in?
!current_user.nil?
end
#
def log_out
session.delete(:user_id)
@current_user = nil
end
end
cookie
21
8.10
cookie
cookie
8.37
20
RED
8.4.3.
user.forget
nil
8.38
user.remember
8.38
forget
app/models/user.rb
class User < ActiveRecord::Base
attr_accessor :remember_token
before_save { self.email = email.downcase }
21.
274 -
cookie
<
8.10
8.39
forget
log_out
user.forget
cookie
forget
user_id
remember_token
8.39
app/helpers/sessions_helper.rb
module SessionsHelper
#
def log_in(user)
session[:user_id] = user.id
end
.
.
.
#
8.4.
- 275
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
#
def log_out
forget(current_user)
session.delete(:user_id)
@current_user = nil
end
end
8.4.4.
current_user
8.39
22
Chrome
23
nil
8.38
ID
log_out
8.39
user.forget
Firefox
Firefox
Firefox
current_user
user
nil
#
cookie
def current_user
if (user_id = session[:user_id])
@current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated?(cookies[:remember_token])
log_in user
@current_user = user
end
end
end
user
user
8.33
22.
23.
Niels de Ron
276 -
nil
nil
Chrome
authenticated?
ID
def authenticated?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
remember_digest
nil
BCrypt::Password.new(remember_digest)
authenticated?
8.28
false
8.40
8.40
RED
test/integration/users_login_test.rb
require 'test_helper'
class UsersLoginTest < ActionDispatch::IntegrationTest
.
.
.
test "login with valid information followed by logout" do
get login_path
post login_path, session: { email: @user.email, password: 'password' }
assert is_logged_in?
assert_redirected_to @user
follow_redirect!
assert_template 'users/show'
assert_select "a[href=?]", login_path, count: 0
assert_select "a[href=?]", logout_path
assert_select "a[href=?]", user_path(@user)
delete logout_path
assert_not is_logged_in?
assert_redirected_to root_url
#
delete logout_path
follow_redirect!
assert_select "a[href=?]", login_path
assert_select "a[href=?]", logout_path,
count: 0
assert_select "a[href=?]", user_path(@user), count: 0
end
end
delete logout_path
8.41
RED
8.42
true
log_out
8.42
GREEN
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
.
8.4.
- 277
.
.
def destroy
log_out if logged_in?
redirect_to root_url
end
end
setup
@user
authenticated?
8.43
authenticated?
8.43
RED
test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com",
password: "foobar", password_confirmation: "foobar")
end
.
.
.
test "authenticated? should return false for a user with nil digest" do
assert_not @user.authenticated?('')
end
end
BCrypt::Password.new(nil)
8.44
RED
8.45
authenticated?
authenticated?
GREEN
app/models/user.rb
class User < ActiveRecord::Base
.
.
.
#
true
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
end
278 -
false
8.45
nil
return
if remember_digest.nil?
false
else
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
8.45
8.46
GREEN
8.4.5.
8.11
8.11
8.4.
- 279
8.2
Rails
label
8.47
8.47
app/views/sessions/new.html.erb
<% provide(:title, "Log in") %>
<h1>Log in</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(:session, url: login_path) do |f| %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :remember_me, class: "checkbox inline" do %>
<%= f.check_box :remember_me %>
<span>Remember me on this computer</span>
<% end %>
<%= f.submit "Log in", class: "btn btn-primary" %>
<% end %>
<p>New user? <%= link_to "Sign up now!", signup_path %></p>
</div>
</div>
8.47
on this computer
CSS
checkbox
CSS
app/assets/stylesheets/custom.css.scss
.
.
.
/* forms */
.
.
.
280 -
Bootstrap
CSS
8.12
8.48
inline
Remember me
8.48
.checkbox {
margin-top: -10px;
margin-bottom: 10px;
span {
margin-left: 20px;
font-weight: normal;
}
}
#session_remember_me {
width: auto;
margin-left: 0;
}
8.12
params
params[:session][:remember_me]
'1'
'0'
params
if params[:session][:remember_me] == '1'
remember(user)
else
forget(user)
end
8.4.
- 281
if-then
8.3
24
8.49
cost
8.18
8.49
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_to user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
log_out if logged_in?
redirect_to root_url
end
end
8.3
10
2
remember user
24.
282 -
10
10
11
if boolean?
do_one_thing
else
do_something_else
end
Ruby
Java
if boolean?
var = foo
else
var = bar
end
def foo
do_stuff
boolean? ? "bar" : "baz"
end
foo
Ruby
"bar"
boolean?
"baz"
8.4.6.
8.49
8.20
'0'
'1'
true
post
session
log_in_as
8.4.
- 283
8.20
session
log_in_as
Ruby
defined?
true
defined?
false
post_via_redirect
defined?(post_via_redirect) ...
true
false
integration_test?
if-else
if integration_test?
#
else
#
session
end
log_in_as
8.50
8.50
log_in_as
test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
.
.
.
class ActiveSupport::TestCase
fixtures :all
#
true
def is_logged_in?
!session[:user_id].nil?
end
#
def log_in_as(user, options = {})
password
= options[:password]
|| 'password'
remember_me = options[:remember_me] || '1'
if integration_test?
post login_path, session: { email:
user.email,
password:
password,
remember_me: remember_me }
else
session[:user_id] = user.id
end
end
private
#
true
def integration_test?
defined?(post_via_redirect)
284 -
end
end
log_in_as
8.50
'passowrd'
options
'1'
nil
8.1
8.50
log_in_as(@user, remember_me: '1')
'1'
cookies
remember_token
user
cookie
@user
remember_token
nil
cookie
cookies
cookies[:remember_token]
nil
cookies
cookies['remember_token']
8.51
8.20
users(:michael)
8.19
8.51
GREEN
test/integration/users_login_test.rb
require 'test_helper'
class UsersLoginTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
.
.
.
test "login with remembering" do
log_in_as(@user, remember_me: '1')
assert_not_nil cookies['remember_token']
end
8.4.
- 285
8.52
GREEN
current_user
8.4.2
8.53
8.53
GREEN
app/helpers/sessions_helper.rb
module SessionsHelper
.
.
.
#
cookie
def current_user
if (user_id = session[:user_id])
@current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])
raise
#
user = User.find_by(id: user_id)
if user && user.authenticated?(cookies[:remember_token])
log_in user
@current_user = user
end
end
end
.
.
.
end
8.54
GREEN
8.53
current_user
286 -
10
8.50
rent_user
log_in_as
session[:user_id]
cur-
current_user
$ touch test/helpers/sessions_helper_test.rb
user
1.
2.
remember
3.
current_user
remember
session[:user_id]
8.55
8.55
test/helpers/sessions_helper_test.rb
require 'test_helper'
class SessionsHelperTest < ActionView::TestCase
def setup
@user = users(:michael)
remember(@user)
end
test "current_user returns right user when session is nil" do
assert_equal @user, current_user
assert is_logged_in?
end
test "current_user returns nil when remember digest is wrong" do
@user.update_attribute(:remember_digest, User.digest(User.new_token))
assert_nil current_user
end
end
nil
if
authenticated?
if user && user.authenticated?(cookies[:remember_token])
8.55
8.56
RED
8.57
current_user
authenticated?
8.57
8.55
8.4.
- 287
8.57
GREEN
app/helpers/sessions_helper.rb
module SessionsHelper
.
.
.
#
cookie
def current_user
if (user_id = session[:user_id])
@current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated?(cookies[:remember_token])
log_in user
@current_user = user
end
end
end
.
.
.
end
8.58
GREEN
8.5.
master
$
$
$
$
$
$
$
$
$
288 -
$
$
$
$
heroku maintenance:on
git push heroku
heroku run rake db:migrate
heroku maintenance:off
Heroku
8.5.1.
Rails
cookie
cookie
flash.now
session
ID
cookies
cookie
ID
ID
cookie
if-else
8.6.
3
1.
User
8.32
User.new_token
8.59
8.59
User.digest
8.60
self
8.60
2. 8.4.5
User
self
remember_token
8.51
assigns
assigns
@user
create
assigns(:user)
ate
creuser
cookies
8.61
8.62
FILL_IN
8.6.
- 289
8.59
self
GREEN
app/models/user.rb
class User < ActiveRecord::Base
.
.
.
#
def self.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
#
def self.new_token
SecureRandom.urlsafe_base64
end
.
.
.
end
8.60
GREEN
app/models/user.rb
class User < ActiveRecord::Base
.
.
.
class << self
#
def digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
#
def new_token
SecureRandom.urlsafe_base64
end
end
.
.
.
create
app/controllers/sessions_controller.rb
8.61
290 -
end
def create
?user = User.find_by(email: params[:session][:email].downcase)
if ?user && ?user.authenticate(params[:session][:password])
log_in ?user
params[:session][:remember_me] == '1' ? remember(?user) : forget(?user)
redirect_to ?user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
log_out if logged_in?
redirect_to root_url
end
end
8.62
GREEN
test/integration/users_login_test.rb
require 'test_helper'
class UsersLoginTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
.
.
.
test "login with remembering" do
log_in_as(@user, remember_me: '1')
assert_equal assigns(:user).FILL_IN, FILL_IN
end
test "login without remembering" do
log_in_as(@user, remember_me: '0')
assert_nil cookies['remember_token']
end
.
.
.
end
8.6.
- 291
REST
edit
7.1
update
index
destroy
9.1.
new
7
edit
PATCH
create
3.2
8
POST
update
before filter
updating-users
$ git checkout master
$ git checkout -b updating-users
9.1.1.
9.1
edit
edit
7.1
ID
params[:id]
/users/1/edit
9.1
edit
ID
9.1
edit
app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
@user = User.new
end
def create
@user = User.new(user_params)
1.
http://www.flickr.com/photos/sashawolff/4598355045/
293
if @user.save
log_in @user
flash[:success] = "Welcome to the Sample App!"
redirect_to @user
else
render 'new'
end
end
def edit
@user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
9.1
294 -
9.2
7.13
9.6
9.2
app/views/users/edit.html.erb
<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(@user) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :email %>
<%= f.text_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit "Save changes", class: "btn btn-primary" %>
<% end %>
<div class="gravatar_edit">
<%= gravatar_for @user %>
<a href="http://gravatar.com/emails" target="_blank">change</a>
</div>
</div>
</div>
error_messages
7.3.3
Gravatar
target="_blank"
@user
9.1
Rails
9.2
Name Email
@user
9.1.
- 295
9.2
HTML
9.3
9.3
9.2
HTML
7.1
9.2
Rails
Active Record
2.
296 -
Rails
POST
form_for(@user)
7.13
POST
PATCH
new_record?
Rails
REST
Rails
$ rails console
>> User.new.new_record?
=> true
>> User.first.new_record?
=> false
form_for(@user)
@user.new_record?
true
POST
PATCH
7.1
it_user_path
8.36
ed-
current_user
9.4
9.4
Settings
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
<%= link_to "sample app", root_path, id: "logo" %>
<nav>
<ul class="nav navbar-nav pull-right">
<li><%= link_to "Home", root_path %></li>
<li><%= link_to "Help", help_path %></li>
<% if logged_in? %>
<li><%= link_to "Users", '#' %></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
Account <b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li><%= link_to "Profile", current_user %></li>
<li><%= link_to "Settings", edit_user_path(current_user) %></li>
<li class="divider"></li>
<li>
<%= link_to "Log out", logout_path, method: "delete" %>
</li>
</ul>
</li>
<% else %>
<li><%= link_to "Log in", login_path %></li>
<% end %>
</ul>
</nav>
</div>
</header>
9.1.2.
update
7.3
params
update_attributes
6.1.5
9.5
9.1.
- 297
false
else
create
7.16
update
app/controllers/users_controller.rb
9.5
ter
user_params
7.3.2
9.2
9.3
298 -
strong parame-
9.3
9.1.3.
9.1.2
3.3
9.6
patch
PATCH
get
post
delete
9.6
GREEN
test/integration/users_edit_test.rb
require 'test_helper'
class UsersEditTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
test "unsuccessful edit" do
9.1.
- 299
get edit_user_path(@user)
patch user_path(@user), user: { name: '',
email: 'foo@invalid',
password:
'foo',
password_confirmation: 'bar' }
assert_template 'users/edit'
end
end
9.7
GREEN
9.1.4.
TDD
Gravatar
9.2
9.4
300 -
change
Gravatar
acceptance test
9.4
9.6
9.8
9.8
@user.reload
6.1.5
TDD
9.8
RED
test/integration/users_edit_test.rb
require 'test_helper'
class UsersEditTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
.
.
.
test "successful edit" do
get edit_user_path(@user)
name = "Foo Bar"
email = "foo@bar.com"
patch user_path(@user), user: { name: name,
email: email,
password:
"",
password_confirmation: "" }
assert_not flash.empty?
assert_redirected_to @user
@user.reload
assert_equal @user.name, name
assert_equal @user.email, email
end
end
create
9.8
8.22
update
9.9
update
app/controllers/users_controller.rb
9.9
RED
9.1.
- 301
end
end
.
.
.
end
9.9
6.39
9.8
allow_blank: true
9.10
validates
9.10
GREEN
app/models/user.rb
class User < ActiveRecord::Base
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 }
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }, allow_blank: true
.
.
.
end
6.3
has_secure_password
9.5
9.11
GREEN
302 -
9.5
9.2.
Web
edit
9.1
update
9.2.1
9.6
9.2.2
9.2.
- 303
9.6
9.2.1.
9.6
before_action
logged_in_user
before_action :logged_in_user
logged_in_user
app/controllers/users_controller.rb
9.12
9.12
RED
before_filter
3.
304 -
Rails
:password_confirmation)
end
#
#
def logged_in_user
unless logged_in?
flash[:danger] = "Please log in."
redirect_to login_url
end
end
end
:only
edit
update
/users/1/edit
9.7
9.7
9.12
9.13
RED
update
9.2.
- 305
edit
update
8.4.6
8.50
9.14
9.14
GREEN
test/integration/users_edit_test.rb
require 'test_helper'
class UsersEditTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
test "unsuccessful edit" do
log_in_as(@user)
get edit_user_path(@user)
.
.
.
end
test "successful edit" do
log_in_as(@user)
get edit_user_path(@user)
.
.
.
end
end
setup
9.2.3
setup
9.15
GREEN
9.16
9.16
9.16
GREEN
app/controllers/users_controller.rb
class UsersController < ApplicationController
# before_action :logged_in_user, only: [:edit, :update]
.
.
.
end
306 -
log_in_as
edit
GET
update
7.1
PATCH
get
9.17
edit
update
test/controllers/users_controller_test.rb
patch
9.17
RED
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
def setup
@user = users(:michael)
end
test "should get new" do
get :new
assert_response :success
end
test "should redirect edit when not logged in" do
get :edit, id: @user
assert_redirected_to login_url
end
test "should redirect update when not logged in" do
patch :update, id: @user, user: { name: @user.name, email: @user.email }
assert_redirected_to login_url
end
end
get
patch
Rails
id: @user
user
Rails
2
@user.id
patch
9.18
9.18
GREEN
app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :logged_in_user, only: [:edit, :update]
.
.
9.2.
- 307
.
end
9.19
GREEN
9.2.2.
9.2.1
9.17
9.20
9.20
test/fixtures/users.yml
michael:
name: Michael Example
email: michael@example.com
password_digest: <%= User.digest('password') %>
archer:
name: Sterling Archer
email: duchess@example.gov
password_digest: <%= User.digest('password') %>
8.50
log_in_as
9.21
9.21
RED
test/controllers/users_controller_test.rb
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
def setup
@user
= users(:michael)
@other_user = users(:archer)
end
test "should get new" do
get :new
assert_response :success
end
test "should redirect edit when not logged in" do
308 -
edit
update
9.22
update
correct_user
@user
edit
@user
9.22
edit
update
app/controllers/users_controller.rb
correct_user
GREEN
9.2.
- 309
:password_confirmation)
end
#
#
def logged_in_user
unless logged_in?
flash[:danger] = "Please log in."
redirect_to login_url
end
end
#
def correct_user
@user = User.find(params[:id])
redirect_to(root_url) unless @user == current_user
end
end
9.23
GREEN
9.24
unless current_user?(@user)
9.24 current_user?
app/helpers/sessions_helper.rb
module SessionsHelper
#
def log_in(user)
session[:user_id] = user.id
end
#
def remember(user)
user.remember
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
#
def current_user?(user)
310 -
cor-
true
user == current_user
end
.
.
.
end
9.25
9.25 correct_user
app/controllers/users_controller.rb
GREEN
9.2.
- 311
end
end
9.2.3.
/users/1
/users/1/edit
9.14
9.26
9.26
RED
test/integration/users_edit_test.rb
require 'test_helper'
class UsersEditTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
.
.
.
test "successful edit with friendly forwarding" do
get edit_user_path(@user)
log_in_as(@user)
assert_redirected_to edit_user_path(@user)
name = "Foo Bar"
email = "foo@bar.com"
patch user_path(@user), user: { name: name,
email: email,
password:
"foobar",
password_confirmation: "foobar" }
assert_not flash.empty?
assert_redirected_to @user
@user.reload
assert_equal @user.name, name
assert_equal @user.email, email
end
end
4
store_location
rect_back_or
4.
312 -
9.27
thoughtbot
Clearance gem
redi-
9.27
app/helpers/sessions_helper.rb
module SessionsHelper
.
.
.
#
def redirect_back_or(default)
redirect_to(session[:forwarding_url] || default)
session.delete(:forwarding_url)
end
#
def store_location
session[:forwarding_url] = request.url if request.get?
end
end
session
8.2.1
request
9.27
request.url
store_location
session[:forwarding_url]
POST
GET
store_location
9.28
store_location
app/controllers/users_controller.rb
if request.get?
logged_in_user
GET
PATCH
DELETE
9.28
logged_in_user
5.
Yoel Adler
9.2.
- 313
#
#
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please log in."
redirect_to login_url
end
end
#
def correct_user
@user = User.find(params[:id])
redirect_to(root_url) unless current_user?(@user)
end
end
create
redirect_back_or
9.29
redirect_back_or
||
session[:forwarding_url] || default
session[:forwarding_url]
nil
9.27
return
9.29
create
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
.
.
.
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_back_or user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
.
.
.
end
314 -
9.26
9.30
GREEN
9.3.
index
index
Users
9.8
9.4
9.8
9.3.1.
6.
http://www.flickr.com/photos/glasgows/338937124/
9.3.
- 315
index
index
9.31
index
RED
test/controllers/users_controller_test.rb
9.31
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
def setup
@user
= users(:michael)
@other_user = users(:archer)
end
test "should redirect index when not logged in" do
get :index
assert_redirected_to login_url
end
.
.
.
end
index
logged_in_user
index
app/controllers/users_controller.rb
9.32
9.32
GREEN
2.5
@users
9.33
9.3.3
7. Twitter
316 -
User.all
9.33
index
app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update]
.
.
.
def index
@users = User.all
end
.
.
.
end
li
each
Gravatar
ul
9.34
index
app/views/users/index.html.erb
9.34
9.34
7.7
7.31
Gravatar
7.31
CSS
9.35
SCSS
9.35
CSS
app/assets/stylesheets/custom.css.scss
.
.
.
/* Users index */
.users {
list-style: none;
margin: 0;
li {
overflow: auto;
9.3.
- 317
padding: 10px 0;
border-bottom: 1px solid $gray-lighter;
}
}
users_path
7.1
9.36
9.36
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
<%= link_to "sample app", root_path, id: "logo" %>
<nav>
<ul class="nav navbar-nav pull-right">
<li><%= link_to "Home", root_path %></li>
<li><%= link_to "Help", help_path %></li>
<% if logged_in? %>
<li><%= link_to "Users", users_path %></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
Account <b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li><%= link_to "Profile", current_user %></li>
<li><%= link_to "Settings", edit_user_path(current_user) %></li>
<li class="divider"></li>
<li>
<%= link_to "Log out", logout_path, method: "delete" %>
</li>
</ul>
</li>
<% else %>
<li><%= link_to "Log in", login_path %></li>
<% end %>
</ul>
</nav>
</div>
</header>
9.37
GREEN
9.9
318 -
9.9
9.3.2.
Ruby
Gemfile
9.38
Gemfile
faker gem
Rake
9.38
gem
faker
source 'https://rubygems.org'
gem 'rails',
gem 'bcrypt',
gem 'faker',
.
.
.
'4.2.0'
'3.1.7'
'1.4.2'
$ bundle install
Rake
9.39
Rails
db/seeds.rb
9.3.
- 319
9.39
Rake
db/seeds.rb
User.create!(name: "Example User",
email: "example@railstutorial.org",
password:
"foobar",
password_confirmation: "foobar")
99.times do |n|
name = Faker::Name.name
email = "example-#{n+1}@railstutorial.org"
password = "password"
User.create!(name: name,
email: email,
password:
password,
password_confirmation: password)
end
9.39
create!
99
create
false
db:seed
Rake
db:seed Rake
100
9.10
Gravatar
rake db:reset
8.
320 -
Rails
9.10
100
9.3.3.
100
30
Rails
will_paginate
will_paginate
will_paginate
9.40
bootstrap-will_paginate
Gemfile
gem
Gemfile
Bootstrap
bootstrap-will_paginate
9.40
will_paginate
source 'https://rubygems.org'
gem
gem
gem
gem
gem
.
.
.
'rails',
'bcrypt',
'faker',
'will_paginate',
'bootstrap-will_paginate',
'4.2.0'
'3.1.7'
'1.4.2'
'3.0.7'
'0.0.10'
$ bundle install
9.3.
- 321
Web
gem
index
index
Rails
User.all
will_paginate
9.41
9.41
index
app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>
<%= will_paginate %>
<ul class="users">
<% @users.each do |user| %>
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
</li>
<% end %>
</ul>
<%= will_paginate %>
will_paginate
@users
@users
9.41
will_paginate
9.33
User.all
paginate
$ rails console
>> User.paginate(page: 1)
User Load (1.5ms) SELECT "users".* FROM "users" LIMIT 30 OFFSET 0
(1.7ms) SELECT COUNT(*) FROM "users"
=> #<ActiveRecord::Relation [#<User id: 1,...
paginate
:page
User.paginate
30
:page
index
nate
:page
nil
all
1-30
paginate
params[:page]
pagi-
9.42
params
index
app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update]
.
.
.
def index
@users = User.paginate(page: params[:page])
end
.
9
31-60
paginate
9.42
322 -
:page
will_pagenate
.
.
end
9.11
Rails
will_paginate
9.11
2
Next
9.12
9.3.
- 323
9.12
9.3.4.
30
9.20
30
password_digest
30
Ruby
9.43
9.43
30
test/fixtures/users.yml
michael:
name: Michael Example
email: michael@example.com
password_digest: <%= User.digest('password') %>
archer:
name: Sterling Archer
email: duchess@example.gov
password_digest: <%= User.digest('password') %>
lana:
name: Lana Kane
324 -
9.43
email: hands@example.gov
password_digest: <%= User.digest('password') %>
mallory:
name: Mallory Archer
email: boss@example.gov
password_digest: <%= User.digest('password') %>
<% 30.times do |n| %>
user_<%= n %>:
name: <%= "User #{n}" %>
email: <%= "user-#{n}@example.com" %>
password_digest: <%= User.digest('password') %>
<% end %>
9.44
9.44
GREEN
test/integration/users_index_test.rb
require 'test_helper'
class UsersIndexTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
test "index including pagination" do
log_in_as(@user)
get users_path
assert_template 'users/index'
assert_select 'div.pagination'
User.paginate(page: 1).each do |user|
assert_select 'a[href=?]', user_path(user), text: user.name
end
end
end
9.45
GREEN
9.3.
- 325
9.3.5.
Rails
9.41
li
render
9.46
9.46
app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>
<%= will_paginate %>
<ul class="users">
<% @users.each do |user| %>
<%= render user %>
<% end %>
</ul>
<%= will_paginate %>
render
User
user
_user.html.erb
Rails
9.47
9.47
app/views/users/_user.html.erb
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
</li>
@users
9.48
render
9.48
GREEN
app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>
<%= will_paginate %>
<ul class="users">
<%= render @users %>
</ul>
user
User
9.
326 -
@users.each do |foobar|
render foobar
Rails
@users
User
render
_user.html.erb
9.49
Rails
9.48
GREEN
9.4.
REST
9.13
destroy
destroy
9.13
9.4.
- 327
9.4.1.
admin
admin
admin?
Active Record
admin
9.14
9.14
admin
admin
boolean
admin
9.50
9.50
default: false
admin
nil
Rails
9.50
admin
db/migrate/[timestamp]_add_admin_to_users.rb
class AddAdminToUsers < ActiveRecord::Migration
def change
add_column :users, :admin, :boolean, default: false
end
end
Rails
$ rails console --sandbox
>> user = User.first
>> user.admin?
=> false
>> user.toggle!(:admin)
=> true
>> user.admin?
=> true
328 -
add_column
default: false
admin
admin?
toggle!
admin
false
true
9.51
9.51
db/seeds.rb
User.create!(name: "Example User",
email: "example@railstutorial.org",
password:
"foobar",
password_confirmation: "foobar",
admin: true)
99.times do |n|
name = Faker::Name.name
email = "example-#{n+1}@railstutorial.org"
password = "password"
User.create!(name: name,
email: email,
password:
password,
password_confirmation: password)
end
admin: true
9.51
10
PATCH
patch /users/17?admin=1
17
7.3.2
params
require
permit
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
admin
:admin
10. curl
PATCH
9.4.
- 329
9.4.2. destroy
destroy
9.52
9.52
app/views/users/_user.html.erb
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
<% if current_user.admin? && !current_user?(user) %>
| <%= link_to "delete", user, method: :delete,
data: { confirm: "You sure?" } %>
<% end %>
</li>
method: delete
DELETE
if
9.15
9.15
DELETE
DELETE
330 -
Rails
JavaScript
JavaScript
JavaScript
JavaScript
POST
11
destroy
Active Record
7.1
destroy
destroy
9.53
logged_in_user
9.53
:destroy
9.53
destroy
app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
before_action :correct_user,
only: [:edit, :update]
.
.
.
def destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted"
redirect_to users_url
end
.
.
.
end
destroy
find
destroy
User.find(params[:id]).destroy
DELETE
destroy
9.2.1
9.2.2
destroy
admin_user
9.54
destroy
9.54
app/controllers/users_controller.rb
11.
RailsCasts
9.4.
- 331
end
end
9.4.3.
9.55
9.55
test/fixtures/users.yml
michael:
name: Michael Example
email: michael@example.com
password_digest: <%= User.digest('password') %>
admin: true
archer:
name: Sterling Archer
email: duchess@example.gov
password_digest: <%= User.digest('password') %>
lana:
name: Lana Kane
email: hands@example.gov
password_digest: <%= User.digest('password') %>
mallory:
name: Mallory Archer
email: boss@example.gov
password_digest: <%= User.digest('password') %>
<% 30.times do |n| %>
user_<%= n %>:
name: <%= "User #{n}" %>
email: <%= "user-#{n}@example.com" %>
password_digest: <%= User.digest('password') %>
<% end %>
9.2.1
8.28
delete
destroy
DELETE
9.56
9.56
GREEN
test/controllers/users_controller_test.rb
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
def setup
@user
= users(:michael)
@other_user = users(:archer)
332 -
end
.
.
.
test "should redirect destroy when not logged in" do
assert_no_difference 'User.count' do
delete :destroy, id: @user
end
assert_redirected_to login_url
end
test "should redirect destroy when logged in as a non-admin" do
log_in_as(@other_user)
assert_no_difference 'User.count' do
delete :destroy, id: @user
end
assert_redirected_to root_url
end
end
assert_no_difference
9.56
7.21
9.56
9.44
assert_difference 'User.count', -1 do
delete user_path(@other_user)
end
assert_difference
7.26
DELETE
User.count
-1
9.57
9.57
GREEN
test/integration/users_index_test.rb
require 'test_helper'
class UsersIndexTest < ActionDispatch::IntegrationTest
def setup
@admin
= users(:michael)
@non_admin = users(:archer)
end
test "index as admin including pagination and delete links" do
log_in_as(@admin)
get users_path
9.4.
- 333
assert_template 'users/index'
assert_select 'div.pagination'
first_page_of_users = User.paginate(page: 1)
first_page_of_users.each do |user|
assert_select 'a[href=?]', user_path(user), text: user.name
unless user == @admin
assert_select 'a[href=?]', user_path(user), text: 'delete',
method: :delete
end
end
assert_difference 'User.count', -1 do
delete user_path(@non_admin)
end
end
test "index as non-admin" do
log_in_as(@non_admin)
get users_path
assert_select 'a', text: 'delete', count: 0
end
end
9.57
9.52
9.58
GREEN
9.5.
5.4
10
master
$
$
$
$
$
git
git
git
git
git
add -A
commit -m "Finish user edit, update, index, and destroy actions"
checkout master
merge updating-users
push
pg:reset
334 -
9.16
9.11
11.1.4
9.16
9.5.1.
update
Web
PATCH
HTTP
Rails
render @users
db/seeds.rb
rake db:seed
_user.html.erb
9.5.
- 335
admin
user.admin?
destroy
Ruby
9.6.
3
1.
session[:forwarding_url]
9.26
2.
log_in_as
5.25
3.
update
9.59
PATCH
admin
4.
new.html.erb
9.60
f
admin
user_params
edit.html.erb
9.61
9.59
admin
test/controllers/users_controller_test.rb
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
def setup
@user
= users(:michael)
@other_user = users(:archer)
end
.
.
.
test "should redirect update when logged in as wrong user" do
log_in_as(@other_user)
patch :update, id: @user, user: { name: @user.name, email: @user.email }
assert_redirected_to root_url
end
test "should not allow the admin attribute to be edited via the web" do
log_in_as(@other_user)
assert_not @other_user.admin?
patch :update, id: @other_user, user: { password:
FILL_IN,
password_confirmation: FILL_IN,
admin: FILL_IN }
assert_not @other_user.FILL_IN.admin?
end
.
336 -
DELETE
.
.
end
9.60 new
edit
app/views/users/_fields.html.erb
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :email %>
<%= f.text_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
9.61
app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(@user) do |f| %>
<%= render 'fields', f: f %>
<%= f.submit "Create my account", class: "btn btn-primary" %>
<% end %>
</div>
</div>
9.6.
- 337
10
7.1
REST
Rails
10.1.
7
8.2
1.
8.4
2.
3.
2
4.
5.
User.digest
User.new_token
user.authenticated?
10.1
10.1.3
10.2
authenticated?
10.1
password
password_digest
authenticate(password)
id
remember_token
remember_digest
authenticated?(:remember, token)
activation_token
activation_digest
authenticated?(:activation, token)
1.
2.
9.1
ID
URL
ID
ID
339
reset_token
reset_digest
authenticated?(:reset, token)
10.3
master
10.1.1.
8.1
REST URL
3
edit
URL
edit_account_activation_url(activation_token, ...)
edit
resources
10.1
10.1
config/routes.rb
Rails.application.routes.draw do
root
'static_pages#home'
get
'help'
=> 'static_pages#help'
get
'about'
=> 'static_pages#about'
get
'contact' => 'static_pages#contact'
get
'signup' => 'users#new'
get
'login'
=> 'sessions#new'
post
'login'
=> 'sessions#create'
delete 'logout' => 'sessions#destroy'
resources :users
resources :account_activations, only: [:edit]
end
10.2
5
3.
update
update
5.
340 -
GET
PATCH
edit
4.
10
8.4
edit
user.activation_token
user.authenticated?(:activation, token)
authenticated?
8.33
activated
9.4.1
if user.activated? ...
10.1
10.1
9.50
activated
false
10.2
10.2
db/migrate/[timestamp]_add_activation_to_users.rb
class AddActivationToUsers < ActiveRecord::Migration
def change
add_column :users, :activation_digest, :string
add_column :users, :activated, :boolean, default: false
add_column :users, :activated_at, :datetime
end
end
10.1.
- 341
be-
6.2.5
fore_save
downcase
before_save
6.31
before_create
before_create :create_activation_digest
create_activation_digest
Rails
before_save
6.31
cre-
ate_activation_digest
7.3.2
Ruby
private
private
def create_activation_digest
#
end
private
$ rails console
>> User.first.create_activation_digest
NoMethodError: private method `create_activation_digest' called for #<User>
before_create
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
8.32
remember
#
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
remember
update_attribute
before_create
User.new
activation_token
7.17
activation_digest
10.1
at-
10.3
tr_accessor
10.3
GREEN
app/models/user.rb
class User < ActiveRecord::Base
attr_accessor :remember_token, :activation_token
before_save
:downcase_email
342 -
10
activation_digest
before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }
.
.
.
private
#
def downcase_email
self.email = email.downcase
end
#
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
10.4
Time.zone.now
10.5
Rails
10.4
db/seeds.rb
User.create!(name: "Example User",
email: "example@railstutorial.org",
password:
"foobar",
password_confirmation: "foobar",
admin:
true,
activated: true,
activated_at: Time.zone.now)
99.times do |n|
name = Faker::Name.name
email = "example-#{n+1}@railstutorial.org"
password = "password"
User.create!(name: name,
email: email,
password:
password,
password_confirmation: password,
activated: true,
activated_at: Time.zone.now)
end
10.5
test/fixtures/users.yml
michael:
name: Michael Example
email: michael@example.com
password_digest: <%= User.digest('password') %>
admin: true
10.1.
- 343
activated: true
activated_at: <%= Time.zone.now %>
archer:
name: Sterling Archer
email: duchess@example.gov
password_digest: <%= User.digest('password') %>
activated: true
activated_at: <%= Time.zone.now %>
lana:
name: Lana Kane
email: hands@example.gov
password_digest: <%= User.digest('password') %>
activated: true
activated_at: <%= Time.zone.now %>
mallory:
name: Mallory Archer
email: boss@example.gov
password_digest: <%= User.digest('password') %>
activated: true
activated_at: <%= Time.zone.now %>
<% 30.times do |n| %>
user_<%= n %>:
name: <%= "User #{n}" %>
email: <%= "user-#{n}@example.com" %>
password_digest: <%= User.digest('password') %>
activated: true
activated_at: <%= Time.zone.now %>
<% end %>
10.4
$ bundle exec rake db:migrate:reset
$ bundle exec rake db:seed
10.1.2.
Action Mailer
create
rails generate
$ rails generate mailer UserMailer account_activation password_reset
account_activation
10.2
Rails
HTML
10.6
344 -
10
password_reset
10.7
10.6
app/views/user_mailer/account_activation.text.erb
UserMailer#account_activation
<%= @greeting %>, find me in app/views/user_mailer/account_activation.text.erb
10.7
HTML
app/views/user_mailer/account_activation.html.erb
<h1>UserMailer#account_activation</h1>
<p>
<%= @greeting %>, find me in app/views/user_mailer/account_activation.html.erb
</p>
10.8
form
@greeting
UserMailer
app/mailers/user_mailer.rb
10.8
user.email
10.9
subject
10.9
app/mailers/user_mailer.rb
10.1.
- 345
Ruby
10.1
edit_account_activation_url(@user.activation_token, ...)
edit_user_url(user)
http://www.example.com/users/1/edit
http://www.example.com/account_activations/q5lt38hQDc_959PVoo6b7A/edit
q5lt38hQDc_959PVoo6b7A
URL
new_token
/users/1/edit
8.31
base64
AccountActivationsController
ID
params[:id]
query parameter
URL
account_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.com
%40
URL
Rails
Rails
params[:email]
@user
10.11
6. URL
it?name=Foo%20Bar&email=foo%40example.com
346 -
10
edit
10.9
10.10
10.11
&
/ed-
Ruby
link_to
edit
10.10
app/views/user_mailer/account_activation.text.erb
Hi <%= @user.name %>,
Welcome to the Sample App! Click on the link below to activate your account:
<%= edit_account_activation_url(@user.activation_token, email: @user.email) %>
10.11
HTML
app/views/user_mailer/account_activation.html.erb
<h1>Sample App</h1>
<p>Hi <%= @user.name %>,</p>
<p>
Welcome to the Sample App! Click on the link below to activate your account:
</p>
<%= link_to "Activate", edit_account_activation_url(@user.activation_token,
email: @user.email) %>
Rails
10.12
URL
10.12
config/environments/development.rb
Rails.application.configure do
.
.
.
config.action_mailer.raise_delivery_errors = true
config.action_mailer.delivery_method = :test
host = 'example.com'
config.action_mailer.default_url_options = { host: host }
.
.
.
end
10.12
'example.com'
IDE
host = 'rails-tutorial-c9-mhartl.c9.io'
host = 'localhost:3000'
#
#
IDE
10.12
10.13
10.13
test/mailers/previews/user_mailer_preview.rb
10.1.
- 347
10.9
account_activation
10.13
user
UserMailer.account_activation
user.activation_token
10.10
10.14
acti-
10.11
vation_token
10.14
test/mailers/previews/user_mailer_preview.rb
# Preview all emails at http://localhost:3000/rails/mailers/user_mailer
class UserMailerPreview < ActionMailer::Preview
# Preview this email at
# http://localhost:3000/rails/mailers/user_mailer/account_activation
def account_activation
user = User.first
user.activation_token = User.new_token
UserMailer.account_activation(user)
end
# Preview this email at
# http://localhost:3000/rails/mailers/user_mailer/password_reset
def password_reset
UserMailer.password_reset
end
end
URL
calhost:3000
348 -
10
URL
HTML
IDE
10.2
10.3
lo-
10.2
HTML
10.3
10.1.
- 349
Rails
10.15
UserMailer
test/mailers/user_mailer_test.rb
10.15
Rails
require 'test_helper'
class UserMailerTest < ActionMailer::TestCase
test "account_activation" do
mail = UserMailer.account_activation
assert_equal "Account activation", mail.subject
assert_equal ["to@example.org"], mail.to
assert_equal ["from@example.com"], mail.from
assert_match "Hi", mail.body.encoded
end
test "password_reset" do
mail = UserMailer.password_reset
assert_equal "Password reset", mail.subject
assert_equal ["to@example.org"], mail.to
assert_equal ["from@example.com"], mail.from
assert_match "Hi", mail.body.encoded
end
end
assert_match
10.15
assert_match
assert_match
assert_match
assert_match
10.16
'foo',
'baz',
/\w+/,
/\w+/,
'foobar'
'foobar'
'foobar'
'$#!*+@'
#
#
#
#
true
false
true
false
assert_match
CGI::escape(user.email)
10.16
RED
test/mailers/user_mailer_test.rb
require 'test_helper'
class UserMailerTest < ActionMailer::TestCase
test "account_activation" do
user = users(:michael)
user.activation_token = User.new_token
mail = UserMailer.account_activation(user)
assert_equal "Account activation", mail.subject
assert_equal [user.email], mail.to
7.
CGI.escape(str)
350 -
10
URI::encode(str)
assert_equal
assert_match
assert_match
assert_match
end
end
["noreply@example.com"],
user.name,
user.activation_token,
CGI::escape(user.email),
mail.from
mail.body.encoded
mail.body.encoded
mail.body.encoded
10.16
10.17
10.17
config/environments/test.rb
Rails.application.configure do
.
.
.
config.action_mailer.delivery_method = :test
config.action_mailer.default_url_options = { host: 'example.com' }
.
.
.
end
10.18
GREEN
10.19
10.19
10.19
7.4
RED
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params)
if @user.save
UserMailer.account_activation(@user).deliver_now
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
end
.
.
10.1.
- 351
.
end
10.20
10.1.4
10.20
GREEN
test/integration/users_signup_test.rb
require 'test_helper'
class UsersSignupTest < ActionDispatch::IntegrationTest
test "invalid signup information" do
get signup_path
assert_no_difference 'User.count' do
post users_path, user: { name: "",
email: "user@invalid",
password:
"foo",
password_confirmation: "bar" }
end
assert_template 'users/new'
assert_select 'div#error_explanation'
assert_select 'div.field_with_errors'
end
test "valid signup information" do
get signup_path
assert_difference 'User.count', 1 do
post_via_redirect users_path, user: { name: "Example User",
email: "user@example.com",
password:
"password",
password_confirmation: "password" }
end
# assert_template 'users/show'
# assert is_logged_in?
end
end
10.4
10.21
10.3
10.21
Sent mail to michael@michaelhartl.com (931.6ms)
Date: Wed, 03 Sep 2014 19:47:18 +0000
From: noreply@example.com
To: michael@michaelhartl.com
Message-ID: <540770474e16_61d3fd1914f4cd0300a0@mhartl-rails-tutorial-953753.mail>
Subject: Account activation
Mime-Version: 1.0
352 -
10
Content-Type: multipart/alternative;
boundary="--==_mimepart_5407704656b50_61d3fd1914f4cd02996a";
charset=UTF-8
Content-Transfer-Encoding: 7bit
----==_mimepart_5407704656b50_61d3fd1914f4cd02996a
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: 7bit
Hi Michael Hartl,
Welcome to the Sample App! Click on the link below to activate your account:
http://rails-tutorial-c9-mhartl.c9.io/account_activations/
fFb_F94mgQtmlSvRFGsITw/edit?email=michael%40michaelhartl.com
----==_mimepart_5407704656b50_61d3fd1914f4cd02996a
Content-Type: text/html;
charset=UTF-8
Content-Transfer-Encoding: 7bit
<h1>Sample App</h1>
<p>Hi Michael Hartl,</p>
<p>
Welcome to the Sample App! Click on the link below to activate your account:
</p>
<a href="http://rails-tutorial-c9-mhartl.c9.io/account_activations/
fFb_F94mgQtmlSvRFGsITw/edit?email=michael%40michaelhartl.com">Activate</a>
----==_mimepart_5407704656b50_61d3fd1914f4cd02996a--
10.1.
- 353
10.4
10.1.3.
AccountActivationsController
10.21
edit
10.1.2
8.5
params[:id]
8.36
authenticated?
authenticated?
8.33
#
true
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
remember_digest
self.remember_digest
354 -
10
params[:email]
self.activation_token
authenticated?
Rails
metaprogramming
Ruby
send
Ruby
send
$ rails console
>> a = [1, 2, 3]
>> a.length
=> 3
>> a.send(:length)
=> 3
>> a.send('length')
=> 3
:length
'length'
send
length
activation_digest
>>
>>
=>
>>
=>
>>
=>
>>
>>
=>
user = User.first
user.activation_digest
"$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
user.send(:activation_digest)
"$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
user.send('activation_digest')
"$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
attribute = :activation
user.send("#{attribute}_digest")
"$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
attribute
send
attribute
"#{attribute}_digest"
send
:activation
'activation'
"activation_digest"
7.4.2
authenticated?
def authenticated?(remember_token)
digest = self.send('remember_digest')
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(remember_token)
end
10.1.
- 355
token
self
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
authenticated?
user.authenticated?(:remember, remember_token)
authenticated?
10.22
authenticated?
10.22
RED
app/models/user.rb
class User < ActiveRecord::Base
.
.
.
#
true
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
.
.
.
end
10.22
10.23
RED
nil
8.36
8.43
authenticated?
au-
thenticated?
10.24
current_user
app/helpers/sessions_helper.rb
authenticated?
10.24
module SessionsHelper
.
.
.
#
def current_user
if (user_id = session[:user_id])
@current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])
356 -
10
10.25
GREEN
10.25
UserTest
test/models/user_test.rb
authenticated?
GREEN
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com",
password: "foobar", password_confirmation: "foobar")
end
.
.
.
test "authenticated? should return false for a user with nil digest" do
assert_not @user.authenticated?(:remember, '')
end
end
10.26
GREEN
8.4.2
10.22
authenticated?
8.4.6
edit
params
activated_at
user.update_attribute(:activated,
true)
user.update_attribute(:activated_at, Time.zone.now)
edit
10.27
10.27
10.1.
- 357
10.27
edit
app/controllers/account_activations_controller.rb
class AccountActivationsController < ApplicationController
def edit
user = User.find_by(email: params[:email])
if user && !user.activated? && user.authenticated?(:activation, params[:id])
user.update_attribute(:activated,
true)
user.update_attribute(:activated_at, Time.zone.now)
log_in user
flash[:success] = "Account activated!"
redirect_to user
else
flash[:danger] = "Invalid activation link"
redirect_to root_url
end
end
end
10.21
http://rails-tutorial-c9-mhartl.c9.io/account_activations/
fFb_F94mgQtmlSvRFGsITw/edit?email=michael%40michaelhartl.com
10.5
358 -
10
10.5
user.activated?
10.6
true
10.28
10.28
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
if user.activated?
log_in user
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_back_or user
else
message = "Account not activated. "
message += "Check your email for the activation link."
flash[:warning] = message
redirect_to root_url
end
else
10.1.
- 359
10.6
10.1.4
10.1.4.
7.4.4
7.26
10.29
10.29
test/integration/users_signup_test.rb
require 'test_helper'
class UsersSignupTest < ActionDispatch::IntegrationTest
360 -
10
GREEN
def setup
ActionMailer::Base.deliveries.clear
end
test "invalid signup information" do
get signup_path
assert_no_difference 'User.count' do
post users_path, user: { name: "",
email: "user@invalid",
password:
"foo",
password_confirmation: "bar" }
end
assert_template 'users/new'
assert_select 'div#error_explanation'
assert_select 'div.field_with_errors'
end
test "valid signup information with account activation" do
get signup_path
assert_difference 'User.count', 1 do
post users_path, user: { name: "Example User",
email: "user@example.com",
password:
"password",
password_confirmation: "password" }
end
assert_equal 1, ActionMailer::Base.deliveries.size
user = assigns(:user)
assert_not user.activated?
#
log_in_as(user)
assert_not is_logged_in?
#
get edit_account_activation_path("invalid token")
assert_not is_logged_in?
#
get edit_account_activation_path(user.activation_token, email: 'wrong')
assert_not is_logged_in?
#
get edit_account_activation_path(user.activation_token, email: user.email)
assert user.reload.activated?
follow_redirect!
assert_template 'users/show'
assert is_logged_in?
end
end
assert_equal 1, ActionMailer::Base.deliveries.size
deliveries
10.2.5
setup
10.29
10.1.
- 361
assigns
assigns
create
@user
10.29
10.30
assigns(:user)
10.20
GREEN
10.29
activate
send_activation_email
10.32
10.31
10.33
10.31
app/models/user.rb
class User < ActiveRecord::Base
.
.
.
#
def activate
update_attribute(:activated,
true)
update_attribute(:activated_at, Time.zone.now)
end
#
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
private
.
.
.
end
10.32
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params)
if @user.save
@user.send_activation_email
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
362 -
10
end
end
.
.
.
end
10.33
app/controllers/account_activations_controller.rb
class AccountActivationsController < ApplicationController
def edit
user = User.find_by(email: params[:email])
if user && !user.activated? && user.authenticated?(:activation, params[:id])
user.activate
log_in user
flash[:success] = "Account activated!"
redirect_to user
else
flash[:danger] = "Invalid activation link"
redirect_to root_url
end
end
end
user
10.31
-user.update_attribute(:activated,
true)
-user.update_attribute(:activated_at, Time.zone.now)
+update_attribute(:activated,
true)
+update_attribute(:activated_at, Time.zone.now)
user
self
6.2.5
self
UserMailer
@user
self
-UserMailer.account_activation(@user).deliver_now
+UserMailer.account_activation(self).deliver_now
10.34
GREEN
$ git add -A
$ git commit -m "Add account activations"
10.1.
- 363
10.2.
10.1
Forgot Password
10.7
10.7
Forgot Password
Forgot Password
10.8
364 -
10
10.8
Forgot Password
10.9
10.2.
- 365
10.9
1.
2.
3.
4.
5.
10.2.1.
10.1.1
$ rails generate controller PasswordResets new edit --no-test-framework
10.1.4
366 -
10
new
create
edit
10.35
10.8
update
10.9
resources
10.35
config/routes.rb
Rails.application.routes.draw do
root
'static_pages#home'
get
'help'
=> 'static_pages#help'
get
'about'
=> 'static_pages#about'
get
'contact' => 'static_pages#contact'
get
'signup' => 'users#new'
get
'login'
=> 'sessions#new'
post
'login'
=> 'sessions#create'
delete 'logout' => 'sessions#destroy'
resources :users
resources :account_activations, only: [:edit]
resources :password_resets,
only: [:new, :create, :edit, :update]
end
10.2
10.2
REST
REST
HTTP
URL
GET
/password_resets/new
new
new_password_reset_path
POST
/password_resets
create
password_resets_path
GET
/password_resets/<token>/edit
edit
edit_password_reset_path(token)
PATCH
/password_resets/<token>
update
password_reset_path(token)
Forgot Password
new_password_reset_path
10.36
10.10
10.36
app/views/sessions/new.html.erb
<% provide(:title, "Log in") %>
<h1>Log in</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(:session, url: login_path) do |f| %>
<%= f.label :email %>
<%= f.text_field :email, class: 'form-control' %>
<%= f.label :password %>
10.2.
- 367
10.10
Forgot Password
10.1
reset_digest
368 -
10
reset_sent_at
10.11
8.4
10.1
10.11
10.2.2.
8.2
10.37
10.37
app/views/sessions/new.html.erb
<% provide(:title, "Log in") %>
<h1>Log in</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(:session, url: login_path) do |f| %>
<%= f.label :email %>
<%= f.text_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :remember_me, class: "checkbox inline" do %>
10.2.
- 369
10.37
10.38
10.38
app/views/password_resets/new.html.erb
<% provide(:title, "Forgot password") %>
<h1>Forgot password</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(:password_reset, url: password_resets_path) do |f| %>
<%= f.label :email %>
<%= f.text_field :email, class: 'form-control' %>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
</div>
</div>
370 -
10
10.12
10.12
Forgot Password
reset_token
10.12
reset_sent_at
reset_digest
8.9
flash.now
create
10.39
PasswordResetsController
create
app/controllers/password_resets_controller.rb
10.39
10.2.
- 371
def edit
end
end
create_reset_digest
10.40
10.40
app/models/user.rb
class User < ActiveRecord::Base
attr_accessor :remember_token, :activation_token, :reset_token
before_save
:downcase_email
before_create :create_activation_digest
.
.
.
#
def activate
update_attribute(:activated,
true)
update_attribute(:activated_at, Time.zone.now)
end
#
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
#
def create_reset_digest
self.reset_token = User.new_token
update_attribute(:reset_digest, User.digest(reset_token))
update_attribute(:reset_sent_at, Time.zone.now)
end
#
def send_password_reset_email
UserMailer.password_reset(self).deliver_now
end
private
#
def downcase_email
self.email = email.downcase
end
#
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
372 -
10
10.13
10.2.3
10.13
Forgot Password
10.2.3.
10.40
UserMailer.password_reset(self).deliver_now
UserMailer
10.1.2
password_reset
10.41
10.42
HTML
10.43
10.41
app/mailers/user_mailer.rb
class UserMailer < ActionMailer::Base
default from: "noreply@example.com"
def account_activation(user)
@user = user
mail to: user.email, subject: "Account activation"
end
def password_reset(user)
@user = user
10.2.
- 373
10.42
app/views/user_mailer/password_reset.text.erb
To reset your password click the link below:
<%= edit_password_reset_url(@user.reset_token, email: @user.email) %>
This link will expire in two hours.
If you did not request your password to be reset, please ignore this email and
your password will stay as it is.
10.43
HTML
app/views/user_mailer/password_reset.html.erb
<h1>Password reset</h1>
<p>To reset your password click the link below:</p>
<%= link_to "Reset password",
edit_password_reset_url(@user.reset_token,
email: @user.email) %>
<p>This link will expire in two hours.</p>
<p>
If you did not request your password to be reset, please ignore this email and
your password will stay as it is.
</p>
10.1.2
10.14
Rails
10.44
10.44
test/mailers/previews/user_mailer_preview.rb
# Preview all emails at http://localhost:3000/rails/mailers/user_mailer
class UserMailerPreview < ActionMailer::Preview
# Preview this email at
# http://localhost:3000/rails/mailers/user_mailer/account_activation
def account_activation
user = User.first
user.activation_token = User.new_token
UserMailer.account_activation(user)
end
# Preview this email at
# http://localhost:3000/rails/mailers/user_mailer/password_reset
def password_reset
374 -
10
user = User.first
user.reset_token = User.new_token
UserMailer.password_reset(user)
end
end
HTML
10.14
10.14
10.15
HTML
10.2.
- 375
10.15
10.16
10.45
before_create
10.3
Forgot Password
10.52
10.45
GREEN
test/mailers/user_mailer_test.rb
require 'test_helper'
class UserMailerTest < ActionMailer::TestCase
test "account_activation" do
user = users(:michael)
user.activation_token = User.new_token
mail = UserMailer.account_activation(user)
assert_equal "Account activation", mail.subject
assert_equal [user.email], mail.to
assert_equal ["noreply@example.com"], mail.from
assert_match user.name,
mail.body.encoded
assert_match user.activation_token,
mail.body.encoded
assert_match CGI::escape(user.email), mail.body.encoded
end
test "password_reset" do
376 -
10
user = users(:michael)
user.reset_token = User.new_token
mail = UserMailer.password_reset(user)
assert_equal "Password reset", mail.subject
assert_equal [user.email], mail.to
assert_equal ["noreply@example.com"], mail.from
assert_match user.reset_token,
mail.body.encoded
assert_match CGI::escape(user.email), mail.body.encoded
end
end
10.46
GREEN
10.41
10.42
10.43
10.47
10.16
10.16
10.47
Sent mail to michael@michaelhartl.com (66.8ms)
Date: Thu, 04 Sep 2014 01:04:59 +0000
From: noreply@example.com
To: michael@michaelhartl.com
Message-ID: <5407babbee139_8722b257d04576a@mhartl-rails-tutorial-953753.mail>
10.2.
- 377
----==_mimepart_5407babbe3505_8722b257d045617
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: 7bit
To reset your password click the link below:
http://rails-tutorial-c9-mhartl.c9.io/password_resets/3BdBrXeQZSWqFIDRN8cxHA/
edit?email=michael%40michaelhartl.com
This link will expire in two hours.
If you did not request your password to be reset, please ignore this email and
your password will stay as it is.
----==_mimepart_5407babbe3505_8722b257d045617
Content-Type: text/html;
charset=UTF-8
Content-Transfer-Encoding: 7bit
<h1>Password reset</h1>
<p>To reset your password click the link below:</p>
<a href="http://rails-tutorial-c9-mhartl.c9.io/
password_resets/3BdBrXeQZSWqFIDRN8cxHA/
edit?email=michael%40michaelhartl.com">Reset password</a>
<p>This link will expire in two hours.</p>
<p>
If you did not request your password to be reset, please ignore this email and
your password will stay as it is.
</p>
----==_mimepart_5407babbe3505_8722b257d045617--
10.2.4.
http://example.com/password_resets/3BdBrXeQZSWqFIDRN8cxHA/edit?email=foo%40bar.com
9.2
edit
edit
378 -
10
update
update
10.48
10.48
app/views/password_resets/edit.html.erb
<% provide(:title, 'Reset password') %>
<h1>Reset password</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(@user, url: password_reset_path(params[:id])) do |f| %>
<%= render 'shared/error_messages' %>
<%= hidden_field_tag :email, @user.email %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit "Update password", class: "btn btn-primary" %>
<% end %>
</div>
</div>
10.48
hidden_field_tag :email, @user.email
edit
@user
params[:email]
10.27
10.22
authenticated?
params[:id]
edit
@user
update
10.49
10.49
edit
app/controllers/password_resets_controller.rb
class PasswordResetsController < ApplicationController
before_action :get_user,
only: [:edit, :update]
before_action :valid_user, only: [:edit, :update]
.
.
.
def edit
10.2.
- 379
end
private
def get_user
@user = User.find_by(email: params[:email])
end
#
def valid_user
unless (@user && @user.activated? &&
@user.authenticated?(:reset, params[:id]))
redirect_to root_url
end
end
end
10.49
authenticated?(:reset, params[:id])
cookies[:remember_token])
10.27
10.24
authenticated?(:remember,
authenticated?(:activation, params[:id])
authenticated?
10.1
10.47
10.17
10.17
edit
update
10.50
if
380 -
Active Record
10
9.10
10.50
update
app/controllers/password_resets_controller.rb
class PasswordResetsController < ApplicationController
before_action :get_user,
only: [:edit, :update]
before_action :valid_user,
only: [:edit, :update]
before_action :check_expiration, only: [:edit, :update]
def new
end
def create
@user = User.find_by(email: params[:password_reset][:email].downcase)
if @user
@user.create_reset_digest
@user.send_password_reset_email
flash[:info] = "Email sent with password reset instructions"
redirect_to root_url
else
flash.now[:danger] = "Email address not found"
render 'new'
end
end
def edit
end
def update
if both_passwords_blank?
flash.now[:danger] = "Password/confirmation can't be blank"
render 'edit'
elsif @user.update_attributes(user_params)
log_in @user
flash[:success] = "Password has been reset."
redirect_to @user
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:password, :password_confirmation)
end
8.
10.2.
- 381
#
true
def both_passwords_blank?
params[:user][:password].blank? &&
params[:user][:password_confirmation].blank?
end
#
def get_user
@user = User.find_by(email: params[:email])
end
#
def valid_user
unless (@user && @user.activated? &&
@user.authenticated?(:reset, params[:id]))
redirect_to root_url
end
end
#
def check_expiration
if @user.password_reset_expired?
flash[:danger] = "Password reset has expired."
redirect_to new_password_reset_url
end
end
end
@user.password_reset_expired?
password_reset_expired?
10.2.3
Ruby
word_reset_expired?
10.51
10.51
password_reset_expired?
app/models/user.rb
class User < ActiveRecord::Base
.
.
.
#
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
382 -
10
pass-
true
10.6
private
.
.
.
end
10.50
10.19
update
10.18
10.5
10.18
10.2.
- 383
10.19
10.2.5.
10.50
10.29
word
10.52
10.52
test/integration/password_resets_test.rb
require 'test_helper'
class PasswordResetsTest < ActionDispatch::IntegrationTest
def setup
ActionMailer::Base.deliveries.clear
@user = users(:michael)
end
384 -
10
Forgot Pass-
10.2.
- 385
end
end
input
10.52
input
10.53
GREEN
10.3.
SendGrid
Heroku
Heroku
200
$ heroku addons:add sendgrid:starter
SendGrid
SMTP
10.54
10.54
SendGrid
config/environments/production.rb
Rails.application.configure do
.
.
.
config.action_mailer.raise_delivery_errors = true
config.action_mailer.delivery_method = :smtp
host = '<your heroku app>.herokuapp.com'
config.action_mailer.default_url_options = { host: host }
ActionMailer::Base.smtp_settings = {
:address
=> 'smtp.sendgrid.net',
:port
=> '587',
:authentication => :plain,
:user_name
=> ENV['SENDGRID_USERNAME'],
:password
=> ENV['SENDGRID_PASSWORD'],
:domain
=> 'heroku.com',
:enable_starttls_auto => true
}
.
.
386 -
10
host
.
end
10.54
SendGrid
user_name
password
ENV
SendGrid
11.4.4
$
$
$
$
$
Heroku
$ bundle exec rake test
$ git push heroku master
$ heroku run rake db:migrate
SendGrid
10.20
10.21
10.2
10.20
10.3.
- 387
10.21
10.4.
Twitter
Rails
11
12
has_many
has_many :through
10.4.1.
Active Record
Rails
Action Mailer
Action Mailer
HTML
Active Record
URL
SendGrid
10.5.
3
388 -
10
1.
10.55
10.50
response.body
10.55
10.55
HTML
expired
2.
/users/:id
where
tive Record
3.
10.40
activate
10.56
11.3.3
/users
create_reset_digest
/users/:id
update_attribute
transaction
update_attribute
Ac-
10.57
update_columns
10.55
GREEN
test/integration/password_resets_test.rb
require 'test_helper'
class PasswordResetsTest < ActionDispatch::IntegrationTest
def setup
ActionMailer::Base.deliveries.clear
@user = users(:michael)
end
.
.
.
test "expired token" do
get new_password_reset_path
post password_resets_path, password_reset: { email: @user.email }
@user = assigns(:user)
@user.update_attribute(:reset_sent_at, 3.hours.ago)
patch password_reset_path(@user.reset_token),
email: @user.email,
user: { password:
"foobar",
password_confirmation: "foobar" }
assert_response :redirect
follow_redirect!
assert_match /FILL_IN/i, response.body
end
end
10.56
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
9.
10.56
and
redirect_to root_url
&&
&&
root_url
and
10.5.
- 389
.
def index
@users = User.where(activated: FILL_IN).paginate(page: params[:page])
end
def show
@user = User.find(params[:id])
redirect_to root_url and return unless FILL_IN
end
.
.
.
end
10.57
update_columns
app/models/user.rb
class User < ActiveRecord::Base
attr_accessor :remember_token, :activation_token, :reset_token
before_save
:downcase_email
before_create :create_activation_digest
.
.
.
#
def activate
update_columns(activated: FILL_IN, activated_at: FILL_IN)
end
#
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
#
def create_reset_digest
self.reset_token = User.new_token
update_columns(reset_digest: FILL_IN,
reset_sent_at: FILL_IN)
end
#
def send_password_reset_email
UserMailer.password_reset(self).deliver_now
end
.
.
.
end
390 -
10
10.6.
10.2.4
tr
te
tr > te
tN
tr
te
tr = tN - tr
te = tN - te
tr > te
tN - tr > tN - te
- tr > - te
-1
tr < te
te = 2.hours.ago
10.51
password_reset_expired?
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
10.2.4
<
10.6.
- 391
11
Active Record
2.3
has_many
belongs_to
11.4
Twitter
12
11.1.
2.3
Git
$ git checkout master
$ git checkout -b user-microposts
11.1.1.
content
user_id
11.1
11.1
content
140
text
string
string
11.1.2
text
text
255
11.3.2
text
6.1
generate model
393
11.1
references
6.2
references
user_id
t.timestamps
11.1.4
created_at
6.1.1
updated_at
created_at
11.2.1
11.1
db/migrate/[timestamp]_create_microposts.rb
class CreateMicroposts < ActiveRecord::Migration
def change
create_table :microposts do |t|
t.text :content
t.references :user, index: true
t.timestamps null: false
end
add_index :microposts, [:user_id, :created_at]
end
end
user_id
created_at
6.2
add_index :microposts, [:user_id, :created_at]
user_id
dex
created_at
Rails
Active Record
11.1.2.
ID
11.1.3
Record
Active
6.7
setup
user_id
11.2
11.2
RED
test/models/micropost_test.rb
require 'test_helper'
class MicropostTest < ActiveSupport::TestCase
def setup
394 -
11
@user = users(:michael)
#
@micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id)
end
test "should be valid" do
assert @micropost.valid?
end
test "user id should be present" do
@micropost.user_id = nil
assert_not @micropost.valid?
end
end
setup
11.1.3
ID
11.3
RED
ID
11.1
11.1.3
user_id
11.4
belongs_to
11.4
GREEN
app/models/micropost.rb
class Micropost < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
end
11.5
GREEN
user_id
2.3.2
140
content
6.2
11.6
11.6
RED
test/models/micropost_test.rb
require 'test_helper'
class MicropostTest < ActiveSupport::TestCase
def setup
@user = users(:michael)
@micropost = @user.microposts.build(content: "Lorem ipsum")
11.1.
- 395
end
test "should be valid" do
assert @micropost.valid?
end
test "user id should be present" do
@micropost.user_id = nil
assert_not @micropost.valid?
end
test "content should be present" do
@micropost.content = " "
assert_not @micropost.valid?
end
test "content should be at most 140 characters" do
@micropost.content = "a" * 141
assert_not @micropost.valid?
end
end
6.2
11.6
$ rails console
>> "a" * 10
=> "aaaaaaaaaa"
>> "a" * 141
=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
name
11.7
6.16
11.7
GREEN
app/models/micropost.rb
class Micropost < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
validates :content, presence: true, length: { maximum: 140 }
end
11.8
GREEN
11.1.3.
Web
2.3.3
11.3
396 -
11
11.2
belongs_to
11.2
has_many
11.3
belongs_to/has_many
Rails
11.1
Micropost.create
Micropost.create!
Micropost.new
user.microposts.create
user.microposts.create!
user.microposts.build
user_id
11.2
@user = users(:michael)
#
@micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id)
11.1.
- 397
@user = users(:michael)
@micropost = @user.microposts.build(content: "Lorem ipsum")
new
build
@micropost
user_id
ID
11.1
micropost.user
user.microposts
user.microposts.create(arg)
user
user.microposts.create!(arg)
user
user.microposts.build(arg)
user
user.microposts.find_by(id: 1)
user
ID
@user.microposts.build
belongs_to :user
11.1
has_many :microposts
11.9
11.10
belongs_to
GREEN
app/models/micropost.rb
class Micropost < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
validates :content, presence: true, length: { maximum: 140 }
end
11.10
has_many
GREEN
app/models/user.rb
class User < ActiveRecord::Base
has_many :microposts
.
.
.
end
11.2
setup
11.11
11.11
GREEN
test/models/micropost_test.rb
require 'test_helper'
class MicropostTest < ActiveSupport::TestCase
def setup
398 -
11
11.9
@user = users(:michael)
@micropost = @user.microposts.build(content: "Lorem ipsum")
end
test "should be valid" do
assert @micropost.valid?
end
test "user id should be present" do
@micropost.user_id = nil
assert_not @micropost.valid?
end
.
.
.
end
11.12
GREEN
11.1.4.
user.microposts
Twitter
1
most_recent
11.13
default scope
11.13
RED
test/models/micropost_test.rb
require 'test_helper'
class MicropostTest < ActiveSupport::TestCase
.
.
.
test "order should be most recent first" do
assert_equal Micropost.first, microposts(:most_recent)
end
end
1. 9.5
11.1.
- 399
11.14
11.14
test/fixtures/microposts.yml
orange:
content: "I just ate an orange!"
created_at: <%= 10.minutes.ago %>
tau_manifesto:
content: "Check out the @tauday site by @mhartl: http://tauday.com"
created_at: <%= 3.years.ago %>
cat_video:
content: "Sad cats are sad: http://youtu.be/PKffm2uI4dk"
created_at: <%= 2.hours.ago %>
most_recent:
content: "Writing a short test"
created_at: <%= Time.zone.now %>
Ruby
11.15
created_at
Rails
RED
Rails
default_scope
order
ated_at
order(:created_at)
SQL
order('created_at DESC')
DESC
SQL
Rails 4.0
Rails
Ruby
order(created_at: :desc)
11.16
11.16
default_scope
app/models/micropost.rb
400 -
11
GREEN
SQL
cre-
11.16
->
Proc procedure
Proc
4.3.2
lambda
Proc
call
Proc
>> -> { puts "foo" }
=> #<Proc:0x007fab938d0108@(irb):1 (lambda)>
>> -> { puts "foo" }.call
foo
=> nil
Proc
Ruby
11.16
11.17
GREEN
destroy
9.4
has_many
11.18
11.18
app/models/user.rb
class User < ActiveRecord::Base
has_many :microposts, dependent: :destroy
.
.
.
end
dependent: :destroy
11.18
ID
11.19
9.57
11.19
dependent: :destroy GREEN
test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
11.1.
- 401
def setup
@user = User.new(name: "Example User", email: "user@example.com",
password: "foobar", password_confirmation: "foobar")
end
.
.
.
test "associated microposts should be destroyed" do
@user.save
@user.microposts.create!(content: "Lorem ipsum")
assert_difference 'Micropost.count', -1 do
@user.destroy
end
end
end
11.18
11.20
GREEN
11.2.
11.3.2
Twitter
11.4
show
9.3.2
402 -
11
index
ERb
11.4
11.2.1.
show.html.erb
9.3
11.3
$ rails generate controller Microposts
9.3.5
<ul class="users">
<%= render @users %>
</ul>
_user.html.erb
@users
_micro-
post.html.erb
<ol class="microposts">
<%= render @microposts %>
</ol>
11.2.
- 403
ol
ul
11.21
11.21
app/views/microposts/_micropost.html.erb
<li id="micropost-<%= micropost.id %>">
<%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
<span class="user"><%= link_to micropost.user.name, micropost.user %></span>
<span class="content"><%= micropost.content %></span>
<span class="timestamp">
Posted <%= time_ago_in_words(micropost.created_at) %> ago.
</span>
</li>
time_ago_in_words
11.21
11.2.2
CSS ID
JavaScript
9.3.3
will_paginate
<%= will_paginate @microposts %>
9.41
<%= will_paginate %>
will_paginate
AvtiveRecord::Relation
9.3.3
@microposts
show
@microposts
show
app/controllers/users_controller.rb
11.22
will_paginate
11.22
@microposts
microposts
count
404 -
11
@users
user.microposts.count
paginate
count
count
length
count
user_id
11.23
@user.microposts.any?
if
7.19
11.23
app/views/users/show.html.erb
<% provide(:title, @user.name) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
</section>
</aside>
<div class="col-md-8">
<% if @user.microposts.any? %>
<h3>Microposts (<%= @user.microposts.count %>)</h3>
<ol class="microposts">
<%= render @microposts %>
</ol>
<%= will_paginate @microposts %>
<% end %>
</div>
</div>
11.5
11.2.
- 405
11.5
11.2.2.
11.2.1
9.3.2
take
User.order(:created_at).take(6)
order
50
Lorem.sentence
Faker
30
2
11.24
11.24
db/seeds.rb
.
.
.
users = User.order(:created_at).take(6)
50.times do
content = Faker::Lorem.sentence(5)
2. Faker::Lorem.sentence
406 -
11
lorem ipsum
lorem ipsum
Rails
11.2.1
11.6
11.6
11.6
11.25
11.25
CSS
app/assets/stylesheets/custom.css.scss
.
.
.
/* microposts */
3. Faker
4.
lorem ipsum
11.25
CSS
11.2.
- 407
.microposts {
list-style: none;
padding: 0;
li {
padding: 10px 0;
border-top: 1px solid #e8e8e8;
}
.user {
margin-top: 5em;
padding-top: 0;
}
.content {
display: block;
margin-left: 60px;
img {
display: block;
padding: 5px 0;
}
}
.timestamp {
color: $gray-light;
display: block;
margin-left: 60px;
}
.gravatar {
float: left;
margin-right: 10px;
margin-top: 5px;
}
}
aside {
textarea {
height: 100px;
margin-bottom: 5px;
}
}
span.picture {
margin-top: 10px;
input {
border: 0;
}
}
11.7
11.8
408 -
11
11.9
11.21
time_ago_in_words
11.7
11.8
/users/1
/users/5
11.2.
- 409
11.9
/users/1?page=2
11.2.3.
10.29
Rails
orange:
content: "I just ate an orange!"
created_at: <%= 10.minutes.ago %>
user: michael
user
michael
Rails
michael:
name: Michael Example
email: michael@example.com
.
.
.
410 -
11
9.43
Ruby
11.26
11.26
test/fixtures/microposts.yml
orange:
content: "I just ate an orange!"
created_at: <%= 10.minutes.ago %>
user: michael
tau_manifesto:
content: "Check out the @tauday site by @mhartl: http://tauday.com"
created_at: <%= 3.years.ago %>
user: michael
cat_video:
content: "Sad cats are sad: http://youtu.be/PKffm2uI4dk"
created_at: <%= 2.hours.ago %>
user: michael
most_recent:
content: "Writing a short test"
created_at: <%= Time.zone.now %>
user: michael
<% 30.times do |n| %>
micropost_<%= n %>:
content: <%= Faker::Lorem.sentence(5) %>
created_at: <%= 42.days.ago %>
user: michael
<% end %>
Gravatar
11.27
4.2
11.27
full_title
ApplicationHelper
GREEN
test/integration/users_profile_test.rb
require 'test_helper'
5.
full_title
3.38
test_helper.rb
ApplicationHelper
11.2.
- 411
response.body
10
body
HTML
assert_select
HTML
assert_select
11.27
assert_select 'h1>img.gravatar'
h1
11.28
gravatar
img
GREEN
11.3.
12
new
11.29
2.3
2
412 -
11
11.29
edit
create
REST
destroy
11.2
11.29
config/routes.rb
Rails.application.routes.draw do
root
'static_pages#home'
get
'help'
=> 'static_pages#help'
get
'about'
=> 'static_pages#about'
get
'contact' => 'static_pages#contact'
get
'signup' => 'users#new'
get
'login'
=> 'sessions#new'
post
'login'
=> 'sessions#create'
delete 'logout' => 'sessions#destroy'
resources :users
resources :account_activations, only: [:edit]
resources :password_resets,
only: [:new, :create, :edit, :update]
resources :microposts,
only: [:create, :destroy]
end
11.2
11.29
HTTP
URL
POST
/microposts
create
DELETE
/microposts/1
destroy
ID
11.3.1.
create
9.17
destroy
9.56
11.30
11.30
RED
test/controllers/microposts_controller_test.rb
require 'test_helper'
class MicropostsControllerTest < ActionController::TestCase
def setup
@micropost = microposts(:orange)
end
test "should redirect create when not logged in" do
assert_no_difference 'Micropost.count' do
post :create, micropost: { content: "Lorem ipsum" }
end
assert_redirected_to login_url
end
test "should redirect destroy when not logged in" do
11.3.
- 413
assert_no_difference 'Micropost.count' do
delete :destroy, id: @micropost
end
assert_redirected_to login_url
end
end
9.2.1
logged_in_user
9.12
ApplicationController
11.31
logged_in_user
ApplicationController
app/controllers/application_controller.rb
11.31
11.32
11.32
GREEN
app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
def create
end
def destroy
end
end
11.33
GREEN
414 -
11
create
destroy
11.3.2.
7
create
HTML
POST
/microposts/new
/
11.10
11.10
5.6
Sign up now!
11.35
create
cropost_params
create
Web
7.23
11.34
mi-
content
create
app/controllers/microposts_controller.rb
11.34
11.3.
- 415
11.35
HTML
11.35
app/views/static_pages/home.html.erb
<% if logged_in? %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<%= render 'shared/user_info' %>
</section>
<section class="micropost_form">
<%= render 'shared/micropost_form' %>
</section>
</aside>
</div>
<% else %>
<div class="center jumbotron">
<h1>Welcome to the Sample App</h1>
<h2>
This is the home page for the
<a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</h2>
<%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
</div>
<%= link_to image_tag("rails.png", alt: "Rails logo"),
'http://rubyonrails.org/' %>
<% end %>
if-else
416 -
11
11.35
11.36
11.36
app/views/shared/_user_info.html.erb
<%= link_to gravatar_for(current_user, size: 50), current_user %>
<h1><%= current_user.name %></h1>
<span><%= link_to "view my profile", current_user %></span>
<span><%= pluralize(current_user.microposts.count, "micropost") %></span>
11.23
11.36
Microposts
1 microposts
1 micropost 2 microposts
Microposts (1)
pluralize
7.3.3
11.37
label
7.13
11.37
app/views/shared/_micropost_form.html.erb
<%= form_for(@micropost) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Compose new micropost..." %>
</div>
<%= f.submit "Post", class: "btn btn-primary" %>
<% end %>
@micro-
11.37
post
@micropost = current_user.microposts.build
11.38
home
@micropost
app/controllers/static_pages_controller.rb
11.38
11.3.
- 417
end
end
current_user
@micropost
11.37
<%= render 'shared/error_messages', object: f.object %>
@user
7.18
@micropost
f.object
form_for(@user) do |f|
form_for(@micropost) do |f|
f.object
f.object
@user
@micropost
11.37
object: f.object
object
error_messages
11.39
11.39
RED
app/views/shared/_error_messages.html.erb
<% if object.errors.any? %>
<div id="error_explanation">
<div class="alert alert-danger">
The form contains <%= pluralize(object.errors.count, "error") %>.
</div>
<ul>
<% object.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
11.40
RED
7.18
11.43
10.48
11.42
9.2
11.41
app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(@user) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
418 -
11
11.41
11.42
app/views/users/edit.html.erb
<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(@user) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :email %>
<%= f.text_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit "Save changes", class: "btn btn-primary" %>
<% end %>
<div class="gravatar_edit">
<%= gravatar_for @user %>
<a href="http://gravatar.com/emails">change</a>
</div>
</div>
</div>
11.43
app/views/password_resets/edit.html.erb
11.3.
- 419
HTML
11.11
11.11
420 -
11
11.12
11.12
11.3.3.
11.11
11.13
12
11.3.
- 421
11.13
feed
where
10.5
11.44
app/models/user.rb
class User < ActiveRecord::Base
.
.
.
#
#
12
def feed
Micropost.where("user_id = ?", id)
end
private
.
.
6. where
422 -
11
Rails Guides
11.44
.
end
Micropost.where("user_id = ?", id)
SQL
SQL
id
SQL
id
SQL injection
11.44
def feed
microposts
end
11.44
12
home
11.45
@feed_items
11.47
11.46
home
app/controllers/static_pages_controller.rb
11.45
11.46
app/views/shared/_feed.html.erb
<% if @feed_items.any? %>
<ol class="microposts">
<%= render @feed_items %>
</ol>
<%= will_paginate @feed_items %>
<% end %>
11.21
<%= render @feed_items %>
11.3.
- 423
micropost
Rails
@feed_items
Micropost
app/views/microposts/_micropost.html.erb
11.47
11.14
11.47
app/views/static_pages/home.html.erb
<% if logged_in? %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<%= render 'shared/user_info' %>
</section>
<section class="micropost_form">
<%= render 'shared/micropost_form' %>
</section>
</aside>
<div class="col-md-8">
<h3>Micropost Feed</h3>
<%= render 'shared/feed' %>
</div>
</div>
<% else %>
.
.
.
<% end %>
11.15
@feed_items
@feed_items
424 -
11
11.48
Rails
11.14
11.15
11.3.
- 425
11.48
create
@feed_items
app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
def create
@micropost = current_user.microposts.build(micropost_params)
if @micropost.save
flash[:success] = "Micropost created!"
redirect_to root_url
else
@feed_items = []
render 'static_pages/home'
end
end
def destroy
end
private
def micropost_params
params.require(:micropost).permit(:content)
end
end
11.3.4.
9.4.2
11.16
11.21
11.49
11.49
app/views/microposts/_micropost.html.erb
<li id="<%= micropost.id %>">
<%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
<span class="user"><%= link_to micropost.user.name, micropost.user %></span>
<span class="content"><%= micropost.content %></span>
<span class="timestamp">
Posted <%= time_ago_in_words(micropost.created_at) %> ago.
<% if current_user?(micropost.user) %>
<%= link_to "delete", micropost, method: :delete,
data: { confirm: "You sure?" } %>
<% end %>
</span>
</li>
426 -
11
11.16
UsersController
UsersController
destroy
correct_user
MicropostsController
9.54
admin_user
destroy
@user
ID
11.50
MicropostsController
destroy
app/controllers/microposts_controller.rb
11.50
11.3.
- 427
def micropost_params
params.require(:micropost).permit(:content)
end
def correct_user
@micropost = current_user.microposts.find_by(id: params[:id])
redirect_to root_url if @micropost.nil?
end
end
destroy
request.referrer || root_url
request.referrer 7
request.url
URL
request.referrer
request.referrer
nil
root_url
8.50
11.17
11.17
7.
HTTP
referrer
8.
428 -
HTTP_REFERER
Rails
11
REFERER
URL
Rails
rails request previous url
Stack Overflow
11.3.5.
11.51
11.51
test/fixtures/microposts.yml
.
.
.
ants:
content: "Oh, is that what you want? Because that's how you get ants!"
created_at: <%= 2.years.ago %>
user: archer
zone:
content: "Danger zone!"
created_at: <%= 3.days.ago %>
user: archer
tone:
content: "I'm sorry. Your words made sense, but your sarcastic tone did not."
created_at: <%= 10.minutes.ago %>
user: lana
van:
content: "Dude, this van's, like, rolling probable cause."
created_at: <%= 4.hours.ago %>
user: lana
11.52
11.52
GREEN
test/controllers/microposts_controller_test.rb
require 'test_helper'
class MicropostsControllerTest < ActionController::TestCase
def setup
@micropost = microposts(:orange)
end
test "should redirect create when not logged in" do
assert_no_difference 'Micropost.count' do
post :create, micropost: { content: "Lorem ipsum" }
end
assert_redirected_to login_url
11.3.
- 429
end
test "should redirect destroy when not logged in" do
assert_no_difference 'Micropost.count' do
delete :destroy, id: @micropost
end
assert_redirected_to login_url
end
test "should redirect destroy for wrong micropost" do
log_in_as(users(:michael))
micropost = microposts(:ants)
assert_no_difference 'Micropost.count' do
delete :destroy, id: micropost
end
assert_redirected_to root_url
end
end
11.53
post
follow_redirect!
11.53
post_via_redirect
GREEN
test/integration/microposts_interface_test.rb
require 'test_helper'
class MicropostInterfaceTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
test "micropost interface" do
log_in_as(@user)
get root_path
assert_select 'div.pagination'
#
assert_no_difference 'Micropost.count' do
post microposts_path, micropost: { content: "" }
end
assert_select 'div#error_explanation'
#
content = "This micropost really ties the room together"
assert_difference 'Micropost.count', 1 do
430 -
11
11.68
11.54
GREEN
11.4.
11.18
9.
https://www.flickr.com/photos/grungepunk/14026922186
11.4.
- 431
11.18
11.4.1.
CarrierWave
carrierwave gem
mini_magick
11.55
Gemfile
11.55
11.4.3
Gemfile
gem
fog
CarrierWave
source 'https://rubygems.org'
gem
gem
gem
gem
gem
gem
gem
gem
.
.
.
432 -
'rails',
'bcrypt',
'faker',
'carrierwave',
'mini_magick',
'fog',
'will_paginate',
'bootstrap-will_paginate',
11
'4.2.0'
'3.1.7'
'1.4.2'
'0.10.0'
'3.8.0'
'1.23.0'
'3.0.7'
'0.0.10'
11.55
11.4.4
$ bundle install
CarrierWave
picture
Rails
CarrierWave
Active Record
11.19
11.19
10
picture
picture
$ rails generate migration add_picture_to_microposts picture:string
$ bundle exec rake db:migrate
mount_uploader
CarrierWave
picture_uploader.rb
11.4.2
11.56
11.56
app/models/micropost.rb
class Micropost < ActiveRecord::Base
belongs_to :user
default_scope -> { order(created_at: :desc) }
mount_uploader :picture, PictureUploader
validates :user_id, presence: true
validates :content, presence: true, length: { maximum: 140 }
end
Rails
10.
image
11.4.
- 433
file_field
11.18
11.57
11.57
app/views/shared/_micropost_form.html.erb
<%= form_for(@micropost, html: { multipart: true }) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Compose new micropost..." %>
</div>
<%= f.submit "Post", class: "btn btn-primary" %>
<span class="picture">
<%= f.file_field :picture %>
</span>
<% end %>
form_for
micropost_params
Web
11.58
11.58
picture
app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
before_action :correct_user,
only: :destroy
.
.
.
private
def micropost_params
params.require(:micropost).permit(:content, :picture)
end
def correct_user
@micropost = current_user.microposts.find_by(id: params[:id])
redirect_to root_url if @micropost.nil?
end
end
image_tag
picture?
img
11.59
CarrierWave
11.20
11.6
11.59
app/views/microposts/_micropost.html.erb
<li id="micropost-<%= micropost.id %>">
<%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
<span class="user"><%= link_to micropost.user.name, micropost.user %></span>
434 -
11
<span class="content">
<%= micropost.content %>
<%= image_tag micropost.picture.url if micropost.picture? %>
</span>
<span class="timestamp">
Posted <%= time_ago_in_words(micropost.created_at) %> ago.
<% if current_user?(micropost.user) %>
<%= link_to "delete", micropost, method: :delete,
data: { confirm: "You sure?" } %>
<% end %>
</span>
</li>
11.20
11.4.2.
CarrierWave
11.60
PNG
GIF
JPEG
11.60
app/uploaders/picture_uploader.rb
11.4.
- 435
Rails
picture_size
validate
11.61
validates
11.61
app/models/micropost.rb
class Micropost < ActiveRecord::Base
belongs_to :user
default_scope -> { order(created_at: :desc) }
mount_uploader :picture, PictureUploader
validates :user_id, presence: true
validates :content, presence: true, length: { maximum: 140 }
validate :picture_size
private
#
def picture_size
if picture.size > 5.megabytes
errors.add(:picture, "should be less than 5MB")
end
end
end
:picture_size
errors
8.2
picture_size
5MB
6.2.2
file_field
MIME
11.60
JavaScript
436 -
11
jQuery
accept
$('#micropost_picture').bind('change', function() {
size_in_megabytes = this.files[0].size/1024/1024;
if (size_in_megabytes > 5) {
alert('Maximum file size is 5MB. Please choose a smaller file.');
}
});
jQuery
CSS ID
ID
alert
micropost_picture
11.57
11
11.62
11.62
jQuery
app/views/shared/_micropost_form.html.erb
<%= form_for(@micropost, html: { multipart: true }) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :content, placeholder: "Compose new micropost..." %>
</div>
<%= f.submit "Post", class: "btn btn-primary" %>
<span class="picture">
<%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
</span>
<% end %>
<script type="text/javascript">
$('#micropost_picture').bind('change', function() {
size_in_megabytes = this.files[0].size/1024/1024;
if (size_in_megabytes > 5) {
alert('Maximum file size is 5MB. Please choose a smaller file.');
}
});
</script>
11.62
Web
Web
POST
JavaScript
curl
11.61
11.4.3.
11.21
12
11.
12.
Stack Overflow
CSS
11.4.
- 437
11.21
ImageMagick
11.4.4
Heroku
13
IDE
$ sudo apt-get update
$ sudo apt-get install imagemagick --fix-missing
CarrierWave
MiniMagick
ImageMagick
resize_to_limit: [400, 400]
MiniMagick
400
CarrierWave
11.63
11.22
11.63
app/uploaders/picture_uploader.rb
class PictureUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
process resize_to_limit: [400, 400]
storage :file
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
13. Ubuntu
m>
OS X
438 -
11
IDE
Homebrew
Linux
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
#
def extension_white_list
%w(jpg jpeg gif png)
end
end
11.22
11.4.4.
11.63
14
storage :file
15
fog gem
11.64
11.64
app/uploaders/picture_uploader.rb
14.
Heroku
15.
11.4.
- 439
11.64
production?
7.1
if Rails.env.production?
storage :fog
else
storage :file
end
Amazon
vice
16
S3
1.
2.
3.
AWS Console
IAM
S3 bucket
S3
S3
11.65
CarrierWave
CarrierWave
S3
config/initializers/carrier_wave.rb
if Rails.env.production?
CarrierWave.configure do |config|
config.fog_credentials = {
# Amazon S3
:provider
=> 'AWS',
:aws_access_key_id
=> ENV['S3_ACCESS_KEY'],
16. S3
440 -
11
11.65
10.54
10.3
11.65
SendGrid
Heroku
ENV
heroku config:set
$ heroku config:set S3_ACCESS_KEY=<access key>
$ heroku config:set S3_SECRET_KEY=<secret key>
$ heroku config:set S3_BUCKET=<bucket name>
master
$
$
$
$
$
$
$
$
$
$
Heroku
ImageMagick
11.23
11.4.
- 441
11.23
11.5.
12
master
11.4.4
$
$
$
$
$
$
$
$
$
$
gem
442 -
11
Gemfile
11.66
Gemfile
11.66
source 'https://rubygems.org'
gem
gem
gem
gem
gem
gem
gem
gem
gem
gem
gem
gem
gem
gem
gem
gem
'rails',
'bcrypt',
'faker',
'carrierwave',
'mini_magick',
'fog',
'will_paginate',
'bootstrap-will_paginate',
'bootstrap-sass',
'sass-rails',
'uglifier',
'coffee-rails',
'jquery-rails',
'turbolinks',
'jbuilder',
'sdoc',
'4.2.0'
'3.1.7'
'1.4.2'
'0.10.0'
'3.8.0'
'1.23.0'
'3.0.7'
'0.0.10'
'3.2.0.0'
'5.0.0.beta1'
'2.5.3'
'4.1.0'
'4.0.0.beta2'
'2.3.0'
'2.2.3'
'0.4.0', group: :doc
group
gem
gem
gem
gem
end
:development, :test do
'sqlite3',
'1.3.9'
'byebug',
'3.4.0'
'web-console', '2.0.0.beta3'
'spring',
'1.1.3'
group
gem
gem
gem
end
:test do
'minitest-reporters', '1.0.5'
'mini_backtrace',
'0.1.3'
'guard-minitest',
'2.3.1'
group
gem
gem
gem
end
:production do
'pg',
'0.17.1'
'rails_12factor', '0.0.2'
'unicorn',
'4.8.3'
11.5.1.
Active Record
Rails
has_many
belongs_to
has_many/belongs_to
user.microposts.build()
Rails
default_scope
11.5.
- 443
dependent: :destroy
Rails
where
Active Record
CarrierWave
11.6.
3
if-else
1.
2.
11.67
3.
11.68
11.4
cp app/assets/images/rails.png test/fixtures/
.gitignore
Git
Car-
11.69
rierWave
11.70
11.68
picture
fixture_file_upload
17
picture
create
10.1.4
assigns
@micropost
11.67
test/integration/microposts_interface_test.rb
require 'test_helper'
class MicropostInterfaceTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
.
.
.
test "micropost sidebar count" do
log_in_as(@user)
get root_path
assert_match "#{FILL_IN} microposts", response.body
#
other_user = users(:mallory)
log_in_as(other_user)
17.
444 -
Windows
11
:binary
get root_path
assert_match "0 microposts", response.body
other_user.microposts.create!(content: "A micropost")
get root_path
assert_match FILL_IN, response.body
end
end
11.68
test/integration/microposts_interface_test.rb
require 'test_helper'
class MicropostInterfaceTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
test "micropost interface" do
log_in_as(@user)
get root_path
assert_select 'div.pagination'
assert_select 'input[type=FILL_IN]'
#
post microposts_path, micropost: { content: "" }
assert_select 'div#error_explanation'
#
content = "This micropost really ties the room together"
picture = fixture_file_upload('test/fixtures/rails.png', 'image/png')
assert_difference 'Micropost.count', 1 do
post microposts_path, micropost: { content: content, picture: FILL_IN }
end
assert FILL_IN.picture?
follow_redirect!
assert_match content, response.body
#
assert_select 'a', 'delete'
first_micropost = @user.microposts.paginate(page: 1).first
assert_difference 'Micropost.count', -1 do
delete micropost_path(first_micropost)
end
#
get user_path(users(:archer))
assert_select 'a', { text: 'delete', count: 0 }
end
.
.
.
end
11.6.
- 445
11.69
#
#
#
#
#
#
.gitignore
11.70
config/initializers/skip_image_resizing.rb
if Rails.env.test?
CarrierWave.configure do |config|
config.enable_processing = false
end
end
446 -
11
12
12.1
Ajax
12.2
12.3
Ruby
SQL
Rails
12.4
12.1
12.2
Follow
12.3
Hobbes
Follow
Calvin
12.4
Hobbes
1.
9187111340
(John Calvin)
Thomas Hobbes
Unfollow
12.5
http://www.flickr.com/photos/john_lustig/2518452221/
https://www.flickr.com/photos/renemensen/
447
12.1
448 -
12
12.2
12
- 449
12.3
450 -
12
Follow
12.4
Unfollow
12
- 451
12.5
12.1.
has_many
has_many :through
Git
$ git checkout master
$ git checkout -b following-users
12.1.1.
Calvin
Hobbes
Hobbes
Hobbes
Calvin
followed
Rails
followers
hobbes.followers
Twitter
lowers
452 -
Calvin
12
calvin.following
Calvin
follower
Hobbes
followeds
following
50 following, 75 fol-
12.6
following
user.following
has_many
following
followed_id
follower_id
12.6
12.6
ID
users
followers
following
users
followers
7.1.2
REST
following
A
Calvin
Calvin
Facebook
Calvin
Hobbes
B
B
Hobbes
Hobbes
Calvin
Hobbes
Hobbes
Active Relationship
Hobbes
Calvin
Positive Relationship
followers
Twitter
Calvin
12.1.5
12.6
followed_id
active_relationships
following
followed_id
users
12.7
2.
12.6
3.
Paul Fioravanti
following
id
12.1.
- 453
12.7
Relationship
relationships
12.8
12.1.4
12.8
Relationship
followed_id
12.1
relationships
db/migrate/[timestamp]_create_relationships.rb
12.1
454 -
12
12.1
(follower_id, followed_id)
6.28
12.1.4
curl
relationships
$ bundle exec rake db:migrate
12.1.2.
has_many ,
11.1.3
belongs_to
user.active_relationships.build(followed_id: ...)
11.1.3
Rails
:microposts
Micropost
Relationship
has_many :active_relationships
Rails
4.
Rails
classify
has_many
"foo_bars"
"FooBar"
12.1.
- 455
microposts
user_id
user_id
foreign key
<class>_id
Rails
<class>
follower_id
12.2
Rails
12.2
11.1.1
Rails
12.3
has_many
app/models/user.rb
class User < ActiveRecord::Base
has_many :microposts, dependent: :destroy
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent:
:destroy
.
.
.
end
12.3
dependent: :destroy
belongs_to
app/models/relationship.rb
class Relationship < ActiveRecord::Base
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
end
followed
12.1.5
11.1
12.1
12.1
active_relationship.follower
active_relationship.followed
user.active_relationships.create(followed_id:
user.id)
user.active_relationships.create!(followed_id:
user.id)
user.active_relationships.build(followed_id:
user.id)
5.
Rails
"foo_bar"
456 -
12
underscore
FooBar
id
foo_bar_id
user
user
user
"FooBar".underscore
12.1.3.
6.29
12.1
12.4
6.30
12.5
12.6
12.4
test/models/relationship_test.rb
require 'test_helper'
class RelationshipTest < ActiveSupport::TestCase
def setup
@relationship = Relationship.new(follower_id: 1, followed_id: 2)
end
test "should be valid" do
assert @relationship.valid?
end
test "should require a follower_id" do
@relationship.follower_id = nil
assert_not @relationship.valid?
end
test "should require a followed_id" do
@relationship.followed_id = nil
assert_not @relationship.valid?
end
end
12.5
app/models/relationship.rb
class Relationship < ActiveRecord::Base
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
validates :follower_id, presence: true
validates :followed_id, presence: true
end
12.6
test/fixtures/relationships.yml
# empty
12.7
GREEN
12.1.
- 457
12.1.4.
following
has_many :through
has_many :through
followers
12.7
Rails
Rails
lowed_id
user.following
followeds
12.1.1
Rails
12.8
relationships
followed
fol-
user.followeds
source
following
followed_id
following
12.8
app/models/user.rb
Active Record
4.3.1
user.following.include?(other_user)
user.following.find(other_user)
following
following
Rails
following.include?(other_user)
include?
Rails
user.microposts.count
11.2.1
follow
user.follow(other_user)
unfollow
6
following?
following?
follow
following?
12.9
6.
458 -
12
unfollow
12.9
RED
test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
.
.
.
test "should follow and unfollow a user" do
michael = users(:michael)
archer = users(:archer)
assert_not michael.following?(archer)
michael.follow(archer)
assert michael.following?(archer)
michael.unfollow(archer)
assert_not michael.following?(archer)
end
end
12.1
following
follow
unfollow
following?
12.10
self
12.10
GREEN
app/models/user.rb
class User < ActiveRecord::Base
.
.
.
def feed
.
.
.
end
#
def follow(other_user)
active_relationships.create(followed_id: other_user.id)
end
#
def unfollow(other_user)
active_relationships.find_by(followed_id: other_user.id).destroy
end
#
def following?(other_user)
following.include?(other_user)
end
true
private
.
12.1.
- 459
.
.
end
12.11
GREEN
12.1.5.
user.following
user.followers
12.7
relationships
12.2
active_relationships
follower_id
active_relationships
passive_relationships
12.9
12.8
12.12
12.12
12.9
12.9
user.followers
app/models/user.rb
class User < ActiveRecord::Base
has_many :microposts, dependent: :destroy
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent:
:destroy
has_many :passive_relationships, class_name: "Relationship",
foreign_key: "followed_id",
dependent:
:destroy
has_many :following, through: :active_relationships, source: :followed
has_many :followers, through: :passive_relationships, source: :follower
.
.
460 -
12
followed_id
.
end
followers
source
Rails
followers
source
follower
follower_id
12.12
has_many :following
followers.include?
following?
12.13
followed_by?
followers
test/models/user_test.rb
12.13
GREEN
require 'test_helper'
class UserTest < ActiveSupport::TestCase
.
.
.
test "should follow and unfollow a user" do
michael = users(:michael)
archer
= users(:archer)
assert_not michael.following?(archer)
michael.follow(archer)
assert michael.following?(archer)
assert archer.followers.include?(michael)
michael.unfollow(archer)
assert_not michael.following?(archer)
end
end
12.9
12.12
12.2.
12.1
12.3
12.2.
- 461
12.2.1.
Rake
12.14
12.14
3-51
db/seeds.rb
# Users
User.create!(name: "Example User",
email: "example@railstutorial.org",
password:
"foobar",
password_confirmation: "foobar",
admin:
true,
activated: true,
activated_at: Time.zone.now)
99.times do |n|
name = Faker::Name.name
email = "example-#{n+1}@railstutorial.org"
password = "password"
User.create!(name: name,
email: email,
password:
password,
password_confirmation: password,
activated: true,
activated_at: Time.zone.now)
end
# Microposts
users = User.order(:created_at).take(6)
50.times do
content = Faker::Lorem.sentence(5)
users.each { |user| user.microposts.create!(content: content) }
end
# Following relationships
users = User.all
user = users.first
following = users[2..50]
followers = users[3..40]
following.each { |followed| user.follow(followed) }
followers.each { |follower| follower.follow(user) }
12.14
$ bundle exec rake db:migrate:reset
$ bundle exec rake db:seed
462 -
12
4-41
12.2.2.
12.1.1
following
following
label
12.10
12.1
50
12.10
12.10
#
5
12.2.3
resources
12.15
:member
following
12.15
followers
config/routes.rb
Rails.application.routes.draw do
root
'static_pages#home'
get
'help'
=> 'static_pages#help'
get
'about'
=> 'static_pages#about'
get
'contact' => 'static_pages#contact'
get
'signup' => 'users#new'
get
'login'
=> 'sessions#new'
post
'login'
=> 'sessions#create'
delete 'logout' => 'sessions#destroy'
resources :users do
member do
get :following, :followers
end
end
resources :account_activations, only: [:edit]
resources :password_resets,
only: [:new, :create, :edit, :update]
resources :microposts,
only: [:create, :destroy]
end
URL
/users/1/following
/users/1/followers
get
12.15
member
GET
ID
collection
URL
URL
ID
resources :users do
collection do
get :tigers
end
end
12.2.
- 463
URL
/users/tigers
12.15
12.2
12.2
12.15
REST
HTTP
URL
GET
/users/1/following
following
following_user_path(1)
GET
/users/1/followers
followers
followers_user_path(1)
div
12.16
12.16
app/views/shared/_stats.html.erb
<% @user ||= current_user %>
<div class="stats">
<a href="<%= following_user_path(@user) %>">
<strong id="following" class="stat">
<%= @user.following.count %>
</strong>
following
</a>
<a href="<%= followers_user_path(@user) %>">
<strong id="followers" class="stat">
<%= @user.followers.count %>
</strong>
followers
</a>
</div>
12.16
8.1
nil
nil
@user
@user.following.count
@user.followers.count
@user.microposts.count
11.23
Rails
CSS ID
<strong id="following" class="stat">
...
</strong>
7.
464 -
Rails
12
ID
12.2.5
Ajax
Ajax
ID
12.17
12.17
app/views/static_pages/home.html.erb
<% if logged_in? %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<%= render 'shared/user_info' %>
</section>
<section class="stats">
<%= render 'shared/stats' %>
</section>
<section class="micropost_form">
<%= render 'shared/micropost_form' %>
</section>
</aside>
<div class="col-md-8">
<h3>Micropost Feed</h3>
<%= render 'shared/feed' %>
</div>
</div>
<% else %>
.
.
.
<% end %>
SCSS
12.11
12.18
12.18
SCSS
app/assets/stylesheets/custom.css.scss
.
.
.
/* sidebar */
.
.
.
.gravatar {
float: left;
margin-right: 10px;
}
.gravatar_edit {
margin-top: 15px;
}
12.2.
- 465
.stats {
overflow: auto;
margin-top: 0;
padding: 0;
a {
float: left;
padding: 0 10px;
border-left: 1px solid $gray-lighter;
color: gray;
&:first-child {
padding-left: 0;
border: 0;
}
&:hover {
text-decoration: none;
color: blue;
}
}
strong {
display: block;
}
}
.user_avatars {
overflow: auto;
margin-top: 10px;
.gravatar {
margin: 1px 1px;
}
a {
padding: 0;
}
}
.users.follow {
padding: 0;
}
/* forms */
.
.
.
466 -
12
12.11
12.19
12.19
app/views/users/_follow_form.html.erb
<% unless current_user?(@user) %>
<div id="follow_form">
<% if current_user.following?(@user) %>
<%= render 'unfollow' %>
<% else %>
<%= render 'follow' %>
<% end %>
</div>
<% end %>
follow
12.20
12.20
unfollow
11.29
config/routes.rb
Rails.application.routes.draw do
root
'static_pages#home'
get
'help'
=> 'static_pages#help'
get
'about'
=> 'static_pages#about'
12.2.
- 467
get
'contact' => 'static_pages#contact'
get
'signup' => 'users#new'
get
'login'
=> 'sessions#new'
post
'login'
=> 'sessions#create'
delete 'logout' => 'sessions#destroy'
resources :users do
member do
get :following, :followers
end
end
resources :account_activations, only: [:edit]
resources :password_resets,
only: [:new, :create, :edit, :update]
resources :microposts,
only: [:create, :destroy]
resources :relationships,
only: [:create, :destroy]
end
follow
unfollow
12.21
12.22
12.21
app/views/users/_follow.html.erb
<%= form_for(current_user.active_relationships.build) do |f| %>
<div><%= hidden_field_tag :followed_id, @user.id %></div>
<%= f.submit "Follow", class: "btn btn-primary" %>
<% end %>
12.22
app/views/users/_unfollow.html.erb
<%= form_for(current_user.active_relationships.find_by(followed_id: @user.id),
html: { method: :delete }) do |f| %>
<%= f.submit "Unfollow", class: "btn" %>
<% end %>
form_for
12.22
POST
12.21
RelationshipsController
create
DELETE
destroy
12.2.4
followed_id
followed_id
hidden_field_tag
12.21
HTML
10.2.4
12.23
12.12
12.13
12.23
app/views/users/show.html.erb
<% provide(:title, @user.name) %>
<div class="row">
<aside class="col-md-4">
468 -
12
<section>
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
</section>
<section class="stats">
<%= render 'shared/stats' %>
</section>
</aside>
<div class="col-md-8">
<%= render 'follow_form' if logged_in? %>
<% if @user.microposts.any? %>
<h3>Microposts (<%= @user.microposts.count %>)</h3>
<ol class="microposts">
<%= render @microposts %>
</ol>
<%= will_paginate @microposts %>
<% end %>
</div>
</div>
12.12
/users/2
12.2.
- 469
12.13
/users/5
12.2.4
Ajax
12.2.5
12.2.3.
12.14
470 -
12.15
12
12.14
12.2.
- 471
12.15
Twitter
12.24
12.24
test/controllers/users_controller_test.rb
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
def setup
@user = users(:michael)
@other_user = users(:archer)
end
.
.
.
test "should redirect following when not logged in" do
get :following, id: @user
assert_redirected_to login_url
end
472 -
12
following
12.15
@user.followed_users
followers
@user.followers
12.25
12.25 following
followers
app/controllers/users_controller.rb
RED
show.html.erb
Rails
12.25
show
render
show_follow
ERb
12.26
12.26
show_follow
app/views/users/show_follow.html.erb
<% provide(:title, @title) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<%= gravatar_for @user %>
12.2.
- 473
12.25
12.16
12.18
474 -
12
12.26
12.17
12.16
12.17
12.2.
- 475
12.18
5.3.4
HTML
URL
11.2.3
orange:
content: "I just ate an orange!"
created_at: <%= 10.minutes.ago %>
user: michael
user_id: 1
12.27
test/fixtures/relationships.yml
one:
follower: michael
476 -
12
user: michael
12.27
followed: lana
two:
follower: michael
followed: mallory
three:
follower: lana
followed: michael
four:
follower: archer
followed: michael
Michael
Lana
Mallory
assert_match
Lana
Archer
11.27
Michael
12.28
12.28
GREEN
test/integration/following_test.rb
require 'test_helper'
class FollowingTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
log_in_as(@user)
end
test "following page" do
get following_user_path(@user)
assert_not @user.following.empty?
assert_match @user.following.count.to_s, response.body
@user.following.each do |user|
assert_select "a[href=?]", user_path(user)
end
end
test "followers page" do
get followers_user_path(@user)
assert_not @user.followers.empty?
assert_match @user.followers.count.to_s, response.body
@user.followers.each do |user|
assert_select "a[href=?]", user_path(user)
end
end
end
assert_not @user.following.empty?
12.2.
- 477
@user.following.each do |user|
assert_select "a[href=?]", user_path(user)
end
12.29
GREEN
12.2.4.
12.31
12.30
RelationshipsController
test/controllers/relationships_controller_test.rb
12.30
RED
require 'test_helper'
class RelationshipsControllerTest < ActionController::TestCase
test "create should require logged-in user" do
assert_no_difference 'Relationship.count' do
post :create
end
assert_redirected_to login_url
end
test "destroy should require logged-in user" do
assert_no_difference 'Relationship.count' do
delete :destroy, id: relationships(:one)
end
assert_redirected_to login_url
end
end
RelationshipsController
logged_in_user
12.31 RelationshipsController
app/controllers/relationships_controller.rb
478 -
12
12.31
GREEN
12.10
12.21
12.22
unfollow
12.32
12.32 RelationshipsController
app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
before_action :logged_in_user
def create
user = User.find(params[:followed_id])
current_user.follow(user)
redirect_to user
end
def destroy
user = Relationship.find(params[:id]).followed
current_user.unfollow(user)
redirect_to user
end
end
curl
current_user
nil
12.2.6
12.19
12.20
12.2.
- 479
12.19
12.20
480 -
12
12.2.5.
Ajax
RelationshipsController
12.2.4
Ajax 8
create
destroy
Ajax
Ajax
Rails
form_for
12.33
12.33
Rails
Ajax
12.34
Ajax
app/views/users/_follow.html.erb
<%= form_for(current_user.active_relationships.
build(followed_id: @user.id),
remote: true) do |f| %>
<div><%= hidden_field_tag :followed_id, @user.id %></div>
<%= f.submit "Follow", class: "btn btn-primary" %>
<% end %>
12.34
Ajax
app/views/users/_unfollow.html.erb
<%= form_for(current_user.active_relationships.find_by(followed_id: @user.id),
html: { method: :delete },
remote: true) do |f| %>
<%= f.submit "Unfollow", class: "btn" %>
<% end %>
ERb
HTML
Rails
Rails
data-remote="true"
JavaScript
JavaScript
Rails
JavaScript
JavaScript
unobtrusive JavaScript
RelationshipsController
HTML
respond_to
Ajax
respond_to do |format|
format.html { redirect_to user }
8.
Ajax
AJAX
Ajax
12.2.
- 481
format.js
end
respond_to
RelationshipsController
respond_to
12.32
@user
12.32
if-else
create
Ajax
12.35
user
Ajax
12.33
12.34
12.35
RelationshipsController
app/controllers/relationships_controller.rb
Ajax
12.35
12.36
JavaScript
12.36
config/application.rb
require File.expand_path('../boot', __FILE__)
.
.
.
module SampleApp
class Application < Rails::Application
.
.
.
#
Ajax
config.action_view.embed_authenticity_token_in_remote_forms = true
end
end
482 -
12
destroy
JavaScript
Ajax
.js.erb
Ruby
JavaScript
JS-ERb
Model
Rails
create.js.erb
JavaScript
destroy.js.erb
Ruby
Rails
jQuery
jQuery
DOM
DOM
ID
DOM
Document Object
follow_form
$("#follow_form")
div
12.19
CSS
ID
jQuery
CSS
.
CSS
html
CSS
HTML
"foobar"
$("#follow_form").html("foobar")
JavaScript
JS-ERb
escape_javascript
12.37
Ruby
JavaScript
create.js.erb
12.37
HTML
HTML
JS-ERb
app/views/relationships/create.js.erb
$("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>")
$("#followers").html('<%= @user.followers.count %>')
destroy.js.erb
12.38
12.38
JS-ERb
app/views/relationships/destroy.js.erb
$("#follow_form").html("<%= escape_javascript(render('users/follow')) %>")
$("#followers").html('<%= @user.followers.count %>')
12.2.6.
POST
assert_difference '@user.following.count', 1 do
post relationships_path, followed_id: @other.id
end
Ajax
post
xhr :post
12.2.
- 483
assert_difference '@user.following.count', 1 do
xhr :post, relationships_path, followed_id: @other.id
end
xhr
XmlHttpRequest
Ajax
12.35
post
delete
ID
assert_difference '@user.following.count', -1 do
delete relationship_path(relationship),
relationship: relationship.id
end
Ajax
assert_difference '@user.following.count', -1 do
xhr :delete, relationship_path(relationship),
relationship: relationship.id
end
12.39
12.39
GREEN
test/integration/following_test.rb
require 'test_helper'
class FollowingTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
@other = users(:archer)
log_in_as(@user)
end
.
.
.
test "should follow a user the standard way" do
assert_difference '@user.following.count', 1 do
post relationships_path, followed_id: @other.id
end
end
test "should follow a user with Ajax" do
assert_difference '@user.following.count', 1 do
xhr :post, relationships_path, followed_id: @other.id
end
end
test "should unfollow a user the standard way" do
484 -
12
respond_to
JavaScript
@user.follow(@other)
relationship = @user.active_relationships.find_by(followed_id: @other.id)
assert_difference '@user.following.count', -1 do
delete relationship_path(relationship)
end
end
test "should unfollow a user with Ajax" do
@user.follow(@other)
relationship = @user.active_relationships.find_by(followed_id: @other.id)
assert_difference '@user.following.count', -1 do
xhr :delete, relationship_path(relationship)
end
end
end
12.40
GREEN
12.3.
11.3.3
Rails
Ruby
SQL
12.5
12.21
12.3.1.
12.22
microposts
microposts
12.3.
- 485
12.21
12.22
9.43
486 -
12
11.51
ID
ID
Michael
12.41
10
Lana
Archer
11.44
feed
12.41
RED
test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
.
.
.
test "feed should have the right posts" do
michael = users(:michael)
archer = users(:archer)
lana
= users(:lana)
#
lana.microposts.each do |post_following|
assert michael.feed.include?(post_following)
end
#
michael.microposts.each do |post_self|
assert michael.feed.include?(post_self)
end
#
archer.microposts.each do |post_unfollowed|
assert_not michael.feed.include?(post_unfollowed)
end
end
end
12.42
RED
12.3.2.
12.41
microposts
SQL
11.3.3
Active Record
ID
SQL
where
11.44
12.3.
- 487
ID
Ruby
map
enumerable
4.3.2
$ rails console
>> [1, 2, 3, 4].map { |i| i.to_s }
=> ["1", "2", "3", "4"]
Ruby
4.3.2
&
4.3.1
id
ID
>> User.first.following.map(&:id)
=> [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51]
Active Record
>> User.first.following_ids
=> [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51]
following_ids
Active Record
_ids
has_many :following
user.following
ID
SQL
?
following_ids
each
9.
488 -
12
12.8
ID
feed
12.43
12.43
GREEN
app/models/user.rb
class User < ActiveRecord::Base
.
.
.
#
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
true
#
def feed
Micropost.where("user_id in (?) OR user_id = ?", following_ids, id)
end
#
def follow(other_user)
active_relationships.create(followed_id: other_user.id)
end
.
.
.
end
12.44
GREEN
5000
12.3.3.
12.3.2
5000
following_ids
12.3.2
ID
12.43
SQL
subselect
ID
12.45
12.45
where
GREEN
app/models/user.rb
class User < ActiveRecord::Base
.
12.3.
- 489
.
.
#
def feed
Micropost.where("user_id IN (:following_ids) OR user_id = :user_id",
following_ids: following_ids, user_id: user)
end
.
.
.
end
user_id
SQL
Ruby
following_ids
SQL
following_ids = "SELECT followed_id FROM relationships
WHERE follower_id = :user_id"
SQL
ID
12.46
following_ids
SQL
12.46
GREEN
app/models/user.rb
class User < ActiveRecord::Base
.
.
.
#
def feed
following_ids = "SELECT followed_id FROM relationships
WHERE follower_id = :user_id"
490 -
12
Rails
12.47
Ruby
SQL
GREEN
background job
11.3.3
11
12.48
11.14
12.46
12.23
12.48 home
app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
def home
if logged_in?
@micropost = current_user.microposts.build
@feed_items = current_user.feed.paginate(page: params[:page])
end
end
.
.
.
end
master
$
$
$
$
$
$
$
$
$
$
git push
git push heroku
heroku pg:reset DATABASE
heroku run rake db:migrate
heroku run rake db:seed
12.24
12.3.
- 491
12.23
12.24
492 -
12
12.4.
Rails
has_many/belongs_to
has_many :through
Rails
12.4.1.
Rails
RailsCasts
RailsCasts
Tealeaf Academy
Academy
Thinkful
Tealeaf
Tealeaf
Tealeaf
RailsApps
Rails
Code School
Ruby
Rails
Ruby
Peter Cooper
Beginning Ruby
David A. Black
The Well-Grounded Rubyist
Russ Olsen
Eloquent Ruby
Hal Fulton
The Ruby Way
Rails
Sam Ruby Dave Thomas David Heinemeier Hansson
Agile Web Development with Rails
Obie Fernandez
The Rails 4 Way
Ryan Bigg
Yehuda Katz
Rails 4 in Action
12.4.2.
has_many :through
has_many
has_many
has_many :through
Rails
where
Rails
SQL
12.5.
3
12.4.
- 493
1.
11.27
2.
12.49
CGI.escapeHTML
12.49
HTML
HTML GREEN
test/integration/following_test.rb
require 'test_helper'
class FollowingTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
log_in_as(@user)
end
.
.
.
test "feed on Home page" do
get root_path
@user.feed.paginate(page: 1).each do |micropost|
assert_match CGI.escapeHTML(FILL_IN), FILL_IN
end
end
end
494 -
12