小江戸の勉強会でバリデーションやったよ

小江戸らぐRails勉強会で今月はvalidationをやりました。


最初に、MVCの意味を簡単に説明して、今回は、MVCの中の
Model部分のバリデーションをやりますよーと説明したけど、省略。

で、バリデーションの意義って?

掲示板のアプリケーションを作ったとき、書き込まれた内容は、データとして保存しますが、
その投稿の中に、名前が無かったり、メールアドレスが不正だったり、文章が無かったりした場合、
データとして保存したくないですよね。
そういうときに、名前は必ず入れてもらったり、メールアドレスが正規表現通りか確認して、
データを保存して良いか判断させる為に実装できます。

と言うわけで、小江戸らぐメンバー表を作ってみましょう。

メンバー表を作るのに必要なデータ定義をみんなで考えて見ました。


まず項目を考える。

番号 項目
1 名前
2 年齢
3 酒が飲めるか
4 性別
5 自己紹介
6 メアド

で、それにカラム名と種類を定義していきました。
性別については、男か女かかける様に、validationの一部の機能を紹介したい為にあえてtext型に*1

番号 項目 カラム名 種類
1 名前 name string
2 年齢 age integer
3 酒が飲めるか drink boolean
4 性別 sex string
5 自己紹介 discription text
6 メアド mail string

と言うわけでカラム名まで決定。
じゃあ、それぞれにバリデーションをかけるのは何にしようかと話し合う。

  1. 名前とメアドは必須だよねー。
  2. 性別は男か女かで。
  3. 年齢18以上とか制限付けてもいい?
  4. 酒とかもあるし18じゃ怪しいしやっぱ20以上で
  5. 自己紹介300文字以下で。
  6. メアドはちゃんとしたメアドじゃないと通さない様に。*2
  7. メアドはサーバが存在するかのチェックもしたいよねー。
と言うわけで参加者全員で開発開始。

とりあえずよいプロジェクト名思い浮かばなかったのでmember_listとかでプロジェクト作成

$ rails member_list
  〜ファイルいっぱい作成〜
$ cd member_list

で、ruby script/generateでモデル作成。

$ ruby script/generate Model Member name:string age:integer drink:boolean \
sex:string discription:text mail:string

で、rake migrateしようとした時にそういえばDB作ってなかったよねーって事でDB作成

$ mysql -u root                       
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 54
Server version: 5.0.38-Ubuntu_0ubuntu1-log Ubuntu 7.04 distribution

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> create database member_list_development character set utf8;

と、ここで何人かの人の端末上はmysqlじゃなくてsqliteだったようで、
sqlite用にconfig/database.ymlを変えてもらう。


で、いつものようにrake migrate*3

$ rake migrate

ほんとは駄目な書き方で、ちゃんとrake db:migrateってやってくだいね。


とりあえずこれでメンバーテーブルができあがったはずなのでscaffold作成。

$ ruby script/generate scaffold Member

これで、いつもの様にruby script/serverをやって、
http://localhost:3000/membersにアクセスするとscaffoldができあがってるはず。


まだバリデーション組み込んでないので、普通に書き込めます。

最初のバリデーション

とりあえず名前の必須化を行うことに。


validates_presence_of を使って、Memberクラスのnameを必須にしてみましょう。

class Member < ActiveRecord::Base
  validates_presence_of :name
end

