Ruby on Rails Tutorial Chapter6をやる (いつも通り)断片的なメモ
ほんとはもうとっくに終わっているのですが・・・第6章いきます。
Chapter 6: Modeling users | Ruby on Rails Tutorial (3rd Ed.) | Softcover.io
やったこと
- モデルの作成とマイグレーション
- ActiveReocordの使用。データモデルを作ったり操作したりするためのメソッドがたくさんある
- Active Recordのバリデーションでモデルに制約を付加する
- 主なバリデーションは
presence
,length
,format
- 正規表現は強力
has_secure_password
を使ってパスワードを保護
$ git checkout -b modeling-users
$ rails g model User name:string email:string
Userモデルを作る。:name
, :email
属性を持つ。
例えばActive Recordでemail
を元にユーザーを探す場合は
User.find_by(email: "noriyo.akita@gmail.com")
find_by_xxx
などの動的ファインダーメソッドは非推奨になっている。
find_by(xxx: "xxx")
などの形で使用すること。
参考記事:
Rails で十分に活用されていなくてもったいない ActiveRecord::Relation のメソッド TOP 10 - 杉風呂2.0 - A Lifelog -
user.update_attribute(:name, "The Dude")
一つの属性だけアプデするにはupdate_attribute(単数形)
引数は2つ (:name, "The Dude")
カンマ区切り。(:name => "The Dude")
これダメ。
6.2 User validations
ActiveRecordの方でバリデーションしても、データベースレベルではしてくれない。そうなると2クリック問題?が起こる。
Chapter 6: Modeling users | Ruby on Rails Tutorial (3rd Ed.) | Softcover.io
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 validationIf the above sequence seems implausible, believe me, it isn’t: it can happen on any Rails website with significant traffic (which I once learned the hard way). Luckily, the solution is straightforward to implement: we just need to enforce uniqueness at the database level as well as at the model level. Our method is to create a database index on the email column (Box 6.2), and then require that the index be unique.
第6章 ユーザーのモデルを作成する | Rails チュートリアル
1.アリスはサンプルアプリケーションにユーザー登録します。メールアドレスはalice@wonderland.comです。
2.アリスは誤って “Submit” を素早く2回クリックしてしまいます。そのためリクエストが2つ連続で送信されます。
3.次のようなことが順に発生します。リクエスト1は、検証にパスするユーザーをメモリー上に作成します。リクエスト2でも同じことが起きます。リクエスト1のユーザーが保存され、リクエスト2のユーザーも保存されます。
4.この結果、一意性の検証が行われているにもかかわらず、同じメールアドレスを持つ2つのユーザーレコードが作成されてしまいます。上のシナリオが信じがたいもののように思えるかもしれませんが、どうか信じてください。RailsのWebサイトでは、トラフィックが多いときにこのような問題が発生する可能性があるのです。幸い、解決策の実装は簡単です。実は、この問題はデータベースレベルでも一意性を強制するだけで解決します。具体的には、emailカラムにデータベースのインデックスを作成し、そのインデックスが一意であることを要求します。
DBレベルでunique実装
$ rails generate migration add_index_to_users_email
class AddIndexToUsersEmail < ActiveRecord::Migration def change add_index :users, :email, unique: true end end
add_index
メソッドでusersテーブルのemailカラムにunique
制約
そしてモデルではDBに保存する前に小文字にしてしまおうと。
before_save
を使う。
user.rb
class User < ActiveRecord::Base before_save { save.email = email.downcase } . . . end
self
を付けてこのようにも書けるが、
before_save { save.email = self.email.downcase }
(where self refers to the current user), but inside the User model the self keyword is optional on the right-hand side:
右辺においてself
はオプショナルなので、別につけなくても良い。
6.3 Adding a secure password
$ rails generate migration add_password_digest_to_users password_digest:string
これでto_users
でusers tableにpassword_digest
カラムを追加。
Gemfile
source 'https://rubygems.org' gem 'rails', '4.2.0' gem 'bcrypt', '3.1.7' . . .
User modelにhas_secure_password
methodを追加。
そうなると、password
,password_confirmation
という2つの属性が必要になる。
user_test.rb
def setup @user = User.new(name: "Example User", email: "user@example.com", password: "foobar", password_confirmation: "foobar") end
setup
メソッドで定義しているUser.new
に追加。
6.3.3 Minimum password length
パスワードの最低限の長さを決めないと。1文字だけでも困るのでw
Userモデルのテスト
user_test.rb
. . . test "password should have a minimum length" do @user.password = @user.password_confirmation = 'a' * 5 assert_not @user.valid? end
passwordとpassword_confimationが一致していて、なおかつ5文字しかないよと。
それでは通らないよね、というテストメソッドを定義。
user.rb
validates :password, length: { minimum: 6 }
パスワードは最低6文字だよ、というバリデーションをUserモデルに定義。
6.3.4 Creating and authenticating a user
$ rails c >> User.create(name: "Michael Hartl", email: "mhartl@example.com", password: "foobar", password_confirmation: "foobar")
実際にユーザーを作成。
>> user = User.find_by(email: "mhartl@example.com") >> user.password_digest => "$2a$10$YmQTuuDNOszvu5yi7auOC.F4G//FGhyQSWCpghqRWQWITUYlG3XVy"
password_digest
が生成されているのが確認できる。(いったんrails consoleから抜けて、入り直したほうがいいかも)
As noted in Section 6.3.1, has_secure_password automatically adds an authenticate method to the corresponding model objects.
>> user.authenticate("not_the_right_password") false >> user.authenticate("foobaz") 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...">
>> !!user.authenticate("foobar") => true
trueかどうかだけを確認したいなら、!!
が使える。
herokuにpushしてあとはマイグレートする。
herokuコンソールに入って確認してみる。
$ heroku run console --sandbox >> User.create(name: "Michael Hartl", email: "michael@example.com", ?> password: "foobar", password_confirmation: "foobar") => #<User id: 1, name: "Michael Hartl", email: "michael@example.com", created_at: "2014-08-29 03:27:50", updated_at: "2014-08-29 03:27:50", password_digest: "$2a$10$IViF0Q5j3hsEVgHgrrKH3uDou86Ka2lEPz8zkwQopwj...">
Exercises
1.assert_equal
メソッドを使用したテスト
test/models/user_test.rb
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
そして
emailを小文字にしたものとDBから再取得したemail(@user.reload.email
)がイコールであるかをasser_equalでチェック。
つまりちゃんと小文字でDBに保存されているかどうか。
その確認のためには一旦save
してからの>reload
による再取得、という流れ
Userモデルのbefore_save
を一旦コメントアウトしてみる。そうするとテストこける。
before_save
を復活させるとテスト通る。ちゃんと保存する前に小文字に変換してますよ、ということで。
DBからレコードを再取得する。
Model.reload
2.そのbefore_save
methodをbefore_save { email.downcase! }
にする。email属性を直接小文字にしてしまう。
3.Disallowing double dots
foo@bar..com
のようにドットが2つ続いてるのはいけない。
というわけで正規表現・・・うっ、頭が
user.rb
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
ドットの後には必ず文字が入ってないといけない。
Guardが動いてくれなくて
guardで
You don't have bcrypt installed in your application. Please add it to your Gemfile and run bundle install
とでてしまいました。
bundle exec rake test
は大丈夫。
bcrypt
はちゃんと入ってるけどなあ・・・。
調べてみると
Rails: "This error occurred while loading the following files: bcrypt" - Stack Overflow
WEBrickを再起動しなよ! みたいなこと言ってるけど、(その時は)立ち上げてないんだよなあ・・・。 Nitrous.ioのBoxを再起動後、guard起動>return押してall testsを走らせる>オールグリーンだった。
*あとでrails sしたときに
server is already running. Check /home/action/workspace/sample_app/tmp/pids/server.pid.
と出ました(ということはやっぱり起動させていたんだろうか?)
サーバーのpid
を確認
$ cat /home/action/workspace/sample_app/tmp/pids/server.pid. 573
とでたので、それをkill
しました。
$ kill -9 573
するとrails s
でサーバー動きました。