発展課題の解答を用意してみました。
一解答例にすぎませんが、参考にしてください。

急いで作ったので間違ってたらツッコんでください。

:per_page に指定した数字に合わせて、前のン件, 次のン件としてみよう。

ActionController::Pagination::Paginator#items_per_pageを使って、
$ vi app/views/bbs/list.rhtml

<%= link_to "前の#{@comment_pages.items_per_page}", { :page => @comment_pages.current.previous }
            if @comment_pages.current.previous %>
<%= link_to "次の#{@comment_pages.items_per_page}", { :page => @comment_pages.current.next }
            if @comment_pages.current.next %>

リファレンスマニュアルでは、Attributesのところにあって、なんの説明もないので、見つけにくいかもしれません。

http://localhost:3000/bbs/edit/1 と言う風に直接アクセスすると、誰の発言でも自由に編集できてしまう。コントローラから使ってないメソッドを削除して、編集ができないようにしよう。

app/controllers/bbs_controller.rb中の、edit,show,updateメソッドを削除し、verify の行にある、:updateも消す。
app/views/bbs/edit.rhtmlapp/views/bbs/show.rhtml二つのファイルも消します。

投稿フォームを /list/bbs の一番上に移動して、直接投稿できるようにしてみよう。

app/views/bbs/list.rhtmlの上の方に、

<div>
  <%= start_form_tag :action => 'create' %>
    <%= render :partial => 'form' %>
    <%= submit_tag "投稿" %>
  <%= end_form_tag %>
</div>

を追加。

# app/controllers/bbs_controller.rb
   def list
     @comment_pages, @comments = paginate :comments, :per_page => 10, :order => 'created_at desc'
     @comment ||= Comment.new # ||= は、create から呼ばれた時に@commentを上書きしないため。
   end
  
   def create
     @comment = Comment.new(params[:comment])
     if @comment.save
       flash[:notice] = 'Comment was successfully created.'
       redirect_to :action => 'list'
     else
       list # この行追加
       render :action => 'list' # エラーがあった場合の戻り先を変更
     end
   end

   # new アクションは削除

app/views/bbs/new.rhtmlも消してしまいましょう。

Commentにvalidatesを追加して、必須項目を決めよう。

title, body, name を必須項目に、email, homapage は空じゃなかったら、正規表現で判定をしています。 email の方の正規表現は、 validates_format_of のサンプルコードから、homepageの方は先頭がhttp://またはhttps://かをチェックしています。 凝りたい人はどうぞ。

# app/models/comment.rb
class Comment < ActiveRecord::Base
  validates_presence_of [:title, :body, :name]
  validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :if => proc{|comment| !comment.email.blank?}
  validates_format_of :homepage, :with => %r|^https?://|, :if => proc{|comment| !comment.homepage.blank?}
end

また、次のようなテストコードを書いて、

$ rake test:units

を実行することで、正しそうに動いているかチェックできます。

テストコード↓を書いてから、実装↑を書くと、TDD.

# test/unit/comment_test.rb
require File.dirname(__FILE__) + '/../test_helper'
  
class CommentTest < Test::Unit::TestCase
  def setup
    @comment = Comment.new(:title => 'hi!', :body => 'hello!', :name => 'bob', :email => "", :homepage => "")
  end
  
  def test_validation
    assert_valid @comment
  end
  
  def test_presence_on_title
    @comment.title = nil
    @comment.save
    assert_not_nil @comment.errors.on(:title)
  end
  
  def test_presence_on_body
    @comment.body = nil
    @comment.save
    assert_not_nil @comment.errors.on(:body)
  end
  
  def test_presence_on_name
    @comment.name = nil
    @comment.save
    assert_not_nil @comment.errors.on(:name)
  end
  
  def test_email
    @comment.email = 'bob@rails6.com'
    @comment.save
    assert_valid @comment
  
    @comment.email = 'bob.rails6.com'
    @comment.save
    assert_not_nil @comment.errors.on(:email)
  end
  
  def test_homepage
    @comment.homepage = 'https://bob.rails6.com'
    @comment.save
    assert_valid @comment
  
    @comment.homepage = 'http://bob.rails6.com'
    @comment.save
    assert_valid @comment
  
    @comment.homepage = 'ftp://bob.rails6.com'
    @comment.save
    assert_not_nil @comment.errors.on(:homepage)
  end
