2009/12/21

Clojure で Google App Engine するためのメモページ

Clojure で GAE(Google App Engine)するためのメモ。

逐次追記する予定。

SLIME

git clone git://github.com/technomancy/swank-clojure.git 現在かなり不安定っぽい。 -> @hchbaw さんに教えてもらって幸せになりました。http://twitter.com/hchbaw/statuses/6895804697

hackers-with-attitude: Interactive Programming with Clojure, Compojure, Google App Engine and Emacs のとおりにすれば、SLIME 上からインタラクティブに開発できる。さいこー!! Lisp たる必須条件だよね。

Compojure

weavejester's compojure at master - GitHub Compojure - Wikibooks, collection of open-content textbooks

メソッドマクロ

  • GET
  • POST
  • PUT
  • DELETE
  • HEADE
  • ANY どれでも ok

URL に対するバインドは : で、取得は :route-params で行う。複数のパラメータの場合は * を使う。ドメイン名でもルーティングできるらしい。

(GET "/posts/:id" ...)
(-> request :route-params :id)

(GET "/foo/*" ...)
(-> request :route-params :*)

静的ファイルをレスポンスするには serve-file を使う。ファイルは public に置く。 404 を返すには page-not-found を使い、public/404.html を用意しておく。

(defroutes main-routes
...
(GET "/*"
(or (serve-file (params :*)) :next))
(ANY "*"
(page-not-found)))

でも Google App Engine ではスタティックファイルが存在するとそちらが優先されること、サーブレットの呼び出しでは MIME タイプを web.xml で指定する必要があることから次のようにしておくのがよいと思う。

(defroutes main-routes
...
(GET "/*"
(or (serve-file "./" (params :*)) :next))
(ANY "*"
(page-not-found "404.html")))
war/
css/
style.css
img/
image.png
404.html

request が持っているもの。

  • params → (:params request)
  • cookies → (:cookies request)
  • session → (:session request)
  • flash → (:flash request)

HTML はベクタで書く。属性はマップ。 id と クラスはタグにくっつけられる。

