カテゴリーアーカイブ: Ruby on Rails

背景の前提で、ログインしている状態にしておくメモ。

steps.rb

Given /^"(.*)"でログインしていること$/ do |login|
  @current_user = Admin.create!(
    :login => login,
    :password => "testtest",
    :password_confirmation => "testtest",
    :email => "foobar@example.com")
  visit "/session/new"
  fill_in("ログインID", :with => login)
  fill_in("パスワード", :with => "testtest")
  click_button("ログイン")
  response.body.should =~ /ログインしました。/m
end

feature

背景:
  前提 "foobar"でログインしていること

シナリオ:
 ....

WEB+DB PRESS Vol.51

Rails特集記事のお手伝いをしましたー

いつも忘れてしまうので、とりあえずの国際化メモ。

sudo gem install amatsuda-i18n_generators
./script/generate i18n ja
./script/generate i18n en

生成されたファイル。

config/locales/translation_ja.yml #モデルまわり
config/locales/translation_en.yml

config/locales/action_view_en.yml
config/locales/active_record_en.yml
config/locales/active_support_en.yml

config/locales/ja.yml #上記の3ファイルがまとめて生成されてる?

バリデーション関係は特に弄らずそのまま。

コントローラーとかでflash[:notice]やる場合に、

flash[:notice] = t(:created_success, :model => Post.human_name)

こんな感じ。

たぶんこんなのが出る。

translation missing: ja, created_success

出たら、


vi config/locales/ja.yml
ja:
created_success: "{{model}}を作成しました。"

みたいに追加しておく。

すると、「記事を作成しました。」と表示される。

やっぱりgrowlにも通知しておきたい。

以前rspecに設定した.autotestを編集します。

# -*- ruby -*-
 require 'autotest/redgreen'
module Autotest::Growl
  def self.growl title, msg, img="~/.rails_ok.png", pri=0, sticky=""
    msg += " at #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
    system "growlnotify -n autotest --image #{img} -p #{pri} -m #{msg.inspect} #{title} #{sticky}"
  end

  Autotest.add_hook :ran_command do |at|
    results = [at.results].flatten.join
    ex = results[/(\d+)\s+example?/].to_i
    fa = results[/(\d+)\s+failure?/].to_i
    er = results[/(\d+)\s+error?/].to_i
    num = fa + er
    if ex >= 0
      if num.to_i > 0
        output = "#{fa} failures, #{er} errors"
        growl "RSpec Failed", "#{output}", "~/.rails_fail.png", 2, "-s"
      else
        growl "RSpec Passed", "#{ex} examples passed", "~/.rails_ok.png", -2
      end
    else
      growl "RSpec Errored", "errors", "~/.rails_fail.png", 2, "-s"
    end
  end

  Autotest.add_hook :ran_features do |at|
    results = [at.results].flatten.join
    sc = results[/(\d+)\s+scenario?/].to_i
    sk = results[/(\d+)\s+skipped?/].to_i
    pe = results[/(\d+)\s+pending?/].to_i
    un = results[/(\d+)\s+undefined?/].to_i
    fa = results[/(\d+)\s+failed?/].to_i
    pa = results[/(\d+)\s+passwd?/].to_i
    st = results[/(\d+)\s+step?/].to_i
    num = sk + pe + fa + un
    if sc >= 0
      if num.to_i > 0
        output = "#{sk} skipped, #{un} undefined, #{pe} pending, #{fa} failed"
        growl "Cucumber Failed", "#{output}", "~/.rails_fail.png", 2, "-s"
      else
        growl "Cucumber Passed", "#{sc} scenarios, #{st} steps passed", "~/.rails_ok.png", -2
      end
    else
      growl "Cucumber Errored", "errors", "~/.rails_fail.png", 2, "-s"
    end
  end
end

いい感じ。

% cd RAILS_ROOT
% cat > cucumber.yml
autotest: features -f pretty -l ja
autotest-all: features -f pretty -l ja
^C
% AUTOFEATURE=true autospec

rspecとcucumberが一緒になりました。

というわけで、前回の続きです。

