2008年1月27日日曜日

日程調整アプリ - イベント 積み残し

まだイベント登録部分しか作っていないが、ここだけでもいろんな RoR のエッセンスが詰まっていて、結構勉強になった。ただ、まだやりたいことがいくつかあって実現できていないことがある。
  • イベントのリスト表示を管理者(パスワード認証)のみに有効にする
  • 新規イベントを作成したら、メールで申請者に通知
  • メール中の URL にアクセスすることでイベントを正式登録
まぁ、どれも今はなくてもとりあえずは困らないので、積み残して後の楽しみとして取っておくことにする。

日程調整アプリ - イベント 入力チェック

さて、次に改良するのは、入力項目のチェックだ。昔は RoR などなかったので、今回のようなアプリを作る場合は、たいてい入力項目のチェックを端折るか、簡単なものだけやることになり、十分なチェックはできなかった(していなかった)。(私だけ?)
だが、 RoR はこの辺も簡単に記述できるので早速実践してみた。

まずは空白チェック。
これはかなり楽チンに実現できる。 apps/models/event.rb に
validates_presence_of :name, :desc, :owner_name, :owner_email
と書くだけだ。
これで空白チェックが行われ、空白が発見されると、ページのトップにエラーメッセージが表示されるとともに、該当箇所が赤くなって訂正を促す。これはかなりいい。
エラーメッセージはデフォルトでは英語になっているので、適宜日本語に変更してもいいが、とりあえずはそのままにする。

次はパスワードの設定などでよく使われる、同じ項目を二度入力させてチェックする入力確認だ。
今回は、イベント作成時等に主催者にメールを飛ばすことを考えているので、メアドが正しく入力されているかを確認する。
まず app/models/event.rb に以下の行を追加する
validates_confirmation_of :owner_email
そして、app/view/events/new.html.erb の owner_email の入力フィールドの次に
<%= f.text_field :owner_email_confirmation %>
と、元の名前に _confirmation をつけた入力フィールドを追加する。
これだけで入力の確認が簡単に行える。

そして最後に、uuid の自動入力 の際には先送りした uuid の重複チェックを行いたいと思う。
この uuid の性格は、イベント登録時に自動的に入力され、以後は変更されない、というものだ。
なので、新規作成時に必ず呼ばれる before_create にて、自動入力と重複チェックを行うこととした。
まず、app/view/events/new.html.erb の頭に記載した uuid の自動入力の行を削除する。
そして、app/models/event.rb に以下のように before_create を定義する。
   def before_create
self.uuid = rand(256**16).to_s(16)
while Event.find(:first, :conditions => ["uuid = ?", self.uuid]) != nil
self.uuid = rand(256**16).to_s(16)
end
end
これで間違いではないのだが、同じ記述を二度しているところがどうにもエレガントではない。C の do-while っぽい書き方をすればいいのだが、よく分からなかったのだ。
この辺に Ruby に慣れていないことの弊害が出てしまった。

調べてみたら do-while っぽく書けるらしい、ということで変更
   def before_create
begin
self.uuid = rand(256**16).to_s(16)
end while Event.find(:first, :conditions => ["uuid = ?", self.uuid]) != nil
end

日程調整アプリ - イベント uuid でアクセス

id ではなく uuid でアクセスすべく、とりあえず uuid を自動入力するようにした。今回はこの uuid を使用して実際にアクセスできるように各所を変更してみる。

まずは、コントローラの各アクションでデータベース検索している
Event.find(params[:id])
の部分。通常だと params[:id] は、1 からインクリメンタルにセットされる id がセットされている(両方とも id だが、前者はURLで指定されるもので、後者はテーブルのフィールド)が、ここが uuid に変わるため、uuid が params[:id] になっているデータを検索するように変更する必要がある。そのような場合の検索方法は以下の通り
Event.find(:first, :conditions => ["uuid = ?", params[:id]])
これで uuid をキーに検索できるようになる

次に、各テンプレートの最下行等に表示される、"Edit" 等のリンク先の変更を行う
まず、"Show" へのリンクだが、通常は @event となっている link_to の第二引数を
{:id => @event.uuid, :action => "show"}
に変更する。"Destroy" も同じだ。
次に、"Edit" へのリンクだが、通常は edit_event_path(@event) となっている部分を
edit_event_path(@event.uuid)
に変更する。
なお、app/view/events/index.html.erb においては、for 文で回しているので、@event の部分は event になる。