[:h1#title "こんにちは"]
[:form#main.list
[:div.foo.bar "..."]
[:input {:type "text" :name "baz"}]]

appengine-clj

duelinmarkers's appengine-clj at master - GitHub

DataStore, UserService の簡単なライブラリ。これだけじゃ足りないので、自分で実装を追加するか、Java の API をコールすることになる。

Datastore 低レベル API

Key は他のエンティティの参照としてプロパティに保持可能。

KeyFactory.keyToString と KeyFactory.stringToKey によって Key と Web セーフな String を相互に変換できる。ポストパラメータ等には keyToString した Key を使う。

2009/12/16

Ruby on Rails で RSpec を使うときのメモ

参考

vender/gems にインストールしたいので config/environment.rb に次を書き

  config.gem('rspec', :lib => false)
config.gem('rspec-rails', :lib => false)

次を実行する。

rake gems:unpack:dependencies
rake gems:build
script/generate rspec

ちなみに config.gem の :lib は "Use :lib to specify a different name.", "To require a library be installed, but not attempt to load it, pass :lib => false" -- http://api.rubyonrails.org/classes/Rails/Configuration.html ということらしい。テスト用なら :lib => false にする。

ルートのテスト

ルートのテストでは route_to を使う。

{ :get => '/users' }.should route_to(:controller => 'users', :action => 'index')
{ :get => "/users/1" }.should route_to(:controller => 'users', :action => 'show', :id => '1')
{ :get => "/users/1/edit" }.should route_to(:controller => 'users', :action => 'edit', :id => '1')
{ :put => '/users/1' }.should route_to(:controller => 'users', :action => 'update', :id => '1')
{ :delete => '/users/1' }.should route_to(:controller => 'users', :action => 'destroy', :id => '1')

ビューのテスト

ビューのテストでは assigns[:key] でインスタンス変数を設定する。 flash[:key], params[:key], session[:key] もある。

assigns[:post] = @post = stub_model(Post,
:name => "value for name",
:title => "value for title",
:content => "value for content")

タグは have_tag で。 content_for は response[:capture].should have_tag を使う。

response.should have_tag('p', "名前")

response.should have_tag("form[action=?][method=post]", posts_path) do
with_tag("input#post_name[name=?]", "post[name]")
with_tag("input#post_title[name=?]", "post[title]")
with_tag("textarea#post_content[name=?]", "post[content]")
end

response[:footer].should have_tag(‘div’)

ヘルパーメソッドのモック、スタブは template オブジェクトで。

template.should_receive(:logged_in?).with().and_return(true)
template.should_receive(:current_user).with().and_return(mock(User))

コントローラのテスト

mock_model 使えば DB なしでテストできる。ログイン状態にしたいときなどは controller.stub! を使う。

@user = mock_model(User)
@user.stub!(:user_profile).and_return(@user_profile)
controller.stub!(:current_user).and_return(@user)
get :show, :id => 1
response.should be_success
response.should render_template("user_profiles/show.html.erb")

2009/12/15

Cucumber

Cucumber は受け入れテストのためのツールで、Ruby on Rails 以外でも使えるみたい。

参考

インストール。

gem install cucumber
sudo apt-get install libxml2-dev libxslt1-dev
gem install nokogiri
gem install webrat

準備。アプリケーションのルートディレクトリで次を実行する。

script/generate cucumber

がんばって日本語でいってみようと思うので webrat_steps.rb を webrat_ja_steps.rb にコピーして(必要なところだけ)翻訳することにする。

xxx_steps.rb の中で操作は Webrat を使って行われるらしい。

日本語で実行する場合は cucumber -l ja と実行する。

「もし」や「かつ」は cucumber-0.4.4/lib/cucumber/languages.yml にあらかじめ定義されている。

Post のテストを作ってみる。

$ script/generate feature post
exists features/step_definitions
create features/manage_posts.feature
create features/step_definitions/post_steps.rb
各ファイルの使い方は↓な感じか。
  • manage_posts.feature にテストというよりむしろ仕様を書く。
  • post_steps.rd にポスト固有の手順(前提、もし、ならばの中身)を書く。ポスト固有でないものは webrat_ja_steps.rd に書く。

features/step_definitions/webrat_ja_steps.rb

# -*- coding: utf-8 -*-
# IMPORTANT: This file was generated by Cucumber 0.4.4
# Edit at your own peril - it's recommended to regenerate this file
# in the future when you upgrade to a newer version of Cucumber.
# Consider adding your own code to a new file instead of editing this one.

require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths"))

# Commonly used webrat steps
# http://github.com/brynary/webrat

#Given /^(?:|I )am on (.+)$/ do |page_name|
Given /"([^\"]*)" ?[にへ]アクセス/ do |page_name|
visit path_to(page_name)
end

#When /^(?:|I )go to (.+)$/ do |page_name|
# visit path_to(page_name)
#end

When /"([^\"]*)" ?ボタンをクリック/ do |button|
click_button(button)
end

When /"([^\"]*)" ?リンクをクリック/ do |link|
click_link(link)
end

#When /^(?:|I )follow "([^\"]*)" within "([^\"]*)"$/ do |link, parent|
When /"([^\"]*)" ?の ?"([^\"]*)" ?リンクをクリック/ do |parent, link| # "
click_link_within(parent, link)
end

#When /^(?:|I )fill in "([^\"]*)" with "([^\"]*)"$/ do |field, value|
When /"([^\"]*)" ?に ?"([^\"]*)" ?[とを]入力$/ do |field, value| # "
fill_in(field, :with => value)
end

#When /^(?:|I )fill in "([^\"]*)" for "([^\"]*)"$/ do |value, field|
# fill_in(field, :with => value)
#end

# Use this to fill in an entire form with data from a table. Example:
#
# When I fill in the following:
# | Account Number | 5002 |
# | Expiry date | 2009-11-01 |
# | Note | Nice guy |
# | Wants Email? | |
#
# TODO: Add support for checkbox, select og option
# based on naming conventions.
#
When /^(?:|I )fill in the following:$/ do |fields|
fields.rows_hash.each do |name, value|
When %{I fill in "#{name}" with "#{value}"}
end
end

When /^(?:|I )select "([^\"]*)" from "([^\"]*)"$/ do |value, field|
select(value, :from => field)
end

# Use this step in conjunction with Rail's datetime_select helper. For example:
# When I select "December 25, 2008 10:00" as the date and time
When /^(?:|I )select "([^\"]*)" as the date and time$/ do |time|
select_datetime(time)
end

# Use this step when using multiple datetime_select helpers on a page or
# you want to specify which datetime to select. Given the following view:
# <%= f.label :preferred %><br />
# <%= f.datetime_select :preferred %>
# <%= f.label :alternative %><br />
# <%= f.datetime_select :alternative %>
# The following steps would fill out the form:
# When I select "November 23, 2004 11:20" as the "Preferred" date and time
# And I select "November 25, 2004 10:30" as the "Alternative" date and time
When /^(?:|I )select "([^\"]*)" as the "([^\"]*)" date and time$/ do |datetime, datetime_label|
select_datetime(datetime, :from => datetime_label)
end

# Use this step in conjunction with Rail's time_select helper. For example:
# When I select "2:20PM" as the time
# Note: Rail's default time helper provides 24-hour time-- not 12 hour time. Webrat
# will convert the 2:20PM to 14:20 and then select it.
When /^(?:|I )select "([^\"]*)" as the time$/ do |time|
select_time(time)
end

# Use this step when using multiple time_select helpers on a page or you want to
# specify the name of the time on the form. For example:
# When I select "7:30AM" as the "Gym" time
When /^(?:|I )select "([^\"]*)" as the "([^\"]*)" time$/ do |time, time_label|
select_time(time, :from => time_label)
end

# Use this step in conjunction with Rail's date_select helper. For example:
# When I select "February 20, 1981" as the date
When /^(?:|I )select "([^\"]*)" as the date$/ do |date|
select_date(date)
end

# Use this step when using multiple date_select helpers on one page or
# you want to specify the name of the date on the form. For example:
# When I select "April 26, 1982" as the "Date of Birth" date
When /^(?:|I )select "([^\"]*)" as the "([^\"]*)" date$/ do |date, date_label|
select_date(date, :from => date_label)
end

When /^(?:|I )check "([^\"]*)"$/ do |field|
check(field)
end

When /^(?:|I )uncheck "([^\"]*)"$/ do |field|
uncheck(field)
end

When /^(?:|I )choose "([^\"]*)"$/ do |field|
choose(field)
end

When /^(?:|I )attach the file at "([^\"]*)" to "([^\"]*)"$/ do |path, field|
attach_file(field, path)
end

Then /^タイトルが ?"([^\"]*)"/ do |text| # "
response.should have_tag('title', text)
end

Then /^説明が ?"([^\"]*)"/ do |text| # "
response.should have_tag("meta[name=description][content=?]", text)
end

Then /^キーワードが ?"([^\"]*)"/ do |text| # "
response.should have_tag("meta[name=keywords][content=?]", text)
end

#Then /^(?:|I )should see "([^\"]*)"$/ do |text|
Then /^"([^\"]*)" ?[とがは]表示される/ do |text|
response.should contain(text)
end

#Then /^(?:|I )should see "([^\"]*)" within "([^\"]*)"$/ do |text, selector|
Then /^"([^\"]*)" ?に ?"([^\"]*)" ?[とがは]表示される/ do |selector, text| # "
within(selector) do |content|
content.should contain(text)
end
end

#Then /^(?:|I )should see \/([^\/]*)\/$/ do |regexp|
Then /^\/([^\/]*)\/ ?[とがは]表示される/ do |regexp|
regexp = Regexp.new(regexp)
response.should contain(regexp)
end

#Then /^(?:|I )should see \/([^\/]*)\/ within "([^\"]*)"$/ do |regexp, selector|
Then /^"([^\"]*)" ?に ?\/([^\/]*)\/ ?[とがは]表示される/ do |selector, regexp|
within(selector) do |content|
regexp = Regexp.new(regexp)
content.should contain(regexp)
end
end

Then /^"([^\"]*)" ?[とがは]表示されない/ do |text|
response.should_not contain(text)
end

Then /^(?:|I )should not see "([^\"]*)" within "([^\"]*)"$/ do |text, selector|
within(selector) do |content|
content.should_not contain(text)
end
end

Then /^(?:|I )should not see \/([^\/]*)\/$/ do |regexp|
regexp = Regexp.new(regexp)
response.should_not contain(regexp)
end

Then /^(?:|I )should not see \/([^\/]*)\/ within "([^\"]*)"$/ do |regexp, selector|
within(selector) do |content|
regexp = Regexp.new(regexp)
content.should_not contain(regexp)
end
end

#Then /^the "([^\"]*)" field should contain "([^\"]*)"$/ do |field, value|
Then /"([^\"]*)" ?に ? "([^\"]*)" ?と入力され/ do |field, value|
field_labeled(field).value.should =~ /#{value}/
end

Then /^the "([^\"]*)" field should not contain "([^\"]*)"$/ do |field, value|
field_labeled(field).value.should_not =~ /#{value}/
end

Then /^the "([^\"]*)" checkbox should be checked$/ do |label|
field_labeled(label).should be_checked
end

Then /^the "([^\"]*)" checkbox should not be checked$/ do |label|
field_labeled(label).should_not be_checked
end

Then /^(?:|I )should be on (.+)$/ do |page_name|
URI.parse(current_url).path.should == path_to(page_name)
end

Then /^show me the page$/ do
save_and_open_page
end

features/step_definitions/post_steps.rb

# -*- coding: utf-8 -*-
#Given /^the following posts:$/ do |posts|
Given /^次のポストがある$/ do |posts|
Post.create!(posts.hashes)
end

#When /^I delete the (\d+)(?:st|nd|rd|th) post$/ do |pos|
When /^(\d+) ?番目のポストを削除/ do |pos|
visit posts_url
within("table > tr:nth-child(#{pos.to_i+1})") do
click_link "削除"
end
end

When /^(\d+) ?番目のポストを編集/ do |pos|
visit posts_url
within("table > tr:nth-child(#{pos.to_i+1})") do
click_link "編集"
end
end

#Then /^I should see the following posts:$/ do |expected_posts_table|
Then /^次のポストが表示されること$/ do |expected_posts_table|
expected_posts_table.diff!(table_at('table').to_a)
end

features/manage_posts.feature

フィーチャ: ポスト
In order to [goal]
[stakeholder]
wants [behaviour]

シナリオ: ポストの一覧
前提 次のポストがある
| name | title | content |
| 山田太郎 | お昼ごはん | ○○麺所に行った。 |
| 田中京子 | 散歩のこと | 猫を追跡した。 |
| 佐藤悟 | 休日のこと | いこーよ |
もし "ポスト一覧" へアクセスした
ならば "山田太郎" と表示されること
かつ "お昼ごはん" と表示されること
かつ "○○麺所に行った。" と表示されること
かつ "田中京子" と表示されること
かつ "散歩のこと" と表示されること
かつ "猫を追跡した。" と表示されること

シナリオ: ポストの削除
前提 次のポストがある
| name | title | content |
| 山田太郎 | お昼ごはん | ○○麺所に行った。 |
| 田中京子 | 散歩のこと | 猫を追跡した。 |
| 佐藤悟 | 休日のこと | いこーよ |
もし 2番目のポストを削除した
ならば "田中京子" は表示されない
# undefined method `css_search' for Webrat::XML:Module (NoMethodError) と
# しかられる。
# http://groups.google.com/group/cukes/browse_thread/thread/81b59d2785dc4256#
# ならば 次のポストが表示されること
# | name | title | content |
# | 山田太郎 | お昼ごはん | ○○麺所に行った。 |
# | 佐藤悟 | 休日のこと | いこーよ |


シナリオ: ポストの編集
前提 次のポストがある
| name | title | content |
| 山田太郎 | お昼ごはん | ○○麺所に行った。 |
| 田中京子 | 散歩のこと | 猫を追跡した。 |
| 佐藤悟 | 休日のこと | いこーよ |
もし "ポスト一覧" へアクセスし
かつ 2番目のポストを編集する
ならば "名前" に "田中京子" と入力されている
かつ "タイトル" に "散歩のこと" と入力されている
かつ "内容" に "猫を追跡した。" と入力されている
もし "名前" に "田中今日子" と入力
かつ "タイトル" に "散歩日和でした" と入力
かつ "内容" に "猫を追跡したが、ふりきられた。" と入力
かつ "保存" ボタンをクリック
ならば "田中今日子" と表示されること
かつ "散歩日和でした" と表示されること
かつ "猫を追跡したが、ふりきられた。" と表示されること

実行してみる(実際は赤と緑の色がついている)。

$ cucumber -l ja features/manage_posts.feature
フィーチャ: ポスト
In order to [goal]
[stakeholder]
wants [behaviour]

シナリオ: ポストの一覧 # features/manage_posts.feature:6
前提 次のポストがある # features/step_definitions/post_steps.rb:3
| name | title | content |
| 山田太郎 | お昼ごはん | ○○麺所に行った。 |
| 田中京子 | 散歩のこと | 猫を追跡した。 |
| 佐藤悟 | 休日のこと | いこーよ |
もし "ポスト一覧" へアクセスした # features/step_definitions/webrat_ja_steps.rb:13
ならば "山田太郎" と表示されること # features/step_definitions/webrat_ja_steps.rb:140
かつ "お昼ごはん" と表示されること # features/step_definitions/webrat_ja_steps.rb:140
かつ "○○麺所に行った。" と表示されること # features/step_definitions/webrat_ja_steps.rb:140
かつ "田中京子" と表示されること # features/step_definitions/webrat_ja_steps.rb:140
かつ "散歩のこと" と表示されること # features/step_definitions/webrat_ja_steps.rb:140
かつ "猫を追跡した。" と表示されること # features/step_definitions/webrat_ja_steps.rb:140

シナリオ: ポストの削除 # features/manage_posts.feature:20
前提 次のポストがある # features/step_definitions/post_steps.rb:3
| name | title | content |
| 山田太郎 | お昼ごはん | ○○麺所に行った。 |
| 田中京子 | 散歩のこと | 猫を追跡した。 |
| 佐藤悟 | 休日のこと | いこーよ |
もし 2番目のポストを削除した # features/step_definitions/post_steps.rb:8
ならば "田中京子" は表示されない # features/step_definitions/webrat_ja_steps.rb:165

# undefined method `css_search' for Webrat::XML:Module (NoMethodError) と
# しかられる。
# http://groups.google.com/group/cukes/browse_thread/thread/81b59d2785dc4256#
# ならば 次のポストが表示されること
# | name | title | content |
# | 山田太郎 | お昼ごはん | ○○麺所に行った。 |
# | 佐藤悟 | 休日のこと | いこーよ |
シナリオ: ポストの編集 # features/manage_posts.feature:37
前提 次のポストがある # features/step_definitions/post_steps.rb:3
| name | title | content |
| 山田太郎 | お昼ごはん | ○○麺所に行った。 |
| 田中京子 | 散歩のこと | 猫を追跡した。 |
| 佐藤悟 | 休日のこと | いこーよ |
もし "ポスト一覧" へアクセスし # features/step_definitions/webrat_ja_steps.rb:13
かつ 2番目のポストを編集する # features/step_definitions/post_steps.rb:15
ならば "名前" に "田中京子" と入力されている # features/step_definitions/webrat_ja_steps.rb:188
かつ "タイトル" に "散歩のこと" と入力されている # features/step_definitions/webrat_ja_steps.rb:188
かつ "内容" に "猫を追跡した。" と入力されている # features/step_definitions/webrat_ja_steps.rb:188
もし "名前" に "田中今日子" と入力 # features/step_definitions/webrat_ja_steps.rb:35
かつ "タイトル" に "散歩日和でした" と入力 # features/step_definitions/webrat_ja_steps.rb:35
かつ "内容" に "猫を追跡したが、ふりきられた。" と入力 # features/step_definitions/webrat_ja_steps.rb:35
かつ "保存" ボタンをクリック # features/step_definitions/webrat_ja_steps.rb:21
ならば "田中今日子" と表示されること # features/step_definitions/webrat_ja_steps.rb:140
かつ "散歩日和でした" と表示されること # features/step_definitions/webrat_ja_steps.rb:140
かつ "猫を追跡したが、ふりきられた。" と表示されること # features/step_definitions/webrat_ja_steps.rb:140

3 scenarios (3 passed)
24 steps (24 passed)
0m0.607s

expected_posts_table.diff! を使うと次エラーが発生するのは http://groups.google.com/group/cukes/browse_thread/thread/81b59d2785dc4256# に書かれている現象だと思うけど、解決はしてない?

      undefined method `css_search' for Webrat::XML:Module (NoMethodError)

日本語でいくために無駄にがんばっている気もするが、あとは画面のハードコピーでもあればテスト仕様書とテスト結果としての体裁がそろってテストドキュメント一式完成となるなら、がんばる価値はあると思う。テストドキュメントが要求されない場合は。。。おおわくでしっかりしたテストがあればソースいじりやすいから。ね。

2009/12/14

Weblocks

以前にもブログに書いたけど、また書く。

Weblocks は Common Lisp で書かれた Web フレームワーク。継続と AJAX を使うのが特徴。

たいして根拠はないのだけど Common Lisp 界では Weblocks が一番いい Web フレームワークのような気がする。

インストールは Weblocks: Installation に書かれているとおり clbuild を使うのが一番いいみたい。ただ moptilities と closer-mop とのバージョン関係に少々問題があるようなので次のとおり moptilities.asd をちょこっと修正する。

diff -rN old-moptilities/moptilities.asd new-moptilities/moptilities.asd
35c35,36
< :depends-on ((:version :closer-mop "0.55")))
---
> ;;:depends-on ((:version :closer-mop "0.55")))
> :depends-on (:closer-mop))

それはともかく asdf で依存ライブラリのバージョン指定なんてできたんですね。

(require :weblocks-demo)
(weblocks-demo:start-weblocks-demo :port 3455)

何故か次のようなメッセージが表示されが無視して http://localhost:3455/weblocks-demo にアクセスすればデモが動いているのを確認できる。

WARNING:
An instance of WEBLOCKS-DEMO with name weblocks-demo is already running, ignoring start request

このデモのデータストレージには CL-PREVALENCE を使っている。 CLSQL を使うのと Elephant を使うデモもある。

以下、記憶にない覚え書き。

flash は次のアクションで消える。

nil をセットする項目は :type (or null (varchar 30))

必須項目は :db-constraints :not-null

id 以外をキーにする場合 (defmethod class-id-slot-name ((W_来店者 (eql (class-name (find-class 'W_来店者)))))

'来店者番号)

今度なんか作ってみよう。

capistrano-ext

Ruby on Rails のデプロイに便利な Capistrano のエクステンションらしい。デプロイ環境毎に設定ファイルを分割できる。

参考

インストール。

gem install capistrano-ext

./config/deploy.rb に

require "capistrano/ext/multistage"
を足す。

これで ./config/deploy/ の下に環境毎の設定ファイルを作成できるようになる。

例えば ./config/deploy/staging.rb を作成して、実行するには次のとおり。

cap staging deploy

./config/deploy/aaa.rb を作成して cap -T とすると aaa タスクが追加になっている。

cap aaa                  # Set the target stage to `aaa'.

ここで aaa とか指定せずに cap するとしかられる。

$ cap deploy:check
triggering start callbacks for `deploy:check'
* executing `multistage:ensure'
No stage specified. Please specify one of: aaa (e.g. `cap aaa deploy:check')
なるほど。

Capistrano

Ruby on Rails のデプロイに便利な Capistrano の使用メモ。

参考

インストール。 termios はディスプレイにパスワードが表示されないようにするためにインストールしておく。

gem install capistrano
gem install termios

アプリケーションのルートディレクトリで capify をカレントディレクトリ指定で実行すると、2つのファイルができる。

  • ./Capfile ← 特にいじる必要なし
  • ./config/deploy.rb ← いじるファイル
capify .

./config/deploy.rb に必要な情報を書く。 Passenger の場合は restart で touch tmp/restart.txt する。

# -*- coding: utf-8 -*-
set :application, "BLOG"
set :repository, "/home/ancient/letter/ruby/rails/apps/blog"

set :scm, :git
# Or: `accurev`, `bzr`, `cvs`, `darcs`, `git`, `mercurial`, `perforce`, `subversion` or `none`

role :web, "127.0.0.1" # Your HTTP server, Apache/etc
role :app, "127.0.0.1" # This may be the same as your `Web` server
role :db, "127.0.0.1", :primary => true # This is where Rails migrations will run
#role :db, "your slave db-server here"

set :deploy_to, "/home/ancient/job/actindi/apache/rails-deploy"
set :use_sudo, false

# ログイン先の環境変数を設定する。
default_environment["PATH"] = "${HOME}/letter/ruby/1.8.7/bin:${PATH}"

# ssh でわけわからなくなったら次のコメントを外してみる。
# ssh_options[:verbose] = :debug

# If you are using Passenger mod_rails uncomment this:
# if you're still using the script/reapear helper you will need
# these http://github.com/rails/irs_process_scripts
namespace :deploy do
task :start do end
task :stop do end
task :restart, :roles => :app, :except => { :no_release => true } do
run "#{try_sudo} touch #{File.join(current_path,'tmp','restart.txt')}"
end
end

ローカルで次のコマンドを実行してサーバに必要なディレクトリを作成する。

cap deploy:setup

デプロイする。

cap deploy

マイグレーションもできる。

cap deploy:migrate

なんかまずかったら rollback できる。

cap deploy:rollback

次のコマンドでタスクの一覧が表示される。

cap -T

ssh でわけわからなくなったら次を ./config/deploy.rb に入れてみる。

ssh_options[:verbose] = :debgu

Rails Paperclip

Ruby on Rails の画像アップロードプラグイン(?) Paperclip の使用メモ。

サムネイルつくるのに ImageMagick が必要なので別途インストールしておく。

参考

インストール

script/plugin install git://github.com/thoughtbot/paperclip.git

今回は新規にモデルを作ってみる。 Paperclip は次の4つのカラムを必要とする。

  • avatar_file_name:string
  • avatar_content_type:string
  • avatar_file_size:integer
  • avatar_updated_at:datetime
script/generate rspec_model UserProfile name:string self_introduction:text avatar_file_name:string avatar_content_type:string avatar_file_size:integer avatar_updated_at:datetime user:references
rake db:migrate
rake db:migrate RAILS_ENV=test

User と UserProfile のリレーションを設定。

class User < ActiveRecord::Base
has_one :user_profile
...
end

モデルに has_attached_file(name, options = {}) を追加する。 options には次が指定可能。

  • url ファイルの場所。ディレクトリも URL も指定可能。デフォルトは "/system/:attachment/:id/:style/:filename"
  • default_url 画像がない場合に表示する画像の場所。
  • styles サムネイルの設定ハッシュ。
  • default_style url メソッドで引数を省略した場合の値。 styles ハッシュのキーを指定する。
  • whiny post_process でエラーが発生した場合にエラーをあげるか。デフォルト true
  • convert_options convert コマンドに渡すオプションのハッシュ。キーは styles ハッシュのキー。:all をキーとしたものは全てに適用。 http://www.imagemagick.org/script/convert.php を参照。
  • storage ファイルを保存するストレージ。 :filesystem か :s3。デフォルトは :filesystem。
class UserProfile < ActiveRecord::Base
belongs_to :user
has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100>" }
end

UserProfile のコントローラを作成。

script/generate rspec_controller UserProfiles show new edit

config/routes.rb に追加。

  map.resource :user_profile

登録のビュー。 :html => { :multipart => true } が必要。

<h1>ユーザプロフィール作成</h1>
<% form_for@user_profile, :html => { :multipart => true } do |f| %>
<p>
<%= f.label :name %>
<%= f.text_field :name %>
</p>
<p>
<%= f.label :self_introduction %>
<%= f.text_field :self_introduction %>
</p>
<p>
<%= f.label :avatar %>
<%= f.file_field :avatar %>
</p>
<p>
<%= f.submit '保存' %>
</p>
<% end %>

コントローラ。

# -*- coding: utf-8 -*-
class UserProfilesController < ApplicationController
def show
@user = User.find(current_user)
@user_profile = @user.user_profile
end

def new
@user_profile = UserProfile.new
end

def create
@user = User.find(current_user)
@user_profile = UserProfile.create(params[:user_profile])
@user.user_profile = @user_profile
if @user.save
flash[:notice] = 'ユーザプロフィールを作成しました。'
redirect_to @user_profile
else
render :action => :new
end
end
end

2009/12/13

グインサーガ

最後の一巻が本屋さんにならんでいた。

読もう。

2009/12/12

Rails exception_notification

Ruby on Rails でエラーが発生したときにメールでお知らせしてくれるプラグイン exception_notification の使い方メモ。

rails's exception_notification at master - GitHub を使う。

インストール

script/plugin install git://github.com/rails/exception_notification.git

ApplicationController に include ExceptionNotifiable を追加。

class ApplicationController < ActionController::Base
...
include ExceptionNotifiable
end

config/environments.rb にメールの送信先等を設定する。 その際、config.after_initialize do で囲む必要があるようだ。 Rails::Initializer.run do |config| ... end の外に書けばよかった。

# メール受信者
ExceptionNotifier.exception_recipients = %w(read.eval.print@gmail.com)
# メール送信者
ExceptionNotifier.sender_address = %("エラー通知" <read.eval.print@gmail.com>)
# メールサブジェクトのプレフィックス
ExceptionNotifier.email_prefix = "【エラー】"

開発環境でのメール送信テスト時には ApplicationController で local_addresses.clear して、config/environments/development.rb で次の設定をしておく。

config.action_controller.consider_all_requests_local = false

config.action_controller.consider_all_requests_local = true にしていて rails's exception_notification at master - GitHub は Rails 2.3.5 で動かないんじゃないかと疑ってしまった。ごめんなさい。

2009/12/11

Rails restful-authentication

Ruby on Rails の認証プラグイン restful-authentication を使うメモ。

参考サイト

インストール

script/plugin install git://github.com/technoweenie/restful-authentication.git

ジェネレート。引数は

  • ユーザとかアカウントとか言われるもののモデル
  • セッションコントローラ
  • RSpec のテストを生成
  • アクティベーションを使う
script/generate authenticated user sessions --rspec --include-activation
rake db:migrate
rake db:migrate RAILS_ENV=test

次のコードが config/routes.rb に自動的に追加されている。

map.logout '/logout', :controller => 'sessions', :action => 'destroy'
map.login '/login', :controller => 'sessions', :action => 'new'
map.register '/register', :controller => 'users', :action => 'create'
map.signup '/signup', :controller => 'users', :action => 'new'
map.resources :users
map.resource :session

が、アクティベーションのために

  map.activate '/activate/:activation_code', :controller => 'users', :action => 'activate', :activation_code => nil
も追加する。

users_controller.rb の次の行を application_controller.rb に移動する。

  # Be sure to include AuthenticationSystem in Application Controller instead
include AuthenticatedSystem

ログインが必要なアクションをコントローラで次のように指定する。指定したアクションを実行するとログイン画面に遷移するようになる。

before_filter :login_required, :only => [:new, :edit, :create, :update, :destroy]

アクティベーションメールのために、

config/environment.rb にオブザーバを追加。

  # result-authentication のアクティベーション用オブザーバ
config.active_record.observers = :user_observer

config/environments/development.rb でメールの設定。

config.action_mailer.delivery_method = :sendmail
config.action_mailer.raise_delivery_errors = true

次のファイルでメールの内容等を指定する。

  • app/models/user_mailer.rb
  • app/views/user_mailer/signup_notification.erb
  • app/views/user_mailer/activation.erb

これで http://localhost:3000/signup からサインアップするとメールが送信され、メールのリンクをクリックするとアカウントが使えるようになる。

:セキュリティ上の注意事項 config/initializers/site_keys.rb は秘密にしましょう。

2009/12/10

hunchentoot スタンドアロンで動かす

Ruby (on Rails) ばかりやっていると、とりかえしのつかないことになりそうなので、今晩は Common Lisp をする。

仕事とプライベートの切り替えは大切だよね。

ということで Common Lisp で書かれた Web サーバ hunchentoot をスタンドアロンで動かす。

Common Lisp の場合はたいてい適当な main を書いて save-lisp-and-die みたいなことをすれば実行ファイルができる。

次のコードは SBCL でのみ動く。

exe.lisp

(eval-when (:compile-toplevel :load-toplevel :execute)
(require :hunchentoot))

(defun main ()
(let ((*invoke-debugger-hook*
(lambda (condition hook)
(declare (ignore hook))
(format *error-output* "error: ~a~%" condition)
(quit :unix-status 1)))
(document-root (second *posix-argv*))
(port (third *posix-argv*)))
(print (list document-root port))
(push (hunchentoot:create-folder-dispatcher-and-handler
"/" (make-pathname :directory document-root))
hunchentoot:*dispatch-table*)
(push (hunchentoot:create-static-file-dispatcher-and-handler
"/" (make-pathname :directory document-root :name "index.html"))
hunchentoot:*dispatch-table*)
(let ((acceptor (make-instance 'hunchentoot:acceptor
:port (read-from-string port))))
(hunchentoot:start acceptor)))
(loop (sleep 777)))

(save-lisp-and-die "hunchentoot" :toplevel #'main :executable t)

;; sbcl --load exe.lisp
;; ./hunchentoot ~/www/ 1234

シェルから

sbcl --load exe.lisp
すると hunchentoot というファイルができるので、ドキュメントルートとポート番号を渡して実行する。
./hunchentoot ~/www/ 1234

あれ? ちゃんと動いたけど C-c で止まんないな。。。ま、いいか。

Rails I18n

参考

i18n 用に、ブランチを作って作業する。

git checkout master
git branch i18n
git checkout i18n

amatsuda's i18n_generators at master - GitHub をインストール。

script/plugin install git://github.com/amatsuda/i18n_generators.git

次で config/environment.rb に config/locales/ja.yml と config/locales/translation_ja.yml が作成される。モデルも適当に翻訳されてる。

script/generate i18n ja

これでエラーメッセージとか日本語化された。

でも、フォームのラベルとか国際化されないと思ったら↓ということらしい。

2009-09-18 - akimatter にあるとおり app/controllers/application_controller.rb にモンキーパッチを追加したらラベルも国際化された。

ネストしたフォームのラベルも OK!

<% @post.tags.build if @post.tags.empty? %>
<% form_for(@post) do |post_form| %>
<%= post_form.error_messages %>

<p>
<%= post_form.label :name %><br />
<%= post_form.text_field :name %>
</p>
<p>
<%= post_form.label :title %><br />
<%= post_form.text_field :title %>
</p>
<p>
<%= post_form.label :content %><br />
<%= post_form.text_area :content %>
</p>
<h2>タグ</h2>
<% post_form.fields_for :tags do |tag_form| %>
<p>
<%= tag_form.label :name %>
<%= tag_form.text_field :name %>
</p>
<% unless tag_form.object.nil? || tag_form.object.new_record? %>
<p>
<%= tag_form.label :_delete %>
<%= tag_form.check_box :_delete %>
</p>
<% end %>
<% end %>
<p>
<%= post_form.submit '保存' %>
</p>
<% end %>

Ruby on Rails で gettext を使う

あとで i18n もやるので、ブランチを作って作業する。

git branch gettext
git checkout gettext

次を参考に

まず必要なものを vendor/gems にインストールするために

rake -T | grep gems

rake gems                                 # List the gems that this rails application depends on
rake gems:build # Build any native extensions for unpacked gems
rake gems:build:force # Force the build of all gems
rake gems:install # Installs all required gems.
rake gems:refresh_specs # Regenerate gem specifications in correct format.
rake gems:unpack # Unpacks all required gems into vendor/gems.
rake gems:unpack:dependencies # Unpacks all required gems and their dependencies into vendor/gems.

config/environments.rb に config.gem "gettext" を追加する。

Rails::Initializer.run do |config|
config.gem 'locale'
config.gem 'locale_rails'
config.gem 'gettext'
config.gem 'gettext_activerecord'
config.gem 'gettext_rails'

次の一連の rake を実行すると vendor/gems の下に gettext が配置される。

rake gems:unpack:dependencies
rake gems:build

ApplicationController に init_gettext "blog" を追加する。

これでサーバを再起動すると検証エラー等が日本語になっている。

apps/blog/vendor/gems/gettext_rails-2.1.0/README.rdoc を参考に lib/tasks/gettext.rake を作成する。

# -*- mode: ruby -*-
require 'rubygems'

namespace :gettext do

desc "Create mo files"
task :makemo do
require 'gettext_rails/tools'
GetText.create_mofiles
end

desc "Update po file"
task :updatepo do
require 'gettext_rails/tools'
# Need to access DB to find Model table/column names.
# Use config/database.yml which is the same style with rails.
GetText.update_pofiles("blog",
Dir.glob("{app,config,components,lib}/**/*.{rb,erb}"),
"blog 1.0.0")
end

end

次で po/blog.pot ファイルを作成する。

rake gettext:updatepo

po/blog.pot を編集。

次で po/ja/blog.po を作成。この手順は最初の1回だけ。 msginit は cp でも ok?

cd po
mkdir ja
msginit -i blog.pot -o ja/blog.po

po/ja/blog.po の msgstr に日本語を書く。

rake gettext:makemo

次で locale/ja/LC_MESSAGES/blog.mo を作成。

rake makemo

サーバを再起動する。

_() を追加したら rake gettext:updatepo する。 po/ja/blog.po を編集したら rake gettext:makemo して、サーバを再起動する。

2009/12/09

Ruby on Rails のメモ

インストール

gem でインストール

gem install rails

MySQL を使うので

gem install mysql

Getting Started with Rails

Getting Started with Rails をやってみる。

アプリケーションの作成。

MySQL を使う。

rails blog -d mysql

これで blog ディレクトリ以下にいっぱいファイルが作成される。

rake -T

で、rake のタスクがいっぱい表示された。

RSpec を使うために

script/plugin install git://github.com/dchelimsky/rspec.git -r 'refs/tags/1.2.9' script/plugin install git://github.com/dchelimsky/rspec-rails.git -r 'refs/tags/1.2.9' script/generate rspec

DB を作成する。

次で blog_development MySQL にデータベースが作成される。 RSpec のために test の方も作る。

rake db:create rake db:create RAILS_ENV=test

Web サーバの実行

script/server

http://localhost:3000/ にアクセスして動いていることを確認。

About your application’s environment をクリックすると各バージョンが表示される。

script/about でも表示される。

RSpec の実行

次でずっと全テストが走るつづける。

autospec

緑は OK 赤は NG。

apps/blog/spec/spec.opts の —format progress を —format specdoc とすると仕様が表示されるようになる。

次で一回だけ全テストが走る。

rake spec

最初のページを作る

home コントローラを作る。inedx アクション付きで。 RSpec のために rspec_controller で。

script/generate rspec_controller home index

app/views/home/index.html.erb を次の内容に編集する。

<h1>Hello, Rails!</h1>
<p>まみむめも♪</p>

http://localhost:3000/home/index にアクセスすると上記内容が表示される。

このページを http://localhost:3000/ で表示するには public/index.html を削除し、config/routes.rb で map.root を指定する。

  map.root :controller => "home"
scaffold を使ってみる

モデル、ビュー、コントロールが一気にできちゃう。これも rspec_scaffold で。

script/generate rspec_scaffold Post name:string title:string content:text rake db:migrate rake db:migrate RAILS_ENV=test

M-x rinari-sql してできたテーブルを確認してみる。

mysql> show tables;
+----------------------------+
| Tables_in_blog_development |
+----------------------------+
| posts |
| schema_migrations |
+----------------------------+
2 rows in set (0.00 sec)

mysql> SHOW FIELDS FROM `posts`;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | YES | | NULL | |
| title | varchar(255) | YES | | NULL | |
| content | text | YES | | NULL | |
| created_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
+------------+--------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)

id, created_at, updated_at はデフォルトで存在するらしい。

app/views/home/index.html.erb にポストページへのリンクを追加する。

<h1>Hello, Rails!</h1>
<p>まみむめも♪</p>
<%= link_to "ブログへ", posts_path %>

posts_path は config/routes.rb に次の行が script/generate scaffold によって追加されて使えるようになっている。レストフルなフレーバー?

map.resources :posts

追加したリンクをクリックすると posts の CRUD ができるようになっている。

autospec が赤になっているので、テストのためのテスト的に spec/views/home/index.html.erb_spec.rb を修正する。 11行目。「まみむめも♪」と書かれた p タグがあることを期待している。

    response.should have_tag('p', %r[まみむめも♪])
validation を追加する

Post に次の validation を追加する。

  • name は必須
  • title は必須
  • title は5文字以上

app/models/post.rb をいじる。

class Post < ActiveRecord::Base
validates_presence_of :name, :title
validates_length_of :title, :minimum => 5
end

RSpec も。

# -*- coding: utf-8 -*-
require 'spec_helper'

describe Post do
before(:each) do
@valid_attributes = {
:name => "value for name",
:title => "value for title",
:content => "value for content"
}
end

it "should create a new instance given valid attributes" do
Post.create!(@valid_attributes)
end

it "name がないと不正" do
post = Post.create(@valid_attributes)
post.name = nil
post.save
post.should_not be_valid
end

it "title がないと不正" do
post = Post.create(@valid_attributes)
post.title = nil
post.save
post.should_not be_valid
end

it "title が4文字だと不正" do
post = Post.create(@valid_attributes)
post.title = "あいうえ"
post.save
post.should_not be_valid
end

it "title が5文字だと正常" do
post = Post.create(@valid_attributes)
post.title = "あいうえお"
post.save
post.should be_valid
end

it "title が6文字でも正常" do
post = Post.create(@valid_attributes)
post.title = "あいうえおか"
post.save
post.should be_valid
end
end
パーシャルテンプレート

app/views/posts/new.html.erb と app/views/posts/edit.html.erb の共通部分をパーシャルテンプレートにする。

共通部分を選択して M-x rinari-extract-partial とすると、名前をきいてくるので form と入力する。 _form.html.erb が作成され、new.html.erb と edit.html.erb は次のようにに編集する。

<%= render :partial => 'form' %>
コメントできるようにする

ポストに対してコメントできるようにする。 rspec_model で Comment モデルを作成。

script/generate rspec_model Comment commenter:string body:text post:references
rake db:migrate
rake db:migrate RAILS_ENV=test

Post は Comment をいくつか持っている。has_many で指定。

class Post < ActiveRecord::Base
validates_presence_of :name, :title
validates_length_of :title, :minimum => 5

has_many :comments
end

config/routes.rb でネスト。

map.resources :posts, :has_many => :comments

rspec_controller で Comment のコントローラを作る。ビューのあるアクションのみ引数に指定する。

script/generate rspec_controller Comments index show new edit

できた app/controllers/CommentsController の中身を実装する。

view を編集(コピペ)。

app/views/posts/show.html.erb にコメントへのリンクを追加。

<%= link_to 'Back', post_comments_path(@post) %>
Building a Multi-Model Form

複数のモデルを扱うフォーム。タグを付けられるようにする。

script/generate rspec_model tag name:string post:references
rake db:migrate
rake db:migrate RAILS_ENV=test

Post モデルを修正。

# -*- coding: utf-8 -*-
class Post < ActiveRecord::Base
validates_presence_of :name, :title
validates_length_of :title, :minimum => 5

has_many :comments

has_many :tags
accepts_nested_attributes_for(:tags,
# 削除チェックボックスのために
:allow_destroy => :true,
:reject_if => proc { |attrs|
# すべての属性が必須
attrs.all? { |k, v|
v.blank?
}
})
end

apps/blog/app/views/posts/_form.html.erb を修正。 new のときのために1行目に ... build if ... を追加。 <% post_form.fields_for :tags do |tag_form| %> を追加。

<% @post.tags.build if @post.tags.empty? %>
<% form_for(@post) do |post_form| %>
<%= post_form.error_messages %>

<p>
<%= post_form.label :name %><br />
<%= post_form.text_field :name %>
</p>
<p>
<%= post_form.label :title %><br />
<%= post_form.text_field :title %>
</p>
<p>
<%= post_form.label :content %><br />
<%= post_form.text_area :content %>
</p>
<h2>タグ</h2>
<% post_form.fields_for :tags do |tag_form| %>
<p>
<%= tag_form.label :name, 'タグ:' %>
<%= tag_form.text_field :name %>
</p>
<% unless tag_form.object.nil? || tag_form.object.new_record? %>
<p>
<%= tag_form.label :_delete %>
<%= tag_form.check_box :_delete %>
</p>
<% end %>
<% end %>
<p>
<%= post_form.submit '保存' %>
</p>
<% end %>

テスト

RSpec でのテスト

ビューのテスト

mock_model でモデルを作って assigns でビューから見えるようにする、ということだろうか。

spec/views/comments/index.html.erb_spec.rb

# -*- coding: utf-8 -*-
require 'spec_helper'

describe "/comments/index" do
before(:each) do
@comment = mock_model(Comment,
:commenter => '田中京子',
:body => '寒かったですね。')
@post = mock_model(Post,
:name => '山田太郎',
:title => '今日のできごと',
:content => '今日はくもりでした。')
assigns[:post] = @post
assigns[:comments] = [@comment]
render 'comments/index'
end

#Delete this example and add some real ones or delete this file
it "should tell you where to find the file" do
response.should have_tag('td', @comment.commenter)
response.should have_tag('td', @comment.body)
end
end

spec/views/comments/show.html.erb_spec.rb

# -*- coding: utf-8 -*-
require 'spec_helper'

describe "/comments/show" do
before(:each) do
@comment = mock_model(Comment,
:commenter => '田中京子',
:body => '寒かったですね。')
@post = mock_model(Post,
:name => '山田太郎',
:title => '今日のできごと',
:content => '今日はくもりでした。',
:comments => [@comment])
assigns[:post] = @post
assigns[:comment] = @comment
render 'comments/show'
end

#Delete this example and add some real ones or delete this file
it "should tell you where to find the file" do
response.should have_tag('p', "#{@comment.commenter} さんのコメント")
response.should have_tag('p', @comment.body)
end
end

spec/views/comments/edit.html.erb_spec.rb

# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
require 'spec_helper'

describe "/comments/edit" do
before(:each) do
@comment = mock_model(Comment,
:commenter => '田中京子',
:body => '寒かったですね。')
@post = mock_model(Post,
:comments => [@comment])
assigns[:post] = @post
assigns[:comment] = @comment
render 'comments/edit'
end

#Delete this example and add some real ones or delete this file
it "should tell you where to find the file" do
response.should have_tag('input[id=comment_commenter][value=?]',
@comment.commenter)
response.should have_tag('textarea[id=comment_body]',
@comment.body)
end
end
モデルのテスト

自動的に作られた comments_controller_spec.rb のテストが失敗するので、 before(:each) でモックを仕込む。

  before(:each) do
@mock_comment = mock_model(Comment, :name => '田中京子',
:body => 'あ')
@mock_post = mock_model(Post,
:name => '山田太郎',
:comments => [@mock_comment])
@mock_post.comments.stub!(:build).and_return(Comment.new)
Post.stub!(:find).and_return(@mock_post)
end

2009/12/07

こういう情報がほしかった

irb を快適に使うための Tips - まさにっき(コードで世界を変えたい人の記録)

gem install wirble して ~/.irbrc に

# -*- coding: utf-8 -*-
#require 'irb/completion'
require 'pp'
require 'rubygems'
require 'wirble'

# 沢山記憶するよ
IRB.conf[:SAVE_HISTORY] = 100000

Wirble.init
Wirble.colorize

素晴らしい。ありがとうございます。

rspec-rails のインストール

リポジトリがどこにあるか探すのに手間取った。 Rails - rspec - GitHub

どんどん GitHub に集まってるのね。

script/plugin install git://github.com/dchelimsky/rspec.git -r 'refs/tags/1.2.9'
script/plugin install git://github.com/dchelimsky/rspec-rails.git -r 'refs/tags/1.2.9'
script/generate rspec

2009/12/06

clear lock

Muse モードのとき C-m で改行してくれない。 M-x help k C-m したら S-RET とか表示されている。 xmodmap で Caps Lock を Control に変更しているんだが、その設定が悪いのかなと思い調べてみた。 キー動作の変更方法 で見つけた。

clear lock

が足りていなかった。ずっとなんかおかしいなぁ、と思ってたんだよw

で、現状の ~/.xmodmaprc

! Caps Lock -> Control_R
keycode 66 = Control_L
add Control = Control_L
clear lock

! For Lisp
keycode 18 = 9 less
keycode 19 = 0 greater
keycode 25 = parenleft comma
keycode 26 = parenright period

! Muhenkan Henkan -> Alt
keycode 100 = Alt_R
keycode 101 = Alt_R
keycode 102 = Alt_L
add Mod1 = Alt_L Alt_R

!! ! Kinesis
!! keycode 115 = Alt_L
keycode 108 = Alt_R
!! add Mod1 = Alt_L Alt_R
!! keysym Super_L = Alt_L Meta_L
!! keysym Super_R = Alt_R Meta_R
!! add Mod1 = Alt_L
add Mod1 = Alt_R

! Dvork kai
keycode 35 = semicolon colon
keycode 52 = l L
keycode 33 = equal plus

ルーティング

Rails Way を読んでどうしてルーティングなるものに、こんなにもたくさんのページをさくのだろう、と思っていた。ようやくその必要性に気づいた。 SEO のためには必要なんだね。 Order.do とかの業務アプリばかりやっていたから気づかなかった。

勉強することがたくさんあるようだ。

2009/12/04

teepeedee2

確かに速い。


diff --git a/src/lib/utils.lisp b/src/lib/utils.lisp
index 2ce21f0..e200cb8 100644
--- a/src/lib/utils.lisp
+++
b/src/lib/utils.lisp
@@ -57,8 +57,9 @@
(defun report-error (err &key (stream *error-output*))
(format stream "~&ERROR ~A, ~A:~%~A~&"
(ignore-errors (princ-to-string err))
- (with-output-to-string (*standard-output*) (describe err))
- (trivial-backtrace:backtrace-string)))
+ (with-output-to-string (*standard-output*) (describe err))
+ (with-output-to-string (out)
+ (trivial-backtrace:print-backtrace-to-stream out))))

(defun backtrace-description (err)
(report-error err :stream nil))