end

削除キーを入力しないと削除できないようにしてみよう。

まず、migration でdelete_key というカラムを追加します。

$ script/generate migration AddDeleteKey
      exists  db/migrate
      create  db/migrate/004_add_delete_key.rb
# vi db/migrate/004_add_delete_key.rb
class AddDeleteKey < ActiveRecord::Migration
  def self.up
    add_column :comments, :delete_key, :string
  end
  
  def self.down
    remove_column :comments, :delete_key
  end
end

$ rake db:migrate

次に、投稿フォームに、削除キー入力フィールドを作り、

<!-- app/views/bbs/_form.rhtml -->
<p><label for="comment_delete_key">削除キー</label><br/>
<%= text_field 'comment', 'delete_key'  %></p>

削除ボタンのとなりにテキストフィールドを追加。

<!-- app/views/bbs/list.rhtml -->
     <td>投稿日時:<%= comment.created_at.strftime("%Y/%m/%d %H:%M:%S") %></td>
     <td>
       <%= start_form_tag :action => 'destroy', :id => comment %>
       <%= text_field_tag 'delete_key' %>
       <%= submit_tag "削除" %>
       <%= end_form_tag %>
     </td>

その値を受けて、保存された削除キーと、入力された削除キーが一致しないとdestroyしないように、コントローラを書き換える。

# app/controllers/bbs_controller.rb
   def destroy
     c = Comment.find(params[:id])
     c.destroy if !c.delete_key.blank? && c.delete_key == params[:delete_key]
     redirect_to :action => 'list'
   end

今回のテストコードは、コントローラのテストなので、以下の通り。

まず、テストデータを二つ用意。 片方は、削除キー’bob123’、片方は削除キーなし(削除できない)

# test/fixtures/comments.rb
bob:
  id: 1
  title: hi
  body: hello
  name: bob
  delete_key: bob123
undeletable:
  id: 2
  title: hi
  body: hello
  name: bob
  delete_key: ''
# test/functional/bbs_controller_test.rb
require File.dirname(__FILE__) + '/../test_helper'
require 'bbs_controller'
  
# Re-raise errors caught by the controller.
class BbsController; def rescue_action(e) raise e end; end
  
class BbsControllerTest < Test::Unit::TestCase
  # テスト用データである、fixture をロード。(上で書いたやつ)
  fixtures :comments
  
  # test_* の前に実行される。
  def setup
    @controller = BbsController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
  end
  
  # 削除キーを指定しないと削除されない
  def test_destroy_without_delete_key
    id = comments(:bob).id
    assert_not_nil Comment.find(id)
    post :destroy, :id => id
    assert_not_nil Comment.find(id)
  end
  
  # 削除キーを指定すると削除される
  def test_destroy_with_delete_key
    id = comments(:bob).id
    assert_not_nil Comment.find(id)
    post :destroy, :id => id, :delete_key => comments(:bob).delete_key
    assert_raise(ActiveRecord::RecordNotFound) {
      Comment.find(id)
    }
  end
  
  # 削除キーが空のレコードは、空の削除キーで削除できない
  def test_destroy_record_having_empty_delete_key
    id = comments(:undeletable).id
    assert_not_nil Comment.find(id)
    post :destroy, :id => id, :delete_key => comments(:undeletable).delete_key
    assert_not_nil Comment.find(id)
  end
end

Rails勉強会@関西6

投稿者 nanki 2007-01-22 05:51:00 GMT

土曜日は、Rails勉強会に行ってきました。

今回は、初心者レッスンの担当。

朝8時に起きて準備するはずが、前日5時くらいまで(というか、三時間前まで)いろいろやっていたので、目覚まし時計の思いも届かず、2時間寝坊。 ひげを剃るのも会場で。 11時到着に合わせて計算していたので、なんとか12時前にはたどりつけたけど、あせったなぁ。

発表は、なんとか終了。 途中で場内を回ってみると、思いの外、進み具合がバラバラだったので、最後の部分は演習時間を延長してカット。

なにから解放されたのか、昼御飯は食べる気も起こらなかったのに懇親会まではずっとお腹が鳴りっぱなしなのでした。