Rails, cucumberでしあわせになりたい

やっていた時点での最新

  • rspec 1.2.6
  • rspec-rails 1.2.6
  • webrat 0.4.4
  • cucumber 0.3.9

すでにこの時点で、

Featureがフィーチャ、Senarioがシナリオ、Givenが前提、、などと書く事ができました。

さらに、features/step_definitions/webrat_steps.rbを日本語対応してfeatures/manage_posts.feature自体も日本語で書きたいので、下記をインストールして実行しました。

% sudo gem install moro-miso
% ./script/generate miso
 create  features/step_definitions/webrat_ja_steps.rb
 create  features/step_definitions/web_extra_ja_steps.rb

ちょっと中身をみてます。

% grep When webrat_ja_steps.rb
When(/^"([^\"]*)"ページを表示する$/, &visit)
When /^"([^\"]*)"ボタンをクリックする$/ do |button|
  When %Q(I press "#{button}")
When /^"([^\"]*)"リンクをクリックする$/ do |link|
  When %Q(I follow "#{link}")
When /^"([^\"]*)"に"([^\"]*)"と入力する$/ do |field, value|
  When %Q(I fill in "#{field}" with "#{value}")
When /^"([^\"]*)"から"([^\"]*)"を選択する$/ do |field, value|
  When %Q(I select "#{value}" from "#{field}")
# When I select "December 25, 2008 10:00" as the date and time
When /^日時として"([^\"]*)"を選択する$/ do |time|
  When %Q(I select "#{time}" as the date and time)
# When I select "November 23, 2004 11:20" as the "Preferred" date and time
When /^"([^\"]*)"の日時として"([^\"]*)"を選択する$/ do |datetime_label, datetime|
  When %Q(I select "#{datetime_label}" as the "#{datetime}" date and time)
# When I select "2:20PM" as the time
When /^日付として"([^\"]*)"を選択する$/ do |time|
  When %Q(I select "#{time}" as the time)
# When I select "7:30AM" as the "Gym" time
When /^"([^\"]*)"の日付として"([^\"]*)"を選択する$/ do |time_label, time|
  When %Q(I select "#{time_label}" as the "#{time}" time)
# When I select "February 20, 1981" as the date
When /^時間として"([^\"]*)"を選択する$/ do |date|
  When %Q(I select "#{date}" as the date)
# When I select "April 26, 1982" as the "Date of Birth" date
When /^"([^\"]*)"の時間として"([^\"]*)"を選択する$/ do |date_label, date|
  When %Q(I select "#{datetime_label}" as the "#{date}" date)
When /^"([^\"]*)"をチェックする$/ do |field|
  When %Q(I check "#{field}")
When /^"([^\"]*)"のチェックを外す$/ do |field|
  When %Q(I uncheck "#{field}")
When /^"([^\"]*)"を選択する$/ do |field|
  When %Q(I choose "#{field}")
When /^"([^\"]*)"としてファイル"([^\"]*)"を選択する$/ do |field, path|
  When %Q(I attach the file at "#{path}" to "#{field}")

これは便利。感謝です。

でfeatureの中身は、

フィーチャ: 記事
  In order to [goal]
  [stakeholder]
  wants [behaviour]

  背景:
    前提 ユニークIDを持っていること

  シナリオ: 新しい記事の登録をする。
    前提 "docomo"でアクセスしている
    もし 記事登録ページへ"email"が"test@example.com"でアクセスする
    かつ "お名前"に"わたくしです"と入力する
    かつ "メールアドレス"に"hoge@example.com"と入力する
    かつ "送信する"ボタンをクリックする

    ならば "わたくしです"と表示されていること
    かつ "hoge@example.com"と表示されていること

これで、rake featuresと実行すればテストできる!!

ただ上記の例で書いている通り、前提に携帯電話のエージェントを指定させているのですが、これはjpmobileでテストしたかった名残です。

結構頑張って色々コード書いたのですが、文字コードの問題なのかうまい事テストできなかったです。これもあとで出来るようにしておきたいな。

PCだけならすごく便利!

お隣のプログラマがとっても幸せそうだったのでチェック!

