やりクサし
先週の朝練の続きを云々することに。validate がなんとかなあたりなので正直かったるい。前回からの続き、ということで以下は実行しているらしい。
$ bundle exec rake db:test:prepare
一応 db 配下には test.sqlite3 が作成されてますね。
最初の例
として例示されているのが以下。
class User < ActiveRecord::Base attr_accessible :name, :email validates :name, :presence => true end
引数を括弧で、な例も示されてますが、わし的にはこっちの方が楽かも。あまり本文読まずに進めますが spec/models/user_spec.rb が以下らしい。
require 'spec_helper' describe User do before(:each) do @attr = { :name => "Example User", :email => "user@example.com" } end it "should create a new instance given valid attributes" do User.create!(@attr) end it "should require a name" end
ここで rspec spec/ してみれば良いのかな。
$ bundle exec rspec spec/ ................* Pending: User should require a name # Not Yet Implemented # ./spec/models/user_spec.rb:24 Finished in 1.83 seconds 17 examples, 0 failures, 1 pending $
確かに pending と言われますな。続きを盛り込んでみます。
末端部分を以下に。
it "should require a name" do no_name_user = User.new(@attr.merge(:name => "")) no_name_user.should_not be_valid end
もっかい試験を起動。
$ bundle exec rspec spec/ ................. Finished in 0.76727 seconds 17 examples, 0 failures $
素晴しい。というか ruby の lisp/scheme 風な記述ができるあたりが好きなんですよね。で、email な属性についても以下な試験を追加。
it "should require an email address" do no_email_user = User.new(@attr.merge(:email => "")) no_email_user.should_not be_valid end
これ、試験実行を emacs 上でできんのかな。って試験失敗しとるぞ。
$ bundle exec rspec spec/ .................F Failures: 1) User should require an email address Failure/Error: no_email_user.should_not be_valid expected valid? to return false, got true # ./spec/models/user_spec.rb:31:in `block (2 levels) in <top (required)>' Finished in 4.4 seconds 18 examples, 1 failure Failed examples: rspec ./spec/models/user_spec.rb:29 # User should require an email address $
あ、これは model の記述がアレなのか。以下を app/models/user.rb に追加。
validates :email, :presence => true
で再度試験実行。
$ bundle exec rspec spec/ .................. Finished in 0.67333 seconds 18 examples, 0 failures $
この重さはやっぱ例のツールを使いたくなりますな。とりあえず試験はパス。ここで _the “presence” validations are complete_ とのこと。
6.2.2 Length validation
どんどん進めます。ええと、以下を追加とあります。
it "should reject names that are too long" do long_name = "a" * 51 long_name_user = User.new(@attr.merge(:name => long_name)) long_name_user.should_not be_valid end
まだ、user.name の length に関する制限はないな。なので試験は微妙。
$ bundle exec rspec spec/ ..................F Failures: 1) User should reject names that are too long Failure/Error: long_name_user.should_not be_valid expected valid? to return false, got true # ./spec/models/user_spec.rb:37:in `block (2 levels) in <top (required)>' Finished in 0.66404 seconds 19 examples, 1 failure Failed examples: rspec ./spec/models/user_spec.rb:34 # User should reject names that are too long $
app/models/user.rb を以下に修正。
class User < ActiveRecord::Base attr_accessible :name, :email validates :name, :presence => true, :length => { :maximum => 50 } validates :email, :presence => true end
もう書いてあるママ、というカンジです。非常に分かりやすい。試験も当然パス。
$ bundle exec rspec spec/ ................... Finished in 0.92053 seconds 19 examples, 0 failures $
6.2.3 Format validation
次は email な属性の format なのかな。以下な試験を追加せよ、とのこと。
it "should accept valid email addresses" do addresses = %w[user@foo.com THE_USER@foo.bar.org first.last@foo.jp] addresses.each do |address| valid_email_user = User.new(@attr.merge(:email => address)) valid_email_user.should be_valid end end it "should reject invalid email addresses" do addresses = %w[user@foo,com user_at_foo.org example.user@foo.] addresses.each do |address| invalid_email_user = User.new(@attr.merge(:email => address)) invalid_email_user.should_not be_valid end end
なるほど。rspec 良いなぁ。いちいち日本語で書かなくて良いのが楽。とりあえず盛り込んで実行します。
$ bundle exec rspec spec/ ....................F Failures: 1) User should reject invalid email addresses Failure/Error: invalid_email_user.should_not be_valid expected valid? to return false, got true # ./spec/models/user_spec.rb:52:in `block (3 levels) in <top (required)>' # ./spec/models/user_spec.rb:50:in `each' # ./spec/models/user_spec.rb:50:in `block (2 levels) in <top (required)>' Finished in 0.79536 seconds 21 examples, 1 failure Failed examples: rspec ./spec/models/user_spec.rb:48 # User should reject invalid email addresses $
失敗して当然、ということで以下にするらしい。
class User < ActiveRecord::Base attr_accessible :name, :email email_regex = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :name, :presence => true, :length => { :maximum => 50 } validates :email, :presence => true, :format => { :with => email_regex } end
微妙に正規表現のアレがナニだったりしてorz
試験も一応パス。
$ bundle exec rspec spec/ ..................... Finished in 0.73002 seconds 21 examples, 0 failures $
6.2.4 Uniqueness validation
以下な試験を追加とのこと。
it "should reject duplicate email addresses" do # Put a user with given email address into the database. User.create!(@attr) user_with_duplicate_email = User.new(@attr) user_with_duplicate_email.should_not be_valid end
試験は失敗するはず。
$ bundle exec rspec spec/ .....................F Failures: 1) User should reject duplicate email addresses Failure/Error: user_with_duplicate_email.should_not be_valid expected valid? to return false, got true # ./spec/models/user_spec.rb:60:in `block (2 levels) in <top (required)>' Finished in 0.89521 seconds 22 examples, 1 failure Failed examples: rspec ./spec/models/user_spec.rb:56 # User should reject duplicate email addresses $
app/models/user.rb に uniq なナニを追加。
class User < ActiveRecord::Base attr_accessible :name, :email email_regex = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :name, :presence => true, :length => { :maximum => 50 } validates :email, :presence => true, :format => { :with => email_regex }, :uniqueness => true end
これだけでは足らんらしい。大文字小文字なナニもなんとかなる模様。試験が以下。
it "should reject email addresses identical up to case" do upcased_email = @attr[:email].upcase User.create!(@attr.merge(:email => upcased_email)) user_with_duplicate_email = User.new(@attr) user_with_duplicate_email.should_not be_valid end
試験にはパスしません。
$ bundle exec rspec spec/ .....................FF Failures: 1) User should reject duplicate email addresses Failure/Error: user_with_duplicate_email.should_not be_valid expected valid? to return false, got true # ./spec/models/user_spec.rb:60:in `block (2 levels) in <top (required)>' 2) User should reject email addresses identical up to case Failure/Error: user_with_duplicate_email.should_not be_valid expected valid? to return false, got true # ./spec/models/user_spec.rb:67:in `block (2 levels) in <top (required)>' Finished in 1.11 seconds 23 examples, 2 failures Failed examples: rspec ./spec/models/user_spec.rb:56 # User should reject duplicate email addresses rspec ./spec/models/user_spec.rb:63 # User should reject email addresses identical up to case $
で、以下を app/models/user.rb に追加。というか修正。
class User < ActiveRecord::Base attr_accessible :name, :email email_regex = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :name, :presence => true, :length => { :maximum => 50 } validates :email, :presence => true, :format => { :with => email_regex }, :uniqueness => { :case_sensitive => false } end
試験パスするんですが case_sensitive というナニは引数になるのかな。
$ bundle exec rspec spec/ ....................... Finished in 4.21 seconds 23 examples, 0 failures $
次
_The uniqueness caveat_という節。もの凄くレアなナニに対応する模様。
D’oh! But what can go wrong? Here’s what:
1. Alice signs up for the sample app, with address alice@wonderland.com.
2.Alice accidentally clicks on “Submit” twice, sending two requests in quick succession.
3.The following sequence occurs: request 1 creates a user in memory that passes validation, request 2 does the same, request 1’s user gets saved, request 2’s user gets saved.
4.Result: two user records with the exact same email address, despite the uniqueness validation.
このあたりは、_へー_という印象だな。
$ rails generate migration add_email_uniqueness_index invoke active_record create db/migrate/20110919112931_add_email_uniqueness_index.rb $
あ、これを以下にするんか。
class AddEmailUniquenessIndex < ActiveRecord::Migration def self.up add_index :users, :email, :unique => true end def self.down remove_index :users, :email end end
users および email 属性は unique であることを保証されている、という理解で良いかな。で、これおを元に migrate しておく、と。
$ bundle exec rake db:migrate == AddEmailUniquenessIndex: migrating ======================================== -- add_index(:users, :email, {:unique=>true}) -> 0.0010s == AddEmailUniquenessIndex: migrated (0.0011s) =============================== $
どうもこれで double click 問題はなんとかなってるらしいけど、もうちょっと続きがあるみたいです。
今日はもう限界ぽ
6.3 Viewing users は明日ってことで。