終わってから気がついたけど、初期のRails勉強会のように、教科書になるような本を写経とかでもいいんじゃないか、と思えてきた。
毎回、目的のテーマにたどり着くまでに、ActiveRecord, scaffold の道を進まねばならないので、なんとも大変。 三回目なので、シリーズ化はまだ止められそうだし。

「RailsでXML-DBにチャレンジ」by モバイル通信兵さん

おもしろそう。 DB2要求されるのが、ちょっとハードル高め。 使えそうな場所を探してみようっと。

「Rubyist SNS の実装について」by かずひこさん

最近コミットできてなくて申し訳ないです。 会場で、updateしていつものように一人で友達作って・・・
この作業がいつも寂しい。

「rails tips」by 氏久さん

<%=h %>は、エディタのショートカットで出せるように設定していて、<%= %> にするには、hを削らないといけない、という安全スイッチがあるので、少人数での開発の場合はまあいいかな。 jitor は楽しそう。

「やさしいRailsの育て方(仮題)」by 西和則さん

DBモデリング?のお話。 火星エステ理論なる判別法で、本質的な項目を洗い出せ!

火星の鈴木さん(だっけ?)で何故か火星年代記を思い出してしまった。

懇親会は、内装がゴージャスなお店。

二次会では、非カラオケチームに合流しようとして連絡をしたら、途中で携帯の電池が切れてしまって、結局合流できなかった・・・。 今朝、僕の目を覚まそうとして、一時間に渡ってブルブルしてたはずだから、当然なのかも。

なので、はぐれ組三人で、駅前のサンマルクカフェで、お話しました。


バーコード大作戦(予告編)

投稿者 nanki 2006-07-10 16:13:00 GMT

がーん。

バーコードペディア:Webカメラでスキャンできる製品情報コミュニティ - Engadget Japanese

以前からbarcoder.nuとしてやっていたところがコンセプトをちょっと変えてみた?よう。

半年くらい天日干しにしている社内プロジェクトがあって、それがちょうどこんなコンセプトなんです。

demo QuickTime:2.4MB

というわけで頑張って今月中にでもリリースします。と書いて、自分のお尻に火をつける。

点スイッチ有限会社の最初のサービスとしてリリースできるといいな。

リンク先がないのにリンクを張るのは、早く会社のサイトを作らなきゃ、というプレッシャーです。

あ、もちR on Railsです。


Ruby勉強会@関西 10

投稿者 nanki 2006-05-16 04:28:00 GMT

今回は半分弱が初参加、60人の定員が満員という勢い。 バス一本乗り過ごして、10分ほど遅れて到着。

丸投げシリーズ(3) C

西本さんの発表。

CのライブラリをRubyから使うというもの。 * #include "ruby.h" * Init_foo(foorequire 'foo'される)の中に、クラス情報、あるいはメソッド情報をつらつらと書きつくる。 * mkmfライブラリを使って、Makefileを作る。 * makeする。 * 30倍から100倍程度の高速化ができることも。

実はなかなか踏み出せないでいた領域だけに(いつもRubyレベルでのアルゴリズムの最適化とかで逃げてる。逃げてるのか?)非常に役立つ内容だった。
さんざん最適化した上でNativeにしたら、相当速くなりそうだ。 非CygwinのWindowsコンパイラを探さないと。
大量のマクロとrb_*が次なる壁。

ゲーム用フレームワーク 「Miyako」

サイロス誠さんの発表。スライドの一枚目がCryossになってる気がしたけど。

本人曰く、女性参加者が予想外に多くて、ネタに対する反応が心配だったそうで、確かにちょっとヒヤヒヤ。
(概ね、高飛車な女性がMiyakoのなんたるかを博士&助手に説明するという内容)
僕は前のほうだったのであまり気にならなかったけど、文字のサイズが小さくて後ろのほうの人は見えなかったそうで、途中から、普段勉強会の会計を担当してくださっている、あゆさんが声優をやって、別の意味で盛り上がっていた。

肝心の内容のほうは、発表の形式がシナリオ形式だったので、記憶に残ってるのは主にデモ中心。
紙芝居的なゲーム(呼び方知らない)を作るなら一考の価値あり、だと思います。 Miyako自体のコードはあまりRubyっぽくないので、Rubyっぽい実装になって欲しい。 あと、プレゼンツールとしての使い道に期待する人もいるようで、今後のバージョンアップはそっちの方向に向かうとか。

それなら、フルスクリーン対応は必須になるね。