rspecとrspec-railsは入っているものとしています。

以下を入れます。

sudo gem install nokogiri
sudo gem install webrat
sudo gem install cucumber
sudo gem install term-ansicolor
sudo gem install treetop
sudo gem diff-lcs

テスト用のプロジェクトを作って確認します。

% rails -d mysql testhoge
% cd testhoge
% rake db:create:all
% ./script/generate cucumber
% ./script/generate rspec
% ./script/generate rspec_scaffold Post name:string content:text
% ./script/generate feature Post name:string content:text
% rake db:migrate

feature/以下にファイルができています。
manage_posts.featureとか。

とりあえず実行してみます。

% rake features
(in /home/works/code/rails/testhoge)
/opt/local/bin/ruby -I "/opt/local/lib/ruby/gems/1.8/gems/cucumber-0.3.0/lib:lib" "/opt/local/lib/ruby/gems/1.8/gems/cucumber-0.3.0/bin/cucumber" --format pretty --require features/step_definitions/post_steps.rb --require features/step_definitions/webrat_steps.rb --require features/support/env.rb --require features/support/paths.rb features/manage_posts.feature
Feature: Manage posts
  In order to [goal]
  [stakeholder]
  wants [behaviour]

  Scenario: Register new post                # features/manage_posts.feature:6
    Given I am on the new post page          # features/step_definitions/webrat_steps.rb:6
    When I fill in "Name" with "name 1"      # features/step_definitions/webrat_steps.rb:22
    And I fill in "Content" with "content 1" # features/step_definitions/webrat_steps.rb:22
    And I press "Create"                     # features/step_definitions/webrat_steps.rb:14
    Then I should see "name 1"               # features/step_definitions/webrat_steps.rb:93
    And I should see "content 1"             # features/step_definitions/webrat_steps.rb:93

  Scenario: Delete post                    # features/manage_posts.feature:14
    Given the following posts:             # features/step_definitions/post_steps.rb:1
      | name   | content   |
      | name 1 | content 1 |
      | name 2 | content 2 |
      | name 3 | content 3 |
      | name 4 | content 4 |
    When I delete the 3rd post             # features/step_definitions/post_steps.rb:5
    Then I should see the following posts: # features/step_definitions/post_steps.rb:12
      | name   | content   |
      | name 1 | content 1 |
      | name 2 | content 2 |
      | name 4 | content 4 |

2 scenarios
9 passed steps

Given~とかWhen~とかは緑色になってました。

動く事を確認できました。

次回は、

  • 出力結果を日本語
  • feature自体を日本語で書く

ようにしてみたいと思います。

目標的にはRailsの下にsinatraをミドルウェアとしてrackupするつもり。

現時点では単体で動かせれば良いのでその辺りまでかな。

rack,sinatra,thinはさくっとgem installしておきます。たぶんどれか入れればrackが入るはず。ログとってなかった。

nginxはソースファイルからインストールします。stableです。

# wget http://sysoev.ru/nginx/nginx-0.6.35.tar.gz
# tar zxvf nginx-0.6.35.tar.gz
# cd nginx-0.6.35
# ./configure --prefix=/usr/local/nginx --with-http_ssl_module
# make
# make install
# cd /usr/local/nginx/conf
# vi nginx.conf
pid        /var/run/nginx/nginx.pid;
worker_processes  3;

events {
    worker_connections  1024;
}
http {
    include       mime.types;
    include       proxy_conf;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  0;

    upstream radiant {
        server unix:/var/tmp/thin.0.sock;
        server unix:/var/tmp/thin.1.sock;
        server unix:/var/tmp/thin.2.sock;
    }
    upstream rackup {
        server 127.0.0.1:9292;
    }
    server {
        listen 80;
        server_name example.net;
        access_log /var/log/nginx/radiant.access.log;
        root /var/www/radiant/public;
        index index.html;

        location / {
            proxy_pass http://radiant;
        }
    }

    server {
        listen 80;
        server_name test.com;
        access_log /var/log/nginx/rackup.access.log;
        location / {
            proxy_pass http://rackup;
        }
    }
}