新規作成や修正のアクション(create/update)は、実行後に redirect_to で表示(show)のアクションに飛ぶが、これも変更が必要である。これも、先ほどの "Show" へのリンクの変更と一緒で
:id => @event.uuid, :action => "show"
に変更する。

あと一つ変更すべき場所がある。それは、修正時のページ中の form 文だ。
form の action にも id が渡されるので、これを uuid に変更する必要がある。
対応する箇所は app/view/events/edit.html.erb の form_for の部分だ。通常は @event になっている箇所を以下のように変更する。
@event, :url => {:action => :update, :id => @event.uuid}

これで、一通りイベントに対するアクションを id から uuid をキーに行えるようになった

2008年1月26日土曜日

日程調整アプリ - トップページの切り替え

RoR は、始めてみて、テストアプリを作ってみて、動かして、ブラウザから見て「あ、動いた」となるまでが非常に早い。そしてその時誰もが見るページが "Welcome aboard" などと書かれたページだ。そして、このあとどうしたらいいか、があっさりと書かれている。

そして、徐々にアプリを拡張していくが、ここから先で作る部分は基本的に controller/action/id の形式の URL でアクセスするように作成される。

… となると、アプリが出来上がってもトップページは "Welcome aboard" のままなのか???

そんなわけないので、どこかで設定が変えられるはずなのだが、実はこの時点では参考書が届いてなかったので、本屋で立ち読みして、
map.connect '', :controller => 'events', :action=> 'new'
とどこかに書けばいいらしいことが分かった。(今回は、イベントの新規作成ページをトップページにする予定)

ただ、問題は「どこかに」の部分を忘れてしまったのだった。仕方ないので、RadRails の左の小窓をつらつら見ながら、app/controller/application.rb がそれっぽい(頭に "helper :all" などと書いてあるし)ので、書いてみたが効果なし。

結局本屋で翌日また立ち読みして、config/routes.rb であることが分かったが、それでも効果がない。
結局本屋で翌日またまた立ち読みして、public/index.html を消去することが分かった。^_^;

これでトップページがちゃんと切り替わったので特に問題はないのだが、実はトップページの切り替えに関しては専用の書き方が用意されていた。
map.root :controller => 'events', :action=> 'new'

日程調整アプリ - イベント uuid 自動入力

通常 RoR では、テーブルには自動的に id フィールドが追加され、この id でアクセスするようになっている。まぁ、これはこれで便利でいいのだが、今回作成しようと思っている日程調整アプリでは、
1. 誰でも新たな日程調整が始められる
2. 他人の日程調整は簡単に覗けないようにする
という仕様にしているので、このままだとちょっと都合が悪い。

イベントごとにパスワードを設けるという方法もあるが、何でもかんでもパスワード入力を求めるのは個人的に好きではないので、イベントにUUIDを割り当て、それでアクセスする。こうすることで、1,2 の仕様を満たすこととした。(UUIDに関してはここを参照のこと)

そんな理由もあって、イベントテーブルには uuid というフィールドを設けてあったわけだが、これはユーザに入力させるものではないので、自動で入力させる必要がある。それをどうやって実現するかだが、とりあえずは、新規作成ページ(app/view/events/new.html.erb)の頭のほうに
<% @event.uuid = rand(256**16).to_s(16) %>
と書いて自動的に uuid に値を埋めるようにした。
UUID の生成方法はちゃんとした方法もあるだろうし、 plugin もあるようだが、面倒なので上のようにした。

また、同じく new.html.erb で uuid の部分を以下のように hidden フィールドにした。
<%= f.hidden_field :uuid %>
もちろん、そのすぐ上のテキスト表示はコメントアウトなりして消しておく。

ただ一点気になるのはUUIDの重複である。UUIDは一応、「重複や偶然の一致」を想定しなくていいとはなっているが、チェックできるならチェックしておきたところである。が、とりあえず今のところは先送りで… (validate とかでやるのかな?)

日程調整アプリ - イベント 作成

ざっくり仕様から、まずはイベントを登録できるようにする。

