まさ@ブログ書き込み中

まさ@ブログ書き込み中

まさの旅、英語、プログラミング、プライベートについて、色々記録しています。

Railsチュートリアルやってて驚いたこと(3)

 

こんにちは。結構やる気出てるまさです。

 

先ほどの投稿に引き続き、Railsチュートリアルの第6章で学んだことをまとめていきたいとおもいます。

 

第4章から、サンプルアプリケーション(ユーザーやマイクロポスト、ログイン/ログアウトなどの認証機能を持つアプリ)を作っていますが、第6章はそのアプリに必要なユーザーに関するモデルを作成することがテーマでした。

 

ぶっちゃけ、「モデルを作成する」というだけを聞くと「ふっ。余裕っしょ。これはすぐ読み終えるな」って思ってたら、全然そんなことありませんでした。

 

何があったのか、一つずつまとめていきます。

 

 

モデルをgenerateするということ

まず今まで使ってきたrailsのgenerateコマンドについて整理すると

  • rails newRailsアプリのための雛形がつくられる
  • rails generate scaffold モデル名 + データ型で、そのモデルのCRUD操作ができるようになる全ての準備が整う(Model, Controller, IndexやShowを含むView全てが整う)
  • rails generate controller コントローラ名 + メソッド名で、コントローラとビューを一気に作成できる(ルーティング済み)
  • rails generateをロールバックするのはrails destroy

といったところでしょうか。

 

そして今回はrails generate model モデル名 + データ型というコマンドを利用します。

scaffoldと違うのは、ただただモデルを作るのか、それに関するあらゆるファイルを作るのかという点です。

 

rails generateで使われるコマンドについて素晴らしく見やすいサイトを見つけたのでここで貼っておきます。

tech-dig.hatenablog.com

 

ちなみにrails db:migrateをするとデータベースを変更するためのマイグレーションファイルに沿ってマイグレーションが行われ、データベースに反映されます。rails db:rollbackをするとその変更が取り消されます。

 

 

ユーザーを検証する

本章ではユーザーモデルにname属性とemail属性を与えましたが、このままでは空の名前やメールアドレスで登録が出来てしまったり、同じ名前やメールアドレスで複数のユーザーが登録出来てしまったりするので、検証(Validation)という機能を通して属性値に制約を課します。 

 

本章では数ある検証に関する機能のうち

  • 存在性(presence)
  • 長さ(length)
  • フォーマット(format)
  • 一意性(uniqueness)
  • 確認(confirmation) 

の検証をしていきました。

 

存在性の検証

存在性を検証するということは、すでにそのデータがあるかどうかを検証するということです。同じ名前やメールアドレスがるかどうかをチェックするのです。

と言っても方法は簡単で、ユーザーモデル(models/user.rb)でvalidates :name, presence: true やvalidates :email, presence: trueとするだけです。

 

ちなみに、presence: trueというのは要素が一つのオプションハッシュでメソッドの最後の引数としてハッシュを渡す場合、波かっこ{}をつけなくてもいいので、このような書き方になっているようです。

 

長さの検証

ある属性値の長さを制限するには、先ほどのvalidatesメソッドの最後に

 

length: { maximum: 50 } のように付け加えるだけでいいのです。先ほどのハッシュの波かっこ{}の省略に関する基準からいうと、このmaximumをキーとしたハッシュはlengthキーの値でしかないので、波かっこは無視できないというわけですね。

 

フォーマットの検証

ここではフォーマットの検証をemailのアドレスがおかしいものではないかどうかをチェックしました。

 

フォーマットの検証には、以下のようなコードを書く必要があります。

validates :email, format: { with: /<regular expression>/ }

オプションハッシュのformatキーの値としてwithをキーとして正規表現Regexp)を書くんですね。

 

正規表現の内容についてはまた他の投稿で書いていきたいと思います。

 

一意性の検証

ここが結構ややこしいところでした。

このサンプルアプリケーションでは、メールアドレス(email属性)に関して一意性(他のデータと被らない)ために一意性を検証する必要があります。

 

大文字と小文字を区別しないようにする

基本的にはvalidatesメソッドのオプションハッシュとしてuniquness: trueを加えるのですが、これでは同じアルファベットのメールアドレスの大文字と小文字を区別してしまいます。

 

例えばmasa@blog.comとMASA@BLOG.COMとMaSa@Blog.comは別のメールアドレスだと認識されます。しかし、メールアドレスは通常大文字と小文字は区別されず扱われるべきです。

 