# vi proxy_conf
proxy_redirect          off;
proxy_set_header        Host            $host;
proxy_set_header        X-Real-IP       $remote_addr;
proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size    10m;
client_body_buffer_size 128k;
proxy_connect_timeout   90;
proxy_send_timeout      90;
proxy_read_timeout      90;
proxy_buffers           32 4k;

pidの位置を/var/run以下にしているのはなんとなくまとめておきたいからです。

radiantの設定とか入ってるけど気にしない。

upstreamでソケットとかポートとか書いてあげてproxy_passで指定するだけ。

server{}でapacheのバーチャルホストっぽいことができる。

sslは次回持ち越し。

proxy_confはサンプルコピペなのであんまり精査してない。

とりあえず動かす設定なので内容もあまり吟味してない。

たぶんrootで実行しちゃまずい気がするけどテストだしいいよね。

# /usr/local/nginx/sbin/nginx

これで準備OK。再起動するときは、

# kill -HUP `cat /var/run/nginx/nginx.pid`

こんな感じ。

やっとsinatraへ。ちょっとサイトが更新されててびっくりした。

ばびっと用意。

# cd /var/www
# mkdir app
# cd app
# touch routes.rb config.ru
# mkdir public tmp views log

sinatraコントローラ作成。

目的のrackミドルウェアとするには、Sinatra::Baseを継承したクラスを作り、そのなかに書いておくのがやりかたとしてあるっぽいけど、それは次回ということで。

# vi routes.rb
require 'rubygems'
require 'sinatra'

get '/' do
  erb :home
end

not_found do
  'This is nowhere to be found'
end

error do
  'Sorry there was a nasty error - ' + env['sinatra.error'].name
end

sinatraテンプレート作成

# vi views/home.erb
halt.

rack設定

# vi config.ru
require 'rubygems'
require 'sinatra'

set :run, false
set :environment, :production

require 'routes.rb'
run Sinatra::Application

前回テストしたrackの設定に比べて簡素。

こんどはthinの設定ファイルを作ります。

# thin config -C thin.yml
# vi thin.yml
pid: /var/run/thin/thin.pid
timeout: 30
log: log/thin.log
max_conns: 1024
require: []
max_persistent_conns: 512
environment: production
servers: 1
daemonize: true
chdir: /var/www/html
rackup: /var/www/html/config.ru
port: 9292
address: 127.0.0.1

この変も割と適当。

ここもpidを/var/run/thinとかにしているので、ディレクトリつくってあげないと起動してくれません。

sinatraだけなら、ruby routes.rb -e production とかやるだけですが、thinの設定でrackup: /var/www/html/config.ruとしてます。
これ、rackもsockでやればよかった・・まあいいや。

でthin起動。

# thin start -C thin.yml

nginxは起動しているので、ブラウザからtest.comの80番へアクセスすれば表示されます。

された!

色々とおかしな状況だったので整理。

Ruby 1.8.7
Rails 2.2.2
rspec 1.1.11
rspec-rails 1.1.11
rcov 0.8.1.2.0

とりあえず、rake specが2回実行されたので、vendor/plugins以下のrspecとrspec-railsを消します。
それから、rake spec:rcovを実行。

% rake spec:rcov

(中略)