特に見た目を気にするアプリではないので、CRUD 操作で事足りる部分に関しては、可能な限り scaffold をベースに作ることにする。その点で、このイベント部分は適している。というわけで、早速 scaffold でイベント登録の部分を作る

RadRails では、下の小窓に Rails 特有の機能を簡単に実現するヘルパー(?)が用意されているが、その中の "Generator" で scaffold も作成できる。
左側のリストから scaffold を選択するが、最初に開いたときはリストが表示されない場合があるので、その際は右上の Referesh ボタンを押すと出るようだ。
右側のフィールドには引数を書く。 RoR 2.0 からこの書式が変わっているので注意が必要だ(先の記事参照)。 Event name:sring desc:text uuid:string owner_name:string owner_email:string

2008年1月17日木曜日

日程調整アプリ - ざっくり仕様

というわけで、手探りで日程調整アプリの作成にとりかかる

まずはざっくりと外部仕様を決める
  1. 誰でも新たな日程調整が始められる
  2. 他人の日程調整は簡単に覗けないようにする
  3. パスワード認証はないほうがいい
  4. 性善説に則る(他人の入力は変更できる)か?
  5. 性悪説に則る(自分の入力のみ変更できる)か?
  6. 日程の候補は、日付のみと、日付+時間と、それ以外(自由)
  7. 日付は、カレンダーから選択したい
  8. 参加者の重み付けができる(偉い人の日程を優先とか)といい
  9. 参加者は追加できる
  10. 入力候補は◎○△×から選ぶ
  11. 未入力者に催促できる
  12. 集計結果は一覧できる(主催者用はランキングも表示できたらいい)
  13. 最終結果をアナウンスできる
操作フローはこんな感じで
  1. 主催者はイベントの概要を入力する
  2. アプリは主催者にメールで主催者用URLを通知する
  3. 主催者は参加者のメアドを登録する
  4. 主催者は日程の候補を入力する
  5. アプリは参加者にメールで参加者用URLを通知する
  6. 参加者は出欠を入力する
  7. 主催者は集計結果を見る
  8. 主催者は未入力者に催促メールを送る
  9. 主催者は最終結果をメールで送る
といった感じだろうか

これらの仕様から、まずデータベースを考えてみると、以下の4つのテーブルが必要そうだ
  • イベント(event)
    • イベント名(name)
    • イベント詳細(desc)
    • 固有ID(event_uuid)
    • 主催者名(owner_name)
    • 主催者メアド(owner_email)
  • 日程候補(option)
    • イベントID(event_id)
    • 日程内容(desc)
  • 参加者(member)
    • 参加者名(name)
    • 参加者メアド(email)
    • イベントID(event_id)
  • 入力結果(attendance)
    • 参加者ID(member_id)
    • 日程候補ID(option_id)
    • 出欠(attend)
間違ってても後で簡単に変更できるようなので、とりあえずこんな感じにしておく

次回はイベント入力画面を作成してみたい

2008年1月13日日曜日

やっぱり参考書が必要か

どうにか scaffold が使えるようになって、いわゆる CRUD で用が済むアプリが簡単に作れることが分かった。しかし、実際に使えるアプリを作ろうと思ったら、もっと複雑なのでいろいろ手を加えなくてはならない。

ここまででもだいぶ RoR の凄さは体感しているが、さらに本格的なアプリを作るときにこそ RoR の真価が体感できるのだろう(と勝手に思い込む)。しかし、独学で学習していくには限度がある。ググってもチュートリアルで扱っているのはこの辺までで、もっと本格的なアプリを作るところまでは踏み込んでいない。

というわけで、参考書の助けを借りながらアプリの作成を進めていくことにする。

現時点で RoR2.0 に早くも対応した書籍はなさそうなので、評判の良さそうな本を検索して早速注文した。選んだ本は「RailsによるアジャイルWebアプリケーション開発 第2版」で、評判も良く、2.0 に対応してはいないながらも、最近内容が刷新されて第2版になったという理由で選んだ。

まだ600ページほどある内容の100ページも読んでいないが、この手の本にありがちな、インストール手順に
大量のページを費やしたり、手順だけを丁寧に説明してあるだけの入門書と違い、内部がどのように動いているかや、アプリ作成時の実態に即した注意点なども記載してあるので、非常にためになるし、またあとで参考書としても大いに活躍してくれそうである。
あと、翻訳がいいので違和感なく読めるのもいい点である。

