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