/opt/local/lib/ruby/1.8/rexml/formatters/pretty.rb:131:in `[]': no implicit conversion from nil to integer (TypeError)
        from /opt/local/lib/ruby/1.8/rexml/formatters/pretty.rb:131:in `wrap'
        from /opt/local/lib/ruby/1.8/rexml/formatters/pretty.rb:131:in `wrap'
        from /opt/local/lib/ruby/1.8/rexml/formatters/pretty.rb:90:in `write_text'
        from /opt/local/lib/ruby/1.8/rexml/formatters/default.rb:50:in `write'
        from /opt/local/lib/ruby/1.8/rexml/formatters/pretty.rb:75:in `write_element'
        from /opt/local/lib/ruby/1.8/rexml/formatters/pretty.rb:73:in `each'
        from /opt/local/lib/ruby/1.8/rexml/formatters/pretty.rb:73:in `write_element'
        from /opt/local/lib/ruby/1.8/rexml/formatters/default.rb:31:in `write'
        from /opt/local/lib/ruby/1.8/rexml/formatters/pretty.rb:75:in `write_element'
        from /opt/local/lib/ruby/1.8/rexml/formatters/pretty.rb:73:in `each'
        from /opt/local/lib/ruby/1.8/rexml/formatters/pretty.rb:73:in `write_element'
        from /opt/local/lib/ruby/1.8/rexml/formatters/default.rb:31:in `write'
        from /opt/local/lib/ruby/1.8/rexml/formatters/pretty.rb:117:in `write_document'
        from /opt/local/lib/ruby/1.8/rexml/formatters/pretty.rb:111:in `each'
        from /opt/local/lib/ruby/1.8/rexml/formatters/pretty.rb:111:in `write_document'
        from /opt/local/lib/ruby/1.8/rexml/formatters/default.rb:28:in `write'
        from /opt/local/lib/ruby/1.8/rexml/document.rb:195:in `write'
        from (eval):93:in `pretty'
        from /opt/local/lib/ruby/gems/1.8/gems/rcov-0.8.1.2.0/lib/rcov/report.rb:1003:in `create_file'
        from /opt/local/lib/ruby/gems/1.8/gems/rcov-0.8.1.2.0/lib/rcov/report.rb:708:in `execute'
        from /opt/local/lib/ruby/gems/1.8/gems/rcov-0.8.1.2.0/lib/rcov/report.rb:125:in `each'
        from /opt/local/lib/ruby/gems/1.8/gems/rcov-0.8.1.2.0/lib/rcov/report.rb:125:in `each_file_pair_sorted'
        from /opt/local/lib/ruby/gems/1.8/gems/rcov-0.8.1.2.0/lib/rcov/report.rb:707:in `execute'
        from /opt/local/lib/ruby/gems/1.8/gems/rcov-0.8.1.2.0/lib/rcov.rb:640:in `dump_coverage_info'
        from /opt/local/lib/ruby/gems/1.8/gems/rcov-0.8.1.2.0/lib/rcov.rb:640:in `each'
        from /opt/local/lib/ruby/gems/1.8/gems/rcov-0.8.1.2.0/lib/rcov.rb:640:in `dump_coverage_info'
        from /opt/local/lib/ruby/gems/1.8/gems/rcov-0.8.1.2.0/bin/rcov:421
rake aborted!

rexmlがなんかおかしいっぽい。
なので直修正。

vi /opt/local/lib/ruby/1.8/rexml/formatters/pretty.rb:130
+ place = string.rindex(' ', width) || width

めでたくrake spec:rcovが実行されました。

いつも最初にきちんと設計するんですが、あれこれと仕様変更でどんどんコードが変わっていく・・

しかもRspecでテスト書いているので仕様変更にも柔軟に対応できてしまう。

そうなると、後でこれってどうなってたっけ?みたいなことになる。

そんなときに簡単に設計をチェックできる、いわゆるリバースエンジニアリングに近いことができます。

http://www.graphviz.org/

http://railroad.rubyforge.org/

# sudo port install graphviz
# sudo gem install railroad

RAILS_ROOTに移動して、コマンド実行。

# railroad -C -o doc/controllers.dot
# railroad -M -o doc/models.dot
# neato -Tpng doc/controllers.dot > doc/controllers.png
# neato -Tpng doc/models.dot

簡単なクラス図を生成してくれます。

いちいちコマンドが面倒なのでrakeタスクにしちゃいます。

namespace :doc do
  namespace :railroad do
    task :models do
      sh "railroad -i -l -a -m -M -o doc/models.dot"
      sh "neato -Tpng doc/models.dot > doc/models.png"
    end

    task :controllers do
      sh "railroad -i -l -C -o doc/controllers.dot"
      sh "neato -Tpng doc/controllers.dot > doc/controllers.png"
    end
  end
  task :railroad => %w(railroad:models railroad:controllers)
end

私はコントローラーのアクションとか確認するのによく使います。