というわけで、この本を片手に次回からアプリの作成に入る。

題材としては、仕事でもプライベートでも時々必要になるのに、なかなか痒いところに手が届くアプリがない、日程調整アプリ。
ここにほぼ近いアプリの解説があるが、微妙に要件が違っていてDBの構造も異なってくるので、一から作ってみたいと思う。

2008年1月6日日曜日

アプリがちゃんと動かない

さて、とりあえずDBを MySQL に切り替えられるようになったので、チュートリアルに従って先に進めてみた。

scaffold を使用することで、簡単な DB へのアクセスができるページが出来上がるはずだが、"Show" "Edit" 等のリンクは表示されるものの、DBの中身やフィールド名が全く表示されない。
model や controller を作ってないせいか? とか、generate scaffold する時の引数の指定の仕方が間違ったか? とか、いろいろトラブったから不整合でもおきたか? とかいろいろ考えたが、一からチュートリアル通りに作っても、やっぱり表示されない…

こういう時はやはりグーグルさんだが、引っかかるページはどれも同じ手順なのでヒントらしいヒントがなかったが、RoR の公式ページから探ったら、それっぽいページがありました。やはり、RoR 2.0 になって手順の変わった部分があるようだ。

以前は、DB を作成してそこに手動でテーブルを作成した後に generate scaffold すると、controller ができあがり、すぐアクセスできるようになった。

2.0 以降では、この手順だと失敗すると書いてある。
2.0 以降の手順は、テーブルを作らず、generate scaffold した後、rake db:create:all を行うと、まず 3 つのDB(???_development/test/production)ができる。その後 db/migrate/001_create_???s.rb にテーブルのメンバを書いてから rake db:migrate すると、それが反映されたテーブルが出来上がる。(ここが間違い:後述参照)

さて、ここまでは順調に行ったので、サーバをスタートさせてアクセスしてみる。

… やっぱり同じで、サーバは動くが "Show" 等のリンクは表示されるが、DBの中身やフィールド名は表示されない。
さていったい問題はどこにあるのか???

と思ったら、generate scaffold の引数の渡し方がおかしいことが判明。
generate scaffold Tablename field1:type1 field2:type2
のように、引数にテーブルのメンバーを渡す必要があることが分かった。つまり、db/migrate/001_create_???s.rb は手動で書くのではなく、scaffold の引数に渡すことで自動でできあがるのであった。(上記 2.0 用のページを斜め読みしてたのが敗因)

2.0 でここの手順が変わったことによって、今まで DB へのテーブルの登録を何かしらの方法で手動で行わなくてはならなかったので、DB の文法を少なからず知っている必要があったが、scaffold への引数に指定するだけで良くなったので、少しハードルを低くしたということだろう(手順は増えたが)。

とりあえずこれで次のステップに進めそうだ。

2008年1月4日金曜日

デフォルトDBを SQLite3 から MySQL に

使ったことない SQLite3 よりは、まだ使ったことがある MySQL を使いたいので、どうにかしてデフォルトのDBを MySQL に切り替えようと、ここから苦戦。

2.0.2 になる前はデフォルトが MySQL だったのでそれを SQLite3 にしようとした先人達の情報によると、方法としては幾つかあるようだ。
  1. skelton の使用を諦めて、手動で rails -d mysql する
  2. config/database.yml を手動で MySQL 用に書き換える
  3. RadRails の設定でどうにかする
  4. RoR の設定でどうにかする
1. のやり方は、RadRails(Eclipse)の使い方に慣れてないのでパス

2. のやり方は、一番原始的だけどスマートじゃないのでパス

3. のやり方は、RadRails に指定している rails の path のところをうまく細工すればいくかな? と思ってやってみた。InstantRails の ruby\bin\rails は Ruby スクリプトなので、うまく細工すれば -d mysql のオプションをつけるなどしていけると思ったが、やり方がわからず断念。同じディレクトリに rails.bat や rails.cmd といった MSDOS スクリプトもあるが、これらをいじってもダメだったので、結局諦めた

