WEB + DB PRESS 28号の【特集】Ruby on Rails入門の続き(2日目)

はじめに

まずは、前回のKPTのTRYを確認

  • そこそこな時間集中して取り組むので飲み物やおやつを側におきたい
    • 紅茶と味カレーを用意した。HHK Lite2は周りのスペースを有効に使えていい感じ。
  • rails.vimの機能をもっと使いたい(常にrails.vimで楽できないかを意識する)
    • よし、意識した。
  • ジェネレーターを多用したい
    • 使うでしょう。
  • ソースコードの中を解析したい
    • ちょっと優先度を下げて、次回以降か勉強会で実施したい。
  • やっている最中の事を文章に書き出すと自分の考えていることや反省点がわかったので続けていきたい。(見える化)
    • 今、書き始めた。

チートシートを用意して、本を開いた。

第二章 Railsの構成と仕組みについて

Railsフルスタックフレームワーク
Javaでよくある複数フレームワークやライブラリの組み合わせとは異なる。

主なRailsに付属するモジュールは下記の5つだとのこと。

  1. ActiveRecord
  2. ActionPack
  3. ActiveSupport
  4. ActionMailer
  5. ActiveWebService

はじめの4つはよく聞いたり、役目はわかるけど、最後のActiveWebServiceがよく知らない。

解説では

Webサービス,主にXML-RPCを行うために使うも
のです.RailsからWebサービスを利用する場合には,
このライブラリを使うことが推奨されています

だって。AjaxとかPrototype.jsに使用するのだろうか。ActiveWebServiceは実際に使用する時に覚える事にする。


あと、RailsのbreakpointはdRubyの分散環境が利用されているらしい。
仕組みは於いておいて、利用してみたいなぁ。


さっそく簡単なアプリを作成することに。


機能は2つ。

  • 会員ログイン
  • 会員登録


面白そう。

データベースの準備

んー。新しくrailsコマンドでアプリ作ってくださいとか書いていないけど、昨日のdemoとは別に作ろう。

rails wdpress

vimでdatabase.ymlを編集する。
触れられてはいないが、開発用と運用用のスキーマを同じものでやるらしい。>database.ymlを見る限りでは*1

development:
  adapter: mysql
  database: wdpress
  username: root
  password:
  host: localhost

test:
  adapter: mysql
  database: wdpress_test
  username: root
  password:
  host: localhost

production:
  adapter: mysql
  database: wdpress
  username: root
  password: 
  host: localhost

PDFからSQLをコピーして中を確認し、スキーマとテーブルを作成した。

create table users (
id int unsigned not null auto_increment,
name varchar(20) not null,
password varchar(20) not null,
primary key (id)
);

大事なこと(規約)

  • 主キーはidかつオートナンバー
  • テーブル名は複数

テーブル名の複数形を知るには、gorouさんのrails2u.comで変換してくれるCGIがあったのですが消えていた。

確か、昨日rails2u.comのサーバー構築し直したってRSSみた気がする…
http://subtech.g.hatena.ne.jp/secondlife/20071027/1193411269

微妙な *.rails2u.com のやつは全部止めてみた。

やっぱり。

RailsActiveSupportで拡張*2したStringクラスのpluralizeメソッドが同じ役割をしてくれると思ったので、irbで確かめてみる。
※追記:server/consoleの方が便利*3


最初にgemsとactiverecordをロードし忘れてエラーってしまった。
やりなおしたら、できた。

C:\InstantRails\rails_apps>irb -r rubygems -r active_support
irb(main):001:0> "user".pluralize
=> "users"
irb(main):002:0> "person".pluralize
=> "people"
irb(main):003:0> exit

これをvimのirbっぽいやつでやれたら便利だろうなぁ。
それはまた今後試してみる。

scaffoldで会員登録機能を作る

scafforld発射。