なので、uniquness: { case_sensitive: false }とすることで大文字と小文字を区別しないようにします。

 

不慮の事故に備えて、データベース側で一意性を担保する

しかし、それだけでは十分ではありません。僕もまだ完全には理解していませんが、このようなことが起こった場合、上記の一意性の検証では不十分なのです。

  1. アリスはサンプルアプリケーションにユーザー登録します。メールアドレスはalice@wonderland.comです。

  2. アリスは誤って “Submit” を素早く2回クリックしてしまいます。そのためリクエストが2つ連続で送信されます。

  3. 次のようなことが順に発生します。リクエスト1は、検証にパスするユーザーをメモリー上に作成します。リクエスト2でも同じことが起きます。リクエスト1のユーザーが保存され、リクエスト2のユーザーも保存されます。

  4. この結果、一意性の検証が行われているにもかかわらず、同じメールアドレスを持つ2つのユーザーレコードが作成されてしまいます。

上のシナリオが信じがたいもののように思えるかもしれませんが、どうか信じてください。RailsのWebサイトでは、トラフィックが多いときにこのような問題が発生する可能性があるのです (筆者もこれを理解するのに苦労しました)。

 

僕は「は?どゆこと?」って一瞬思いましたが謙虚に「このようなことが起こりうるのだ」と受け止め、その対策を学ぶことにしました。

 

対策方法はデータベース側で一意性を担保することです。Railsのアプリ側のバリデーションで一意性を担保できないのであれば、データベースにやらせようじゃないかということですね、納得がいきます。

 

 

具体的な方法は、データベースのemail属性にインデックス(索引)を貼り付け、その索引に一意性をもたせます。

 

rails generate migration add_index_to_users_email

マイグレーションファイルをジェネレートし、そのマイグレーションファイルに

    add_index :users, :email, unique: true

と追加するだけです。

 

全てのemail属性を小文字にすることで完璧に一意性を保つ

と言っても、これだけではまだ課題が残っているようです。チュートリアルによると

それは、いくつかのデータベースのアダプタが、常に大文字小文字を区別するインデックス を使っているとは限らない問題への対処です。

 

僕は

f:id:masaincebu:20170817103917j:plain

ってなりました。

 

やるやん、って思いました(上から目線 part2)。

この抜け目ない感じ好きです。

 

具体的な対策方法は、ActiveRecordのコールバックメソッドという、ある特定の時点で呼び出されるメソッドを利用して、どんなメールアドレスでも小文字に変換して登録してしまう、というものでした。

 

オブジェクト(email属性値)が保存される前に小文字にするので、before_saveメソッドを使います。

  before_save { self.email = email.downcase }

 

ここで右辺のemail.downcaseは、self.email.downcaseと同じ意味です。Rubyではselfを省略できることがあります。今回はUserモデルの中では右辺のselfは省略できます。

 

 

セキュアなパスワードを追加する

モデルを作り、最低限すべきユーザーの検証を一通り終えたあとは、パスワードをセキュアに保管する必要があります。

 

パスワードのハッシュ化

セキュアに保管するということは、つまりパスワードをハッシュ化するということです。ハッシュ化とはRubyのデータ構造の「ハッシュ」ということではなく、「ハッシュ関数を使って、入力されたデータを元に戻せない (不可逆な) データにする処理を指します」。

 

セキュアなパスワードの実装はいたってシンプル。

has_secure_passwordというRailsのメソッドをモデルに関するクラス定義内に書くだけ。ただし、モデル内にpassword_digestという属性が含まれていることが条件です。

 

また、has_secure_passwordを使ってパスワードをハッシュ化するには最先端のハッシュ関数であるbcryptというGemが必要なので注意。

 

authenticateメソッド

has_secure_passwordは、パスワードをハッシュ化させるだけでなく「引数に渡された文字列 (パスワード) をハッシュ化した値と、データベース内にあるpassword_digestカラムの値を比較」するという便利な機能があります。

 

 このauthenticateメソッドは引数とパスワードが一致するとユーザーを返し、間違っているとfalseを返します。

 

 

感想

さて、4章〜6章までの内容を二つの記事にわたって書いてきました。

Railsチュートリアルは丁寧に説明されている上、演習も多く、テストも徹底しているので分量が多く、時間がかかってしまいますが、とても良質な教材だと思っているので、最後までしっかり学習していきたいと思っています。

 

ではまた。