RailsによるアジャイルWebアプリケーション開発 第2版 作者/アーティスト: Dave Thomas,David Heinemeier Hansson,Leon Breedt,Mike Clark,Andreas Schwarz,James Duncan Davidson,Justin Gehtland出版社/メーカー: オーム社メディア: 大型本発売日: 2007-10-26
風邪をひいてしまった。せっかく「RailsによるアジャイルWebアプリケーション開発 第2版」 を買ったのだけど、頭が痛くて内容が入ってこない。パラ読みした感じではよさそうでワクワクしています。
ユルさん に、「ニコニコ動画好きだよね」と言われてしまったので、勢いでkaeruspoon にニコニコ動画をはれるようにしてみました。【ニコニコ動画】
Rails 2.0のPRが出たので、いろいろと試してみました。まずはインストール。
sudo gem install rails --source http://gems.rubyonrails.org
あとは適当にrailsアプリを作って、Rails 2.0PRをfreezeします。
rails rails2_test
rake rails:freeze:edge TAG=rel_2-0-0_PR
これで vender/railsにRails 2.0PRが入れられて、rails2_testアプリはここのRails を使うようになります。
sexy migrationを試してみました(すごい名前だ)。
migrationを以下のように書けます。
create_table :users do |t|
t.string :name, :email
t.text :introduction
t.integer :age
end
Userモデルと、index(一覧表示)、show(ひとつの表示)、new,create(新規作成)、edit,update(更新)、destroy(削除)のアクションを持つコントローラがあったとすると、config/routes.rbに
map.resources :users
と書くだけでカッコいいURIを自動で作ってくれます。その確認はrake routesコマンドでOK。
users GET /users {:action=>"index", :controller=>"users"}
formatted_users GET /users.:format {:action=>"index", :controller=>"users"}
POST /users {:action=>"create", :controller=>"users"}
POST /users.:format {:action=>"create", :controller=>"users"}
new_user GET /users/new {:action=>"new", :controller=>"users"}
formatted_new_user GET /users/new.:format {:action=>"new", :controller=>"users"}
edit_user GET /users/:id/edit {:action=>"edit", :controller=>"users"}
formatted_edit_user GET /users/:id/edit.:format {:action=>"edit", :controller=>"users"}
user GET /users/:id {:action=>"show", :controller=>"users"}
formatted_user GET /users/:id.:format {:action=>"show", :controller=>"users"}
PUT /users/:id {:action=>"update", :controller=>"users"}
PUT /users/:id.:format {:action=>"update", :controller=>"users"}
DELETE /users/:id {:action=>"destroy", :controller=>"users"}
DELETE /users/:id.:format {:action=>"destroy", :controller=>"users"}
左から、名前、メソッド、URI、実際のアクション、になります。/usersに対してGETメソッドでアクセスすると一覧表示、同じURIに対してPOSTメソッドでデータを送ると、新しいモデルオブジェクトが作成されます。/users/:idに対してGETメソッドでひとつのデータの表示、PUTメソッドで更新、DELETEメソッドで削除されます。RESTfulでカッコいい。
さらにUserモデルに1対多でひもつくCarモデルがあった場合、config/routes.rbに
map.resources :users, :has_many => :cars
と書いてやることで、
.....省略......
user_cars GET /users/:user_id/cars {:action=>"index", :controller=>"cars"}
formatted_user_cars GET /users/:user_id/cars.:format {:action=>"index", :controller=>"cars"}
POST /users/:user_id/cars {:action=>"create", :controller=>"cars"}
POST /users/:user_id/cars.:format {:action=>"create", :controller=>"cars"}
new_user_car GET /users/:user_id/cars/new {:action=>"new", :controller=>"cars"}
formatted_new_user_car GET /users/:user_id/cars/new.:format {:action=>"new", :controller=>"cars"}
edit_user_car GET /users/:user_id/cars/:id/edit {:action=>"edit", :controller=>"cars"}
formatted_edit_user_car GET /users/:user_id/cars/:id/edit.:format {:action=>"edit", :controller=>"cars"}
user_car GET /users/:user_id/cars/:id {:action=>"show", :controller=>"cars"}
formatted_user_car GET /users/:user_id/cars/:id.:format {:action=>"show", :controller=>"cars"}
PUT /users/:user_id/cars/:id {:action=>"update", :controller=>"cars"}
PUT /users/:user_id/cars/:id.:format {:action=>"update", :controller=>"cars"}
DELETE /users/:user_id/cars/:id {:action=>"destroy", :controller=>"cars"}
DELETE /users/:user_id/cars/:id.:format {:action=>"destroy", :controller=>"cars"}
といったURIが作られます。
名前つきrouteは、モデルのインスタンスを指定してあげるだけ。
@user = User.find(1)
user_url(@user) #=> http://localhost:3000/users/1
user_path(@user) #=> /users/1
@car = @user.find(2)
user_car_path(@user, @car) #=> /users/1/cars/2
さらにすごいのは、link_toとかredirect_toとかform_for
@user = User.find(1)
link_to(h(@user.name), @user) #=> link_to h(@user.name), :controller => "users", :action => "show", :id => 1
redirect_to(@user) #=> redirect_to "/users/1"
form_for(@user) #=> action="/users/1" method="PUT"
@user = User.new
redirect_to(@user) #=> redirect_to "/users"
form_for(@user) #=> action="/users" method="POST"
オブジェクトの状態を見て、空のときとそうでないときとで、URIが変化するのだ。これはすごい。
ちなみに、carsのようなhas_many状態のrouteでは、単純にオブジエクトを指定するのではなくて名前つきroutesを使用したほうがよさそう。
viewの中で、render :partial を使うことはよくあるけど、コレクションのデータを表示するときにぼくは今までcollectionを使ってました。
<%= render :partial => "item", :collection => @items %>
partialの先でループで回すやり方もありますけど、
<%= render partial => "item_list", :object => @items %>
どちらが速いのか調べてみました。
Rendered _item (0.00498)
Rendered _item (0.00248)
Rendered _item (0.00150)
Rendered _item (0.00149)
Rendered _item (0.00148)
Rendered _item_list (0.00158)
というわけで、partial先でループで回したほうが圧倒的に速かったです。
朝、ちょっとRails 2.0PRでRspecを走らせてみたらこんなエラーが出た。
vendor/rails/activerecord/lib/../../activesupport/lib/active_support/dependencies.rb:263:in `load_missing_constant': uninitialized constant ActionView::Helpers::JavaScriptMacrosHelper (NameError)
今日、仕事から帰ってきたら調べよう。<自分メモ
複数のファイルに対して文字置換を実行するやり方
for i in * ; do cat "$i" | sed 's/AAA/BBB/g' > "$i.tmp"; cat "$i.tmp" > "$i"; rm -f "$i.tmp"; done
自分メモですね、これ。
(追記)
さらにzshだと
for i in * ; do cat =( cat "$i" | sed 's/AAA/BBB/g') > "$i"; done
朝のエラーの原因は、JavaScriptMac rosHelperがRails 2.0からpluginにキックアウトされるようになったため。Rails コアはできるだけシンプルにするつもりみたい。というわけで、単純だけどRspecの該当モジュールを呼ぶところでコメントアウトしてみた。
……んだけどまだうまく動かない。どうも、Rspecもedge版を使わないといけないようだ。なので、edge版を入れてみる。
ruby script/plugin install svn://rubyforge.org/var/svn/rspec/trunk/rspec
ruby script/plugin install svn://rubyforge.org/var/svn/rspec/trunk/rspec_on_rails
script/generate rspec を忘れずに。これで動きました。
Rspecを超いまさらながらやっています。Rspecで一番よくわからないのがMock
とstubで、いまでも両者の違いがよくわかっていないし、mockはあるオブジェクトのフリをする仮想的なオブジェクトで、stubはあるインターフェースを偽装するボックスなのかなあとも考えたりしていますが、はっきりと理解していません。
今、趣味でとあるwebアプリを作っていて、そのログイン処理の部分の仕様をRspecで書いてみました。
before(:each) do
@user = mock("user")
end
まずはUserモデルのインスタンスのフリをするmockオブジェクトをbeforeで作っておきます。Rspecの例 などを見てみると、そのmockオブジェクトやPersonクラスにとstubを定義したりしていますが、今回のログイン仕様ではUserモデルのインスタンスは作らないので用意していません。
で、ログインが成功する部分の仕様を書きます。
it "should log in" do
User.should_receive(:authenticate).with("abe@mail.com", "abe_pass").and_return(@user)
post "login", {:email => "abe@mail.com", :password => "abe_pass"}
response.should be_redirect
response.should redirect_to(index_url)
assigns[:current_user].should == @user
end
まずはUserモデルにstubを定義します。それぞれのitブロックの中で、それに関係するstubを宣言するのが、Rspecのやりかたみたいです。before部では全体に関係するようなstubだけを定義するらしい。しかも、そのときはshould_receiveメソッドではなくstub!メソッドを使って、検証はさせないようにするようです(こんな理解でいいのかな?)。
これで、正当なメールアドレスとパスワードをPOSTメソッドで送ると、indexへリダイレクトされてログイン状態になるという仕様が定義されました。あとはこれを通るようにコードを書くだけです。
ログインに失敗する部分の仕様は下記のとおり。
it "shouldn't log in" do
User.should_receive(:authenticate).with("itou@mail.com", "invalid_pass").and_return(nil)
post "login", {:email => "itou@mail.com", :password => "invalid_pass"}
response.should be_success
response.should render_template("account/login")
assigns[:current_user].should be_nil
end
言葉にすると至極あたりまえの話のような気もしますが、Rspecでは、モデルとコントローラとビューのテストはそれぞれ分離されています(Rspecでも、should have_tagでビューの検証ができてしまいますが、結合度が密になりやすそうなのであまり使わないほうがいいかもです)。結合度を疎にすることによって、コントローラの仕様を考えるときはそれに専念することができます。そして、どこかのエラーが他に影響を及ぼすのを防いでいます。例えば、ビューの仕様を変更してエラーが出たとき、そのビューの仕様をコントローラに書いているとコントローラでもエラーが出る(当たり前すぎることを書いていますね)のです。
とはいえ、Test::Unitのfunctionテストとかだと、結構普通にコントローラとビューのテストを一緒に書いちゃってます。
def test_diary
get "diary", {:id => diaries(:abe_1).id}
assert_response :success
assert_template "diary/show"
assert_select "#diary_#{diaries(:abe_1).id}" do
assert_select "#title", diaries(:abe_1).title
assert_select "#body", diaries(:abe_1).body
end
end
ここでデザイナさんが、日記の内容のIDを"#message"に変えたりすると、このテストはエラーになってしまいます。しかし、コントローラがおかしくてエラーになるわけではないのです。
さらにいうと、ここでDiaryモデルがid値からインスタンスを取得する部分の仕様を変えたときに、エラーになる可能性があります。
ここで疎結合を保つために登場するのがmockとstubなのですね。なるほど。
【ニコニコ動画】 やべー、バンピートロット2やりてー
Rails 2.0RC1が出たみたいなのでインストールしてみました。
まずはPRのunfreeze。
rake rails:unfreeze TAG=rel_2-0-0_PR
そしてRC1。
rake rails:freeze:edge TAG=rel_2-0-0_RC1
Rails 2.0のmigrationでは、以下のように timestamps を書くと、
create_table :user do |t|
t.string :name
t.timestamps
end
自動的に created_atとupdated_at を作ってくれます。
FirefoxのpluginでYSlow なるものを教えてもらった。これはおもしろい。
会社の同僚の方に一度教えてもらったのですが、うろ覚えだったので復習しておきます。
alias_method_chainは、既存のメソッドを継承クラスなんて作らなくてもオーバーライドしてくれます。メソッドの再定義と違うのは、オーバーライド前のメソッドも呼べてしまうところ。
まずは、メソッドを定義します。
class Integer
def next_with_goodby
next_without_goodby.to_s + ", and goodby"
end
alias_method_chain :next, :goodby
end
こうしておくと、
3.next #=> "4, and goodby"
となります。ポイントはnext_without_goodbyというメソッド。これがオーバーライド前のメソッドになります。
土曜日は葛西臨海公園で、みんなとバーベキューにいきました。寒かったけど、豚汁とワインであったまったら寒さは感じなくなります。途中で記憶がなくなったため、最後がどうなったのかよく覚えていません。とても楽しかったことだけは覚えているのだけど。ステーキや焼きそばも食べたらしいのですが、全然覚えていません(もったいないことをした)。どうもユルさん の話によると、みんなにご迷惑をおかけしたようです。いけませんいけません。もう大人なんだから自重しなくては。帰りはユルさん に嘘のルートを教えたらしく、気がついたら新松戸にいました。すげえ。
今日は二日酔いと謎の筋肉痛で、ずっと家にいました。ニコニコとプログラミング だけで一日が終わってしまった。
はてなダイアリーには、あるキーワードに対して自動的にリンクがはられる機能があります(ユルさん はこの機能が嫌いなのだそうです)。キーワードがはてなでは20万以上も存在しているので、ブログの記事の中にその20万のキーワードのどれかが存在しているかどうかを調べるなんてすごく大変そう(というか時間がすごくかかりそう)な印象を受けます。
で、はてなではキーワード検索用の正規表現を公開していたりします。ためしに6000文字程度の記事に検索をかけてみます。
#!/usr/bin/perl
use strict;
use warnings;
use IO::File;
use Time::HiRes;
my $k_f = IO::File->new("hatena_keywordlist", "r") or die $!;
my $keyword = $k_f->getline;
$k_f->close;
my $fh = IO::File->new("blog.txt", "r") or die $!;
my @f_lines = $fh->getlines;
$fh->close;
my $str = "";
$str .= $_ for (@f_lines);
my @list;
my $s_time = Time::HiRes::time();
$str =~ s|
($keyword)
|
push @list, $1;
|egiox;
my $e_time = Time::HiRes::time();
print "$_ " for @list;
print "\n";
print "time: ", $e_time - $s_time, "\n";
ひさしぶりに稚拙なPerl です(はてなの正規表現がRuby で使えなかったので)。で、計測結果は、
time: 0.810735940933228
でした。
正規表現は非決定性有限オートマトンを使っているそうなので、DFAで検索するようにすれば速くなりそうです。
で、Ruby で作ってみました。仕事で使うことになりそうなので、とりあえずソースの公開はやめておきます。別にすごいことをやっているわけじゃなくて、ただの決定性オートマトンだけど。20万件のキーワード、6000字ほどの記事という、Perl と同条件でやってみた計測結果は
time: 0.010049
となりました。めちゃめちゃ速くなって一安心。
たぶん、はてなも正規表現は使ってないんじゃないかなあ、という気もします。
サーバーの生き死にを監視するツールをRuby で書いてみました。いちよ、L3層とL7層の監視ができます。
#!/usr/local/bin/ruby
require 'ping'
require 'net/smtp'
require 'net/http'
unless Ping.pingecho("www.server.com", 3, "80")
Net::SMTP.start('mail.server.com', 25) {|s| s.send_mail "Subject:Server Down!!(L3)", 'kanshi-24@mail.server.com', 'oishi@example.com'}
end
begin
Net::HTTP.start("www.server.com")
rescue
Net::SMTP.start('mail.server.com', 25) {|s| s.send_mail "Subject:Server Down!!(L7)", 'kanshi-24@mail.server.com', 'oishi@example.com'}
end
【ニコニコ動画】
サーバーが壊れました。
この連休でサーバーのOSをCentOSからDebianに移行しようとしたのですが、CD-Rの読み込みがおかしくなり、電源も入らなくなってしまいました。どうも復活は無理のようです。kaeruspoon とmilook のデータはバックアップを取っておいたので、復活はできるのですが、ちょっと今はやる気がない状態。
というわけで、しばらく日記は、開発環境上で更新することになりそうです。誰かがここをいつか見ることがあるのでしょうか…。まいいや。nowaをつかって日記を書いていこうかとも思ったのだけど、どうも使う気がせず。すごくいいブログだとは思うんだけど。