ruby script/generate scaffold user
      exists  app/controllers/
      exists  app/helpers/
      exists  app/views/users
      exists  app/views/layouts/
      exists  test/functional/
  dependency  model
      exists    app/models/
      exists    test/unit/
      exists    test/fixtures/
   identical    app/models/user.rb
   identical    test/unit/user_test.rb
   identical    test/fixtures/users.yml
      create  app/views/users/_form.rhtml
      create  app/views/users/list.rhtml
      create  app/views/users/show.rhtml
      create  app/views/users/new.rhtml
      create  app/views/users/edit.rhtml
      create  app/controllers/users_controller.rb
      create  test/functional/users_controller_test.rb
      create  app/helpers/users_helper.rb
      create  app/views/layouts/users.rhtml
      create  public/stylesheets/scaffold.css


一回目は失敗した。なぜかと言うと、database.ymlを保存し忘れていたから。
とほほ…

: w

次はvalidation

会員情報のvalidationを設定するらしい。

  • アカウントを一意にする
  • アカウントの文字列長を制限
  • パスワードの文字列長を制限

ここでもrails.vimを使って、モデルにvalidationを書く。
strutsではActionForm名をvalidation.xmlでvalidationを設定したが、railsではモデルに書くんですね。


結構これ、ポイントだよなー。


strutsではvalidation.xml複数に分けて、includeして分散化することあるけど、モデルごとに集約できれば、異なる人が
validationが作成する時にActionFormが異なっていて、モデルで微妙に作成しているvalidationが違うよ。ってことなくなるなぁ。

class User < ActiveRecord::Base
  validate_uniqueness_of :name     , :on     => :create
  validate_length_of     :name     , :within => 3..40
  validate_length_of     :password , :within => 5..40
end

validationを追記した。*4
※これはタイポしたまんまの例です。

バリデーションルールも調べた。
『Ruby on Rails入門 優しいRailsの育て方』の77ページに載っていた。

  • validates_uniqueness_of
    • カラムの値がユニークである事を検証する(一意性)
    • オプション1 :scope 指定されたカラムの値が同じオブジェクト間のみユニークであることを保証する
    • オプション2 :case_sensitive 大文字小文字を無視したい場合に利用。falseにする。
  • validates_length_of
    • カラムの値が指定された長さであることを検証する(長さ)
    • オプション 「最大値」「最小値」「長さ」「範囲」指定ができるらしい。なるほど今回は範囲を使うわけね。

Railsセミナーで増井さん(masuidrive)が言っていた「Rubyらしい書き方。RailsRubyの特性を活かしている」っていうのは
こういう書き方のことなんだろうか。

  validates_length_of :password , :within => 5..40

Rangeでパラメータをハッシュにセットしているが、可視性がやたらよくて、わかりやすい


日本語っぽく、前から順に読んでみた。


「長さチェックバリデーションを使って、パスワードカラムを範囲が5〜40のものチェック」


これで、CRUD&Validationができているはずなので、動作確認

動作確認と修正

起動する

ruby script/server

アクセスしたらエラーった。
http://localhost:3000/users

