ユーザ管理[ユーザ追加]

今日の作業

テストファースト、TDDについて
ユーザ管理
1)ユーザの追加

今日の作業でわかったこと&わからなかったこと

今日は午前中テストについていろいろ調べた。
理由は今後のPJでそれらの知識が必須だから。
具体的にはテストファースト、TDD、ユニットテストについてである。
調べているうちにそれらの概念的な考え方みたいなものはわかった気がしたけど、
実際にそれをどう実現していくかについてはいろいろ例を出してるサイトを見たけど
ピンとこない感じ。
幸い現在勉強中のRailsによるアジャイルWeb開発には現在作っているユーザ管理機能のあとに
テストに関してのタスクがある。しかも他の今までやってきたタスクの3倍近い量のページ数で。
だからまずはそのタスクを確実に理解しながらこなしていきたい。
気持ちは既に「テストについて勉強したい!!」って感じになっているけど、
まずはユーザ管理をしっかり理解しつつ終わらせようと思う。

明日の作業

ユーザ管理
1)ログイン
2)アクセス制限

調べたことのまとめ

テストファーストはなぜやるのか

テストファーストとはまずテストプログラムを作る。
それからそのテストプログラムをパスするように単体プログラムをコーディングする。
その後テストを実行して正しくコーディングできたか確認する手法。

メリットとしては
事前にテスト設計を行うので、自分がどんなプログラムを作るべきかよくわかる。
単体テストを繰り返すことで早期にバグが見つかる。
早期問題解決によって後工程からの手戻りが減る。
などがある。

ユーザ管理の続き

今回ユーザ作成する際のコードはadd_user()と言う1つのアクションのみで作る。
そのためにはそのメソッドの呼び出しが初期状態の(空)のフォームなのか、
入力が完了してデータを保存するためのものなのか判別する。
その判別はブラウザからのHTTPメソッドの種類でする。
関連付けられたデータを持たないリクエストならGET送信、
フォームデータを含むリクエストはPOSTとして送られる。
controller内ではrequest属性を使用してリクエスト情報を取得できる。
リクエストの種類を確認するにはrequestのget?()メソッドかpost?()メソッドを使う。
つまりこんな感じ

  def add_user
    if request.get?
      @user = User.new
    else
      @user = User.new(params[:user])
      if @user.save
        redirect_to_index("ユーザ#{@user.name}が作成されました")
      end

if request.get?がtrueならフォームデータが存在しないので、
新しいUserオブジェクトを作成するだけ。
falseならフォームデータが存在するのでUserオブジェクトに格納し
DBに保存しようとする。
次に対応するビューを作成する。
add_user.rhtmlはこんな感じ↓

<% @page_title = "ユーザの追加" -%>
<%= error_messages_for 'user'%>
<%= form_tag %>
<table>
  <tr>
    <td>ユーザ名:</td>
	<td><%= text_field("user","name") %></td>
  </tr>
  <tr>
  	<td>パスワード:</td>
	<td><%= password_field("user","password") %></td>
  </tr>
  <tr>
  	<td></td>
	<td><input type="submit" value="ユーザを追加" /></td>
  </tr>
</table>
<%= end_form_tag %>

このときform_tagではパラメータを何も指定しない。
呼び出すアクションが指定されてないとき、
ユーザが送信したフォームデータはデフォルトで同じコントローラ内の、
そのテンプレートをレンダリングしたアクションに送り返される。
なるほど〜!
続いてユーザモデルの作成!!
これは昨日少し触れたが

まずユーザのパスワードはDB内には40文字のハッシュ文字列として格納されるが、

ユーザがフォームで入力するときは平文として入力される。

このためユーザモデルはフォームデータのパスワードを平文で保持しつつ

DBへの書き込みは時にはハッシュ化されたパスワードを使用して

処理をするようにしなければならない。

こんな感じする。
そのためにコールバック機能を使う。
まずUserクラスはusersテーブルの各列を認識しているため、
hashed_password属性を持っている。
しかしDBには平文のパスワードは格納されないのでモデル内に
読み書き可能な属性を作成。

class User < ActiveRecord::Base
  attr_accessor :password

モデルにデータを書き込むにはその前にpassword属性に格納されている
平文パスワードに基づくハッシュ値をhashed_password属性の値として
認定しておく。
この処理をコールバックによって実現する。
コールバックとは目的の作業の前後に「付随して行わせる作業」のこと。
ARではモデルオブジェクトのライフサイクルの様々な時点で呼び出せる
コールバックフックが多数定義されている。
実行されるのはモデルが検証される前、データがDBの行として保存される前
新しい行が作成された後などの時点。

http://techno.hippy.jp/apidoc/classes/ActiveRecord/Callbacks.html

まず、before_save()フックを使うことによりユーザのデータがDBの行として
保存される前の時点で平文のパスワードに対しSHA-1ハッシュ関数を適用し
その結果をhashed_password属性に格納する。
また行が保存された直後にafter_save()フックを使って平文のパスワードの
フィールドをクリアする。
これはユーザオブジェクトがその後、セッションデータに格納され、
それを何者かに見られる可能性があるため。

require "digest/sha1"
class User < ActiveRecord::Base
  attr_accessor :password
  attr_accessible :name, :password
  validates_uniqueness_of :name
  validates_presence_of :name, :password
  def before_save
    self.hashed_password = User.hash_password(self.password)
  end
  def after_save
    @password = nil
  end
  
  private
  def self.hash_password(password)
    Digest::SHA1.hexdigest(password)
  end
end

require "digest/sha1"は平文のパスワードをハッシュ形式に変換する機能を
Railsに取り込みますという意味。
hash_password(password)で実際にハッシュ形式に変換している。
詳しくは
http://www.ruby-lang.org/ja/man/?cmd=view;name=Digest%3A%3ABase

またloginコントローラのadd_user()メソッドは、
redirect_to_index()メソッドを呼び出している。
このメソッドは既にstoreコントローラで定義済みだが両方のコントローラから
アクセス可能にするにはapp/controllers/application.rbに移動させる必要がある。
このファイルはコントローラのすべてのクラスの親である
ApplicationControllerクラスが定義されているためここのメソッドは
すべてのコントローラから呼び出せる。
なるほどやっと勝手に作られるapplication.rbの役割がわかった。