で、最後は 4. のやり方だ。
InstantRails 以下の ruby/lib/ruby/gems/1.8/gems/rails-2.0.2 を sqlite3 でサーチしたところ、lib/rails_generator/generators/applications/app/app_generator.rb 内にそれらしい記述があった。見ると、default_options :db => (ENV["RAILS_DEFAULT_DATABASE"] || "sqlite3"), という
記述になっていた。
ということは、RAILS_DEFAULT_DATABASE を mysql にしてあげればうまくいきそうだ。しかし、結局これはうまくいかなかった。うまく環境変数が渡せなかったようだ。

で、結局どうしたかというと、上記の app_generator.rb をいじって、最後の "sqlite3" の部分を "mysql" に書き換えた。とても最善の方法とは言えないが、とりあえずこれでどうにかなったので、もっといい方法が見つかるまでこれでいくことにした。

アプリ作成 … でも失敗

引き続き、ThinkITの「Ruby on Rails入門」を参考にしながら、アプリを作ってみる。

RadRailsで Rails Project を新規作成すると、コマンドラインで rails myApp を実行したのと同じ処理が行われてプロジェクトが作成される。ただし、新規作成時に "Generate Rails application skelton" のオプションを ON にしておく(デフォルトON)必要がある。

この後、バックエンドの MySQL にデータベースを作成するのだが、これはコマンドラインから実行するしかないようだ。InstantRails 下の mysql\bin に移動し、mysql -u root でインタラクティブモードで作成した。(DBは不慣れなので、実際には作成までちょっと手間取ったが… ^^;)
> 追記: 後で分かったが、InstantRails には phpMyAdmin も含まれていて、http://localhost/mysql/ でアクセスできるので、そちらでやった方が楽であった。
> 追記2: さらに別の方法として、rails の migration の機能を使う、というのもある。

ThinkITのページでは、Scaffold を生成するようになっているのだが、scaffold が良く分からない。調べてみると、DBへのアクセスを容易にしてくれるものらしい。Generator タブから作成できるようなのだが、リストが空で scaffold が選択できない。ちょっと戸惑ったが、referesh することで選択できるようになり、無事 scaffold が作成できた。

さていよいよ Servers タブからサーバを動かしてアクセスしてみると、"SQLite3::SQLException: no such table: reqs: SELECT * FROM reqs" というメッセージとともにエラー画面が表示された。ここで初めて、デフォルトDBが MySQL から SQLite3 になっていることを思い出した… ここからちょっと手間取ってしまう。

その顛末は次回

2008年1月3日木曜日

まずは環境設定

いまさらながら Ruby をいじってみたくなったのと、折角なら Ruby on Rails で何か作ってみよう、ということで、ごそごそ開始。

ただ、時期的に、RoR 2.0 が出たばかり(07/12/7)で、 2.0.2 (07/12/17)からはデフォルトDBが mysql から sqlite3 に変わってたり、Ruby も 1.9 が出た(07/12/25)りして変化が激しいので、難航しそうな臭いはプンプン。どうなることやら。

開発環境は WinXP なので、まずは、この辺 を参考にしながら、InstantRails と RadRails を入れる。

●InstantRails

これまた出たばかり(07/12/28)の 2.0 を使用。1.7 と比べて一番大きいのは、やはり RoR 2.0.2 対応でしょう。んでもって Ruby は 1.8.6 PL-111。

インストールは特に問題なし。

実行させたら、MySQL が正常にスタートしなかったが、Stop したら正常に起動するようになったので、気にしないことにする

●RadRails

Aptana というところから Aptana Studio という形で出ることになったようである。とりあえずダウソして入れる。

起動すると、オンラインアップデートをしろと言われるので指示に従うと、1.0.2 にバージョンがあがったようだ。

その後、Plugin を入れるために、Help > Software Updates > Find and Install... を選択。"Search for new features to install" を選択後、"Aptana: RadRails Development Environment" をチェックして Finish する。(Aptana のサイトでは、専用のサイトをここで登録してからそれを選択するように言われるが、そうしなくても問題なかった)

設定は、Window > Preference > Rails > Configuration > Rails Path だけ。InstantRails 以下の rails コマンドの path を指定する。あと、どのみち日本語のページを作るので、General > Workspace > Text file encoding を UTF-8 にする。

一応これでインストールは終了。