ライトニングトーク Ruby on Google SketchUP

兼重さんの発表。Googleが発表したブツを早速。 このスピード感がライトニング。 デモは、SketchUpでぐいぐい立体を作って(ここはRubyと関係ない)、PureImage(西本さん作)で作ったテクスチャーを貼り付けるというもの。

西本さん、丸投げばかりじゃなかったのね。
PureImageの回は参加できなかったのですっかり忘れてた。

Rubyと関係ないFollow Meツールがうまくいかないのでちょっとヒヤッとしてた。

Ruby 初級者向けレッスン 7 -ブロックとイテレータ-

最後はかずひこさん・コウザイさんによる発表。 もう七回目になるのか。僕が最初に参加したときは一回目だったかなぁ。 内容は、イテレータ・ブロックを使ってみる、というもの。 今回は、時間がよっぽど足りなかったらしく、途中のキリのいいところ?で終わってしまった。 * injectの引数省略知らなかった * 僕、Ruby歴2年半くらいなんですが・・・ * おじさん向け課題:パスカルの三角形

(1..9).inject([1]){|r,| p r;(r+[0]).zip([0]+r).map{|a| a[0]+a[1]}}
* いまいさんの解答、enum_consとか勉強になります。 * to_procのやつ(.map(&:+))、引数一個以上のメソッドを渡しても大丈夫なのか。むむ。 * &:+が梅干たべたパーマの外国人みたい。 * 偶然か必然か、途中までまったく一緒・・・。1..9 とか、10にすると文字が一文字増えるからだよなぁ。

小波ゼミでは最初からRubyを教え込まれるので、卒業するころにはRuby暦3年とか4年とかざらにいるみたいです。恐るべし。

発表の形式として、壇上で先生と生徒の対話みたいな形がとられていた?(→意図したものだったようで)のは聞いてる方としては入りやすいんじゃないかと思う。
Ruby/SDL上での先生と生徒の対話はちょっと食傷気味だけど。
後半の応用編(ログ解析)は次回に持ち越し?

プリンセスラインに対する京都女子大学の絶大なる影響力(ピポパで臨時便)を目の当たりにしつつ、懇親会へ。

懇親会の内容は日記になるので略。

参考:

第10回 Ruby勉強会@関西
Miyako メインサイト


The Game of Life - ライフゲーム占い

投稿者 nanki 2006-02-09 18:22:00 GMT

人生ゲームではなくて、コンウェイのライフゲーム。

ライフゲームというのは二次元の升目上で展開されるセルオートマトンで、 * 空のセルの周り(8近傍)に3個の生きたセルがあると生まれる。 * 生きたセルの周りに2ないし3個の生きたセ ルがある場合は何も起こらない。それ以外は死ぬ。

という簡単なルールの成り立つ平面上で、初期パターンの発展を見守るという雅なあそび。

セルオートマトンの見せる複雑な挙動に魅せられて、中学生とか高校生のころなど、暇なときによくやった。

大学に入って図書館で、「ライフゲームの宇宙」なる本を見つけて来て、ほくほく顔で借りてきたのを覚えている。

なぜ今さらこんなことを書いたのかというと、会社のロゴ候補があまりにもライフゲームの生命体に見えたので、先行きを占うため(?)に実行してみたのだ。

結果は、174サイクル後にブロック、池、ブリンカーを残して安定。長寿型と言われる「ダイハード」よりも長い寿命なのでまずまずの結果。一瞬で消えてなくなったら嫌だもの。

たぶん、ライフゲーム界のスゴイ人になると、こんな情報だけで元になったロゴを特定できたりするんだろうなあ・・・

WindowsだとQLIFEというソフトが爆速でオススメ。 淡い思い出によると、何かしら上限があるらしくて、max とかが途中でほつれちゃった気がする。

参考:

ライフゲーム - Wikipedia


ThinkPad T43

投稿者 nanki 2006-01-26 03:36:00 GMT

weekday も終わりに近づいた日の朝、衝動買いしてしまった。

営業日で二日くらいしかかかってないんじゃないかな。早い。

それはそうと、ThinkPadのキーボード、Windowsキーはないし、ESCは遠いし、Fnキーが慣れない位置にあるし、慣れるまで結構苦労しそう。

しかし、F1がESCと半角/全角の間にあるのにはさすがに我慢できなくて、窓使いの憂鬱を入れてしまった。

