Ruby on Rails Tutorial (5)
Chapter 6 突入。イベント運営しつつぼちぼち進めます。つうか何故にここでテザリングなんだと小一時間orz
Chapter 9 までは User 云々が続く模様。とりあえずブランチを作成。
$ git checkout master $ git checkout -b modeling-users
6.1 User model
まずはデータ構造を云々とのこと。signup なページの mockup も示されています。そいえば model を云々するのはここでは初めてになるんですね。
テキストにざっくり目を通した後に以下をナニ。
$ rails generate controller Users new --no-test-framework $ rails generate model User name:string email:string $ bundle exec rake db:migrate
あ、app/view/users ってのは作ってましたね。controller の generate な出力が以下。
identical app/controllers/users_controller.rb route get "users/new" invoke erb exist app/views/users conflict app/views/users/new.html.erb force app/views/users/new.html.erb invoke helper identical app/helpers/users_helper.rb invoke assets invoke coffee identical app/assets/javascripts/users.js.coffee invoke scss identical app/assets/stylesheets/users.css.scss
あら? これって一回ヤッてるんかな。。あ、やっぱそうでした。全部元に戻すのって git checkout 何だったか。
$ git checkout .
元に戻ったはずなので、model の generate を実行。出力が以下。
invoke active_record create db/migrate/20130119012313_create_users.rb create app/models/user.rb invoke rspec create spec/models/user_spec.rb
migrate のソレがタイムスタンプになってます。確かにねぇ、というカンジ。そのまま rake db:migrate もナニ。
$ bundle exec rake db:migrate == CreateUsers: migrating ==================================================== -- create_table(:users) -> 0.0523s == CreateUsers: migrated (0.0525s) ===========================================
中身確認。
$ sqlite3 db/development.sqlite3 SQLite version 3.7.9 2011-11-01 00:52:41 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> .schema CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL); CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255), "email" varchar(255), "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL); CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version"); sqlite> .tables schema_migrations users
で、一回 rollback もかけてみるなど。
$ bundle exec rake db:rollback == CreateUsers: reverting ==================================================== -- drop_table("users") -> 0.0691s == CreateUsers: reverted (0.0693s) ===========================================
で、もっかい接続して状態確認。
$ sqlite3 db/development.sqlite3 SQLite version 3.7.9 2011-11-01 00:52:41 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> .schema CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL); CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version"); sqlite> .tables schema_migrations
再度作成。
$ bundle exec rake db:migrate
6.1.2 The model file
某イベント対応がちょい落ち着いたので続きに着手。Model annotatetion て何でしょ、と言いつつ Gemfile に以下を追加して云々。
group :development do gem 'annotate', '2.5.0' end
で、
$ bundle exec annotate
したら確かにこうなりました。すばらです。
$ cat app/models/user.rb # == Schema Information # # Table name: users # # id :integer not null, primary key # name :string(255) # email :string(255) # created_at :datetime not null # updated_at :datetime not null # class User < ActiveRecord::Base attr_accessible :email, :name end
6.1.3 Creating user objects
$ rails console --sandbox Loading development environment in sandbox Any modifications you make will be rolled back on exit >>
の中で User なソレを云々してますね。一通りヤッてみますがログはスルーで。
つうか --sandbox 良いですね。exit したら rollback されるのか。
6.1.4 Finding user objects
ここもスルー。
6.1.5 Updating user objects
update_attributes ての、知りませんでした。ここもログはスルーで目を通すのみ。
6.2 User validations
今は何でもあり状態なので validation を云々、ということなのか。
6.2.1 Initial user tests
初期状態が以下 (コメント除く)。
require 'spec_helper' describe User do pending "add some examples to (or delete) #{__FILE__}" end
これを実行してみるとどうなるかというと
$ bundle exec rspec spec/models/ Rack::File headers parameter replaces cache_control after Rack 1.5. * Pending: User add some examples to (or delete) /home/rms/OLDHome/rms/Documents/rails_proj/sample_app/spec/models/user_spec.rb # No reason given # ./spec/models/user_spec.rb:15 Finished in 0.0005 seconds 1 example, 0 failures, 1 pending Randomized with seed 16161
とのこと。とりあえず試験を追加していくのかな。
以下を盛り込んで試験実行。
require 'spec_helper' describe User do before { @user = User.new(name: "Example User", email: "user@example.com") } subject { @user } it { should respond_to(:name) } it { should respond_to(:email) } end
試験 red 終了。おかしいな。って以下を云々しないといけない模様。
$ bundle exec rake db:test:prepare
sqlite3 で接続してみても何も出なかったので、まさかとは思ったのですがやはりでしたか。この後に試験実行リトライしてみたら green 終了でした。
6.2.2 Validating presence
name 属性がブランクだと invalid なソレを盛り込む模様。とりあえず以下な試験を追加とのこと。
it { should be_valid } describe "when name is not present" do before { @user.name = " " } it { should_not be_valid } end
今はまだ model に validate なソレを追加していないので試験は失敗するはず。
Failures: 1) User when name is not present Failure/Error: it { should_not be_valid } expected valid? to return false, got true # ./spec/models/user_spec.rb:27:in `block (3 levels) in <top (required)>'
では以下を model に追加して
validates :name, presence: true
試験リトライで green 確認。今度は email の試験ってことで試験を以下に。
require 'spec_helper' describe User do before do @user = User.new(name: "Example User", email: "user@example.com") end subject { @user } it { should respond_to(:name) } it { should respond_to(:email) } it { should be_valid } describe "when name is not present" do before { @user.name = " " } it { should_not be_valid } end describe "when email is not present" do before { @user.email = " " } it { should_not be_valid } end end
当り前ですが試験は red ですね。で、こちらも validate を追加して green 終了。
6.2.3 Length validation
長さチェック。以下な試験を追加。
describe "when name is too long" do before { @user.name = "a" * 51 } it { should_not be_valid } end
当り前ですが結果は red 終了。name 属性な validate を以下にして
validates :name, presence: true, length: { maximum: 50 }
試験 green 終了。
6.2.4 Format validation
こんどは email な書式チェックなのかな。
describe "when email format is invalid" do it "should be invalid" do addresses = %w[user@foo,com user_at_foo.org example.user@foo. foo@bar_baz.com foo@bar+baz.com] addresses.each do |invalid_address| @user.email = invalid_address @user.should_not be_valid end end end describe "when email format is valid" do it "should be valid" do addresses = %w[user@foo.COM A_US-ER@f.b.org frst.lst@foo.jp a+b@baz.cn] addresses.each do |valid_address| @user.email = valid_address @user.should be_valid end end end
追加して red 確認。model の実装は以下らしい。
validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }
試験 green なのは良いのですが、正規表現がアレ。(何
6.2.5 Uniqueness validation
値重複の確認か。以下な試験追加。
describe "when email address is already taken" do before do user_with_same_email = @user.dup user_with_same_email.save end it { should_not be_valid } end
試験 red を確認。email な属性の validate が以下になる模様。
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: true
試験 green を確認。大文字小文字なソレも云々できる模様。試験が以下。
describe "when email address is already taken" do before do user_with_same_email = @user.dup user_with_same_email.email = @user.email.upcase user_with_same_email.save end it { should_not be_valid } end
試験 red を確認して以下に修正。
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false }
試験 green 確認。
The uniqueness caveat
unique の担保は DB 側のレイヤに委ねる模様。まず migrate なソレを generate します。
$ rails generate migration add_index_to_users_email invoke active_record create db/migrate/20130119065011_add_index_to_users_email.rb
で、生成されたソレを以下に
class AddIndexToUsersEmail < ActiveRecord::Migration def change add_index :users, :email, unique: true end end
した上で rake db:migrate 実行。
$ bundle exec rake db:migrate == AddIndexToUsersEmail: migrating =========================================== -- add_index(:users, :email, {:unique=>true}) -> 0.0181s == AddIndexToUsersEmail: migrated (0.0183s) ==================================
で、User な model に以下を盛り込む模様。
before_save { |user| user.email = email.downcase }
ちょっとこの項、もう少しきちんとテキスト確認した方が良さげ。
6.3 Adding a secure password
パスワードな属性を追加するのか。とりあえず gem 追加らしい。以下を Gemfile に追加とのこと。
gem 'bcrypt-ruby', '3.0.1'
で、bundle install を実行。大丈夫かな。問題なく導入できたようです。盛り込みですがまずは試験を追加。
describe User do before do @user = User.new(name: "Example User", email: "user@example.com") end subject { @user } it { should respond_to(:name) } it { should respond_to(:email) } it { should respond_to(:password_digest) }
試験 red を確認後にまずは migrate を云々。
$ rails generate migration add_password_digest_to_users password_digest:string invoke active_record create db/migrate/20130119065930_add_password_digest_to_users.rb
で、順に以下とのこと。
$ bundle exec rake db:migrate $ bundle exec rake db:test:prepare $ bundle exec rspec spec/
試験 green でした。良かった。
6.3.2 Password and confirmation
password_confirmation という属性を追加とのこと。試験が以下で。
require 'spec_helper' describe User do before do @user = User.new(name: "Example User", email: "user@example.com", password: "foobar", password_confirmation: "foobar") end subject { @user } it { should respond_to(:name) } it { should respond_to(:email) } it { should respond_to(:password_digest) } it { should respond_to(:password) } it { should respond_to(:password_confirmation) } it { should be_valid }
以下も追加かな。
describe "when password is not present" do before { @user.password = @user.password_confirmation = " " } it { should_not be_valid } end describe "when password doesn't match confirmation" do before { @user.password_confirmation = "mismatch" } it { should_not be_valid } end describe "when password confirmation is nil" do before { @user.password_confirmation = nil } it { should_not be_valid } end
で、試験実行も red 終了ですね。
6.3.3 User authentication
この項で以下な試験を追加らしい。
it { should respond_to(:authenticate) } . . . describe "with a password that's too short" do before { @user.password = @user.password_confirmation = "a" * 5 } it { should be_invalid } end describe "return value of authenticate method" do before { @user.save } let(:found_user) { User.find_by_email(@user.email) } describe "with valid password" do it { should == found_user.authenticate(@user.password) } end describe "with invalid password" do let(:user_for_invalid_password) { found_user.authenticate("invalid") } it { should_not == user_for_invalid_password } specify { user_for_invalid_password.should be_false } end end
盛り込み後、試験は実行してません。
6.3.4 User has secure password
最終的に User な model は以下な定義になる模様。
class User < ActiveRecord::Base attr_accessible :name, :email, :password, :password_confirmation has_secure_password before_save { |user| user.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, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } validates :password, presence: true, length: { minimum: 6 } validates :password_confirmation, presence: true end
むむむ、試験 green ですね。ちと諸々再確認します。
とりあえずエントリ投入。