そして、http://localhost:3000/members/newから名前を空で作ってみると
[Name can't be blank]と警告が出て作成出来ないようになります。


でも、せっかくなので警告文を変えてあげましょう。って事で:messageを指定してみる。

class Member < ActiveRecord::Base
  validates_presence_of :name, :message =>  'は空白ではいけないのです。'
end


これで「Name は空白ではいけないのです。」と警告がでました。


で、mailも空白駄目でしたよね。って事でメールを追加。

class Member < ActiveRecord::Base
  validates_presence_of :name, :mail, :message =>  'は空白ではいけないのです。'
end

って書くとなんとmailもnameも警告を出してくれます。

  • Name は空白ではいけないのです。
  • Mail は空白ではいけないのです。

こんな風に二行。
楽ですよねー。


って説明してると勉強会の時間が無くなってきたので、
一番汎用性のあるvalidationを説明。
def validate って書くと色々できますよー。と

class Member < ActiveRecord::Base
  validates_presence_of :name, :mail, :message =>  'は空白ではいけないのです。'

  def validate
    if self.age && self.age < 20
      self.errors.add :age,'が20才以下の入会は認めません'
      return false
    end
  end
end

こんな感じ。


で、時間が無くなってきたので、validationの解説は別にして、
どこでエラーメッセージを表示してんのー?って話をさせてもらいました。

DRYな感じ

エラーメッセージの前に、app/views/member/new.rhtmlとedit.rhtmlを開いて解説。


new.rhtml

<h1>New member</h1>

<% form_tag :action => 'create' do %>
  <%= render :partial => 'form' %>
  <%= submit_tag "Create" %>
<% end %>

<%= link_to 'Back', :action => 'list' %>

edit.rhtml

<h1>Editing member</h1>

<% form_tag :action => 'update', :id => @member do %>
  <%= render :partial => 'form' %>
  <%= submit_tag 'Edit' %>
<% end %>

<%= link_to 'Show', :action => 'show', :id => @member %> |
<%= link_to 'Back', :action => 'list' %>

新規作成画面も、編集画面も共通の入力フォームは、<%= render :partial => 'form' %>で
_form.rhtmlを呼び出してる事がわかります。


というわけで、実際の項目の画面なんかは共通の部分つかってて、変更時はここだけを変えれば
済むようになってます。


Railsのこういう書き方はDRY的でかなり好きです。
で、実際の_form.rhtmlを開いてみます。

<%= error_messages_for 'member' %>

<!--[form:member]-->
<p><label for="member_name">Name</label><br/>
<%= text_field 'member', 'name'  %></p>

<p><label for="member_age">Age</label><br/>
<%= text_field 'member', 'age'  %></p>

<p><label for="member_drink">Drink</label><br/>
<select id="member_drink" name="member[drink]"><option value="false">False</option><option value="true">True</option></select></p>

<p><label for="member_sex">Sex</label><br/>
<%= text_field 'member', 'sex'  %></p>

<p><label for="member_discription">Discription</label><br/>
<%= text_area 'member', 'discription'  %></p>

<p><label for="member_mail">Mail</label><br/>
<%= text_field 'member', 'mail'  %></p>
<!--[eoform:member]-->

で、ここのerror_messages_forでエラーメッセージがあるときだけ表示させてますよーって話をしました。


controllerのクリエイトの部分もあわせて解説。
member_controller.rbのcreateメソッドだけを抜き出すとこんな感じ

1  def create
2    @member = Member.new(params[:member])
3    if @member.save
4      flash[:notice] = 'Member was successfully created.'
5      redirect_to :action => 'list'
6    else
7      render :action => 'new'
8   end
9  end

2行目で、params[:member]に入ってるデータを元に、@memberにデータを作成し、
3行目で保存にいってます。
で、今回入れたバリデーションなどの理由によってデータの保存に失敗すると、
6行目のelseにいって、newの内容を表示するrender :action => 'new'と実行されています。
その後、@memberが保存する際に発生したエラーをview側のerror_messages_forで表示しています。


で、ここで、参加してた人が、javaだとこういう例外処理を実装するのに時間がかかるけど、
railsはこういうノウハウを誰でも使えるからいいよねーって話をされてました。
わたしも賛成です。


と言う感じで今回の勉強会終了しました。

*1:それ以外の性別は?って話も出てましたw

*2:メアドの正規表現、本気で頑張るとそれだけで大変になるので、程々で

*3:rails 2.0じゃ通用しない駄目な癖