とりあえず、ESCの位置とかを変えてみたり、Alt+H,Alt+Lでブラウザの戻る、進むをやってみたりと、なかなか快適な状態になった。

あと、無変換あたりをESCにしたりね。

早くこのキー配列に慣れたくて、デスクトップPCもThinkPadのキーボードで操作できるようにしようかと思ったんだけど、どこドアの通信経路がセキュアじゃないので躊躇。


先日、HaagenDazsが新フレーバー、ココナツマカロンを出したらしい。

マカロンというと僕は実物を見たことがなくて、まっさきに頭に浮かんだのはケーブルタートルだったけど、あながち間違ってはいないようだ。 (マカロン)

それはそれとして、昨年夏、同じくHaagenDazsがパンナコッタ&ラズベリーを出した時にも、パンナコッタってなんだ?という騒動が僕の周りで起こった。

  1. まず、パンナコッタがピンとこない
  2. 僕が子供の頃にはそんなお菓子はなかった。
  3. ババロアとの違いがわからない
    • パンナコッタの作り方とババロアの作り方に大きな差はない
  4. ムースも作り方が一緒
    • 調査の途中で、ムースの作り方も根本的に違いがないことがわかる

さて困った。

これについて、数人の友人と話しあった結果、ついに答えらしきものにたどり着いた。

パンナコッタ≒ババロア⊂ムース

パンナコッタはイタリア語で、パンナ(クリーム)コッタ(火を通す)。

ババロアは、フランス語でbavarois、英語ではBavarian cream、どちらも、バイエルンの(クリーム)という意味。 ドイツで働いていたフランスの菓子職人が命名したそうな。

ムースは「泡」が語源。

こんな感じで、同じものを指す別の言葉と考えても矛盾しない。 発祥地が違うのに同じもの、というのはちょっと乱暴だけど。

ムースだけはお菓子以外にも登場するので、より広義な言葉だと考えるのが適当。 オマール海老のムースはおいしそうだけど、オマール海老のババロアはふた味くらい違う、でしょ?

日常生活においては、ムースとババロア、パンナコッタの違いは、煮物とおでんの違いと大して変わらない、という認識で支障がないという結論に達したのでした。

めでたしめでたし。

(誰か真相を知ってたら教えて!)

「おしることぜんざいは何が違うか?」につづ・・・かない。


firefox

投稿者 nanki 2005-12-06 20:04:00 GMT

firefox に乗り換えた。 ちょっと前から、ajaxっぽいサイトはfirefox で開いたりしていたのだが、今日ついにsleipnir を閉じることに。

  • SessionSaver2
  • All-in-One Gesture

あたりのプラグインが効いた。

SessionSaver は、見ていたページの状態を次回起動時に復元してくれるもので、うさんくさく言うとセレブのマストアイテム。 Sleipnirを使ってたのも、この機能が結構決めてだった気がする。 入力中の文字とかも保存してくれたり、ネット上に書き出して、別のPCからもセッションを復元できたりもするみたい。

ジェスチャーもなかなか快適。 最初、たくさんの命令が割り当てられていたので、戻る、進む、新しいタブ のみとした。


揮発性のアイディアを収集する

投稿者 nanki 2005-11-21 08:24:00 GMT

土曜日にbabie さんとRails勉強会@京都 を行ったわけだが、結局本の方はあまり読み進まずに、雑談中心になってしまった。

その中で、babie さんが最近実行しているというメモ術が気になった。

例えば、バスに乗っている時だったり、授業中だったり、一日のうちで何回か、なにかしらひらめくことがあるのだが、メモしておかないと忘れてしまう。 あるいはメモをなくしてしまう。

そんな人(僕)に効くかもしれない方法。

ベルトに小さいペンと単語帳をぶらさげて、思いついたときにすぐメモするというもの。 文字にしてみると単純だけれども、手ぶらで歩いているときでもメモできる環境というのが重要。

メモはプライオリティ毎に分けて、これまた携帯用の分類箱?みたいなのに入れるのだとか。

それを一日に何度か見直してみる。 書いて実行することで、達成感を味方につける。 (自分でニンジンつるして走る馬みたいだ)

まずは習慣づけないと。 といいつつ、腰に装着する器具を買い忘れたよン・・・

腰リールを買うこと