NoMethodError in UsersController#index 
undefined method `validate_uniqueness_of' for User:Class


タイポしたかなぁ。確認する
わかった。「s」が抜けている。


正しくは

class User < ActiveRecord::Base
  validates_uniqueness_of :name     , :on     => :create
  validates_length_of     :name     , :within => 3..40
  validates_length_of     :password , :within => 5..40
end


よし、動いた。動作確認おーけー。


ログイン機能を作る


ログイン機能も簡単に作れるようです。

Accountコントローラーをジェネレーターで作るらしい。
まずは、ジェネレーターで作ってから、解説を読みながらソースを読むことにする。

ruby script/generate controller Account login logout welcome
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/account
      exists  test/functional/
      create  app/controllers/account_controller.rb
      create  test/functional/account_controller_test.rb
      create  app/helpers/account_helper.rb
      create  app/views/account/login.rhtml
      create  app/views/account/logout.rhtml
      create  app/views/account/welcome.rhtml

コントローラージェネレーターを使って、Accountコントローラーを作成しAccountコントローラーに結びつく、
「login」「logout」「welcome」ビューを作成するということらしい。

  • ジェネレータのパラメータの注意
    • コントローラー名は大文字で始める
    • ビューは小文字で

コントローラーにロジックを書く。

rails.vimで移動するときにタブで補完が効いた!

:Rcontroller account


ジェネレータで生成されたコントローラーのソースコード

class AccountController < ApplicationController

  def login
  end

  def logout
  end

  def welcome
  end
end

よし、書いた。
タイポしていなければ、OKだと思う。*5
自分なりに、コメントもつけてみた。コメントは後で変更するするかもしれない。


ロジックを追加したコントローラーのソースコード

class AccountController < ApplicationController
  # モデルとしてUserクラスと使うことを宣言する
  model :user
  # アクションを実行する前に呼ぶコールバックを指定する
  before_filter :session_authenticate , :except => %w(login authenticate)

  def login
  end

  def logout
    # セッションのログイン情報を消す
    @session ["me"] = nil
  end

  def welcome
    # Viewのwelcomeで参照して使用できるようにする為に
    # sessionに格納されている、ログイン情報をインスタンス変数のmeにセットする
    @me = @session["me"]
  end

  def authenticate
    # POSTで受けとった、「ログイン名」と「ログインパスワード」をUserモデルで
    # 認証メソッドでログイン認証を行う
    if user = User.authenticate(@params["me"]["name"], @params["me"]["password"])
      # ログイン情報が一致したら、セッションにログイン情報(userオブジェクト)をセットして
      @session["me"] = user
      # welcomeページへ遷移する
      redirect_to :action => "welcome"
    else
      # ログイン情報が一致しなければエラーメッセージをセットして
      flash["alert"] = "Login failed!"
      # ログイン画面へ遷移する(再認証を行う)
      redirect_to :action => "login"
    end
  end

private

  #-------------------------------------------
  # セッションのログイン情報の存在チェック
  #-------------------------------------------
  def session_authenticate
    # セッションにログイン情報がセットされていない場合は
    unless @session["me"]
      # ログインアクションへリダイレクトする
      redirect_to :action => "login"
      # 失敗したら以降のactionは呼ばれないようにする為に、最後にfalseをセットする
      false
    end
  end
end


このままの勢いで、Viewを書く。


まずはlogin.rhtml

<h1>Account#login</h1>
<p>Find me in app/views/account/login.rhtml</p>

<%= form_tag :action => "authenticate" %>
name: <%= text_field "me", "name" %><br/>
password: <%= password_field "me", "password" %><br/>

<%= submit_tag "Login" %>
<%= end_form_tag %>
<%= h @flash["alert"] %>

続いてwelcome.rhtml

<h1>Account#welcome</h1>
<p>Find me in app/views/account/welcome.rhtml</p>

welcome, <%= h @me.name %>!


最後にモデルにログイン認証チェックのメソッドを作る

class User < ActiveRecord::Base
  validates_uniqueness_of :name     , :on     => :create
  validates_length_of     :name     , :within => 3..40
  validates_length_of     :password , :within => 5..40

  def self.authenticate(name, password)
    find_first([ "name = ? and password = ?", name, password ])
  end
end


よし、これで全部か??
見落としていないだろうか・・・

これでタイポがなかったら、自分へのご褒美として「ガリガリ君」を買うよ。

動作確認

ruby script/server

そして、ログインページへアクセスする。


コントローラー51行目でエラーが起きた。


やってしまった。「.」と「,」を打ち間違えた…
ご褒美はなしだな。


修正したら、ビューは表示された。


よし、ログイン認証・ログアウトもできた。

  • ログイン画面

  • ログイン後Welcome画面

  • ログアウト画面(ログアウト処理が行われた)


解説を読みながらソースコードを読み解く

続きは明日にしようと・・・

*1:この例題では開発環境と運用環境までは踏み込んでないだけだからかも。

*2:拡張であっている??リフレクション??Railsが書き換えているのはわかるけど、Rubyのリフレクションという機能がまだ理解できていない

*3:id:secondlifeさんのコメントから補足。ありがとうございました。

*4:タイポした事を後で気づいた。

*5:タイポしてしまった。ソースコードはタイポ修正済み