2012年4月26日木曜日

Ruby on Rails + RSpec 画像アップロードのテスト方法

画像をformからアップロードする時のバリデーションテストをどうするか?
という話をメモしておきます。
[環境]
CentOS6.2
Rails 3.2.2
Ruby 1.9.3-p125


(1)テスト対象のModel

前回に引き続き
簡易blog(食べたもの記録)アプリの記事本体部分を想定。
Modelの名前はDiningとします。
app_root/db/migrate/[date]_dinings.rbより抜粋すると
class CreateDinings < ActiveRecord::Migration
  def change
    create_table :dinings do |t|
      t.date :date      #日付
      t.string :time   #「朝」「昼」「夜」が入る
      t.string :menu #「食べたもの」
      t.integer :price  #「お値段」
      t.string :shop   #「店」
      t.string :place  #「店の場所」
      t.integer :evaluetion #1~5の5段階評価
      t.string :impression #感想
      t.string :ctype    # 写真が指定されている時写真のcontent_type
      t.binary :photo  #写真

      t.timestamps
    end
  end
end

写真データを直接DBに格納するモデルです。 ここでは ・画像ファイルのcontent-typeが画像で無い時バリデーションエラーになること をテストしていきます。

(2)テスト

app_root/spec/model/dining_spec.rbより抜粋
describe "写真(photo)" do
    it "content-typeが画像の時バリデーションエラーが発生しないこと" do
      @dining.data = Rack::Test::UploadedFile.new(Rails.root.join("test/fixtures/test.jpg"), "image/jpeg")
      @dining.should be_valid
    end
    it "content-typeが画像以外の時バリデーションエラーが発生すること" do
      @dining.data = Rack::Test::UploadedFile.new(Rails.root.join("test/fixtures/test.jpg"), "image/text")
      @dining.should be_valid
    end
  end

Rack::Test::UploadedFile.new()が仮想アップロードファイルをテストするためのメソッドで 第1引数にテスト画像,第2引数にcontent-typeを設定します。

(3)バリデーションの実装

テストがでけたので。 app_root/app/model/dining.rbにバリデーションを実装していきます。
#coding:utf-8
class Dining < ActiveRecord::Base

#中略

  validate :file_invalid? #写真のバリデート

  def data=(data)
    self.ctype = data.content_type
    self.photo = data.read
  end

  def file_invalid?
    if !self.photo.nil? then #写真が指定されてる時
      ps = ['image/jpeg', 'image/gif', 'image/png']
      errors.add(:photo, 'は画像ファイルではありません') if !ps.include?(self.ctype)
     end
  end

end
テスト/実装コードにもある通りフォーム側で画像をアップロードする時に、 直接model上のカラムである:imageではなく、:dataという仮のパラメータにセットして アップロード時に:ctypeと:imageを:dataから抽出してセットしている点が注意が必要。 form側では
<%= f.label :data, "写真" %> <%= f.file_field :data %>
という記述でアップロードフォームを実現しています。

2012年4月20日金曜日

スカパー!e2とひかりTVを比較検討した。

というわけで。
CS放送契約を検討中でして、現住居(都内の賃貸ワンルーム)の環境では
スカパーe2(スカパーHDはアンテナ立てられず断念)とひかりTVが選択肢になりました。
そこでスカパーe2の16日間無料とひかりTVの2ヶ月無料キャンペーンを利用して
両者のサービスを体験し、比較した結果をメモしておきます。

スカパー!e2

必要なもの

・CS110度対応アンテナ または フレッツTV
アンテナは無料でつけてくれるキャンペーンが今やってるそうです。
うちの場合はマンションの共聴設備で対応してました。

料金

月490円 + 基本パック3,570 円 + オプションチャンネル (Jリーグをみるなら2,580円)

メリット

・Jリーグ全試合生で見られるオプション有(スカパーのみ)
・テレビ内蔵のEPGが使える為動作・視聴予約が軽快(AQUOS使ってます)

デメリット

・(ひかりTV比で)料金が高い。
・別途レコーダーが無いと録画不可
・成人系ch無し


ひかりTV

必要なもの

・光Bフレッツ回線
・IPv6対応ルータ

料金

・月2,625円(テレビざんまいプラン) + チューナーレンタル料 525円 + オプションチャンネル
・月3,675円(おねうちプラン) + チューナーレンタル料 525円 +オプションチャンネル(テレビに加えてビデオのいくつかが見放題)

メリット

・外付けHDD取り付けで録画可能。
・オンライン録画予約可(スマホ/PCから可能なので撮り忘れが減る)
・料金安
・囲碁・将棋チャンネルがある(ただし2012年後半にはスカパー対応予定)
・成人系chがある(メリットかどうか)

デメリット

・Jリーグは諦めないといけない。
・UIがかなりもっさり。
単体で使うとマシかもしれないですがテレビ内蔵EPG等と比較するとさすがに重い。
NetFront(UIブラウザ)のせいなのか、STBのスペックがたりてないのか。
・番組表の見通しが悪い
縦の視認性が4時間分しかないので録画しておきたい番組があるかどうかガイド誌みたほうが早い
・リモコンショートカットが全画面視聴中に効かない
裏番組表表示中でないとショートカットできないのもちょっとイライラ


まとめ

・ひかりTV(テレビざんまい)+スカパー!e2 Jリーグパックが贅沢ながら個人的には満足度を最大化できる組み合わせな気がします。(がJパックの方は諦めるかもしれません。)
・人がこの調べ物してひかりTVのSTB申し込んだ直後にSonyが3波対応のnasne出してきたのでスカパーに乗り換えるかもしれません。
・ビデオ見たいならhuluのが安いしよさそう
・おうちでえろえろなものを見たい人はひかりTV一択です

2012年4月13日金曜日

Rails3.2 + RSpecで楽しいTDD(ModelのValidationテスト)

というわけでModelのバリデーション機能をテストしていきます。

[環境]

CentOS6.2
Rails 3.2.2
Ruby 1.9.3-p125

(1)テスト対象のModel

簡易blog(食べたもの記録)アプリの記事本体部分を想定。
Modelの名前はDiningとします。
app_root/db/migrate/[date]_dinings.rbより抜粋すると

class CreateDinings < ActiveRecord::Migration
  def change
    create_table :dinings do |t|
      t.date :date      #日付
      t.string :time   #「朝」「昼」「夜」が入る
      t.string :menu #「食べたもの」
      t.integer :price  #「お値段」
      t.string :shop   #「店」
      t.string :place  #「店の場所」
      t.integer :evaluetion #1~5の5段階評価
      t.string :impression #感想
      t.string :ctype    # 写真が指定されている時写真のcontent_type
      t.binary :photo  #写真

      t.timestamps
    end
  end
end

写真データを直接DBに格納する荒っぽいモデルですが利用者が1人ということでご勘弁を。 このモデルに対して
1.必須パラメータが空の時バリデーションではじかれること
2.必須で無いパラメータが空の時バリデーションではじかれないこと
3.長さの最大長があるものに関しては境界値動作が正しいこと
を確認するためのコードを書いていきます。

(2)テストデータの準備
app_root/spec/fixtures/dinings.ymlに以下の内容のテストデータを準備します。
#coding:utf-8
valid:
  date: 2012-03-22
  time: "朝"
  menu: "おうどん"
  price: 290
  shop : "おにやんま"
  place: "五反田"
  evaluetion: 4
  impression: "おいしい"
  ctype : "image/jpeg"
  photo:
 
ここではバリデーションに引っかからないようなデータを用意しておきます。
(3)テストコードの実装
app_root/spec/models/dinings_spec.rb
にテストコードをバリバリ書いていきます。
#coding:utf-8
require 'spec_helper'

describe "Dining バリデーションテスト" do

  fixtures :dinings #テストデータを設定
  before(:each) do #全テスト実行前の共通動作
    @dining = dinings(:valid) #dinings.ymlの (valid:)で指定されているデータを設定
  end
  it "dateが空の時:エラーが発生すること" do
    @dining.date = ""
    @dining.should_not be_valid #be_validで無いことを確認する
  end

さてこれでテストを実行すると(sporkはたちあげておいて下さい)
$ rspec --drb --drb spec/models/dining_spec.rb

バリデーションコード書いてないので失敗(日付が空だけどbe_valid)します。 4.テストを通すコードの実装
というわけでテストを通すコードを書いていきます。
app_root/app/model/dining.rb
#conding:utf-8
class Dining < ActiveRecord::Base
  validates :date,
    :presence => true #dateは空を許さない
end

これでテストを再度実行すると無事テストが成功。
というようなサイクルで
テストコード→実装→テスト通った嬉しいを繰り返していくと
段々緑色が見える度に脳内麻薬が出てコード書くのにboostがかかるのでおすすめです。
あと赤色が緑にならないと気持ち悪くて寝られなくなる。
次回は写真アップロードテストのやり方あたりをメモしておきます。

2012年4月12日木曜日

Rails3.2 + RSpecで楽しいTDD(導入編)

さて、標題にはTDDと書いてますが実際にはコード書いてからテスト書いてたりしてます。

手元でちまちま書いてるアプリもさすがにテストも無しではいかんだろうということで
まずは手元にあるコードのカバレッジが100%となるテストを書き、
その後次に何か乗っける時からはテストから書いていきたいと思います。

というわけで今回はテスト環境の導入編から。
この記事で前提とする環境は
・CentOS6
・Ruby on Rails 3.2.2
・Ruby 1.9.3 p-125
・MySQL
となっています。

(1)テストに必要なGemの導入
app_root/Gemfileに以下の記述を追加
gem 'rspec-rails'
gem 'simplecov', :require => false
#gem 'simplecov-rcov', :require => false
gem 'spork'

'rspec-rails'は文字通りRailsでRSpecテストを行うためのGem
'simplecov'はカバレッジ計測の為のGem(rcovはRuby 1.9系列では使用できない為)
'simplecov-rcov'はカバレッジレポートをrcovと同等の形で出力するためのgemで
Jenkins先生の出番がある人以外はいらないと思います。
sporkはTwiwt:Blog / jugyo : spork でサクサク RSpec on Rails3
こちらを参照。テストを高速に実行しまくる為に導入します。そして
$ bundle install

ここまでが済んだ状態で既存のものに対するテストのひな形を作る場合は
$ rails g rspec :controller hoge hage ...

てな感じで実行してみると(hoge,hageは自分が作ったコントローラ名)
app_root/spec/以下に自動生成されたテストケースのひな形ができると思います。

(2)テストの準備
1.sporkを起動する
テスト実行用のサーバーことsporkを起動します。
最初の1回のみ以下の設定が必要です。
$ spork --bootstrap

終われば
$ spork 

で起動。
2.カバレッジ計測処理の追加
app_root/spec/spec_helper.rbの先頭に以下の記述を追加
require 'rubygems'
require 'spork'

Spork.prefork do
end

Spork.each_run do
end

require 'simplecov'
require 'simplecov-rcov'
SimpleCov.start 'rails'



(3)テスト実行

$ rspec --drb spec/model/user_spec.rb

とこんな感じで実行できます。

$ rspec --drb spec/*

とかでまとめて実行も。
カバレッジは app_root/coverage/index.html
を見るとわかりやすく表示されていると思います。
次はmodelのバリデーションテストの書き方をメモしておこうと思います。

2012年4月4日水曜日

Ruby on Rails3.2でログイン機能を実装する。

というわけで。
簡易なブログアプリを作っていて、記事の投稿・編集・削除はログインした管理者のみで行いたいので
Rails3.1で追加された認証関連機能を使ってログイン機能をつけました。

参考にしたエントリー
ASCIIcasts - “Episode 270 - Rails 3.1の認証機能"

以下作業メモ
[環境]
CentOS6.2
Ruby 1.9.3(p125)
Ruby on Rails 3.2.2

1. Gemfileにbcryptrubyを追加
Gemfile内のgem 'bcrypt-ruby', '~>3.0.0~のコメントアウトを外した上で
bundle install

2.ユーザmodelの作成
$ rails g model user name:string password_digest:string
$ rake db:migrate

※password_digestのカラム名は変更してはいけない
/app/model/users.rbにhas_secure_passwordを追加
class User < ActiveRecord::Base  
  has_secure_password  
end  
3.管理アカウントの追加 とりあえずユーザアカウントは管理用アカウント1つでいいので、新規登録画面はつけずに直接DBに追加
$rails c
User.create!(:name => "admin", :password => "hoge", :password_confirmation => "hoge")
本来はpasswordなんてカラム無いのでエラーになるはずがこれで登録できている (DB上にはハッシュ化された値で登録されている)
4.ログイン機能実装 ・まずはコントローラの生成
$rails g controller sessions
・ログインフォーム(app/view/sessions/new.html.erb)

Log in

<%= form_tag sessions_path do %>
<%= label_tag :name, 'login name' %> <%= text_field_tag :name, params[:name] %>
<%= label_tag :pass, 'password' %> <%= password_field_tag :pass , params[:pass]%>
<%= submit_tag "ログイン" %>
<% end %>
・ルートの追加 config/routes.rbに以下を追加
resources :sessions do
  end
・コントローラ処理実装 app/controller/session_contoroller.rb ログインに成功したらトップヘリダイレクト 失敗したら同じフォームを再描画
def index
      render "new" 
  end
  def create
    user = User.find_by_name params[:name]
    if user && user.authenticate(params[:pass])
      session[:user_id] = user.id
      redirect_to root_path
    else
      flash.now.alert = "Invalid"
      render "new" 
    end
  end
  def destroy
    session[:user_id] = nil
    redirect_to root_path
  end
5.ビュー用ヘルパーメソッドの定義 ビューからログインユーザを参照できるようにする app/controller/application_contoroller.rb
class ApplicationController < ActionController::Base
app/controller/application_contoroller.rb
  protect_from_forgery

  def current_user
    @current_user ||= User.find(session[:user_id]) if session[:user_id]
  end

  helper_method :current_user
end
6.ビューにログイン機能へのパスを追加
<% if current_user == nil %>
  <%= link_to "ログイン", sessions_path %>
<% else %>
  <%= link_to 'ログアウト', session_path(current_user.id), :confirm => 'ログアウトしますか?', :method => :delete %>
<% end %>
こんな感じでログインされてなければログイン表示・ログインしていればログアウト表示に切り替える。
あとは同様にログイン中のみ新規作成等を行えるようにしていけば目的は達成できた。
しかし簡素に書けて便利ですね。

2012年4月3日火曜日

CentOS6.2 + Rails 3.2.2 + Passenger+nginx環境構築メモ

とりあえずモダンな環境を試してみたいミーハー心で環境を構築
Rails 3.2.2をインストール済みの環境にpassenger+nginx環境を構築する際の
作業内容メモ
[環境]
CentOS6.2
rubyは

1. Passengerをインストール

$ gem install passenger


2.nginxのインストール
$ rvmsudo passenger-install-nginx-module

標準では/opt/nginx/に入る。

3.nginxの設定
/opt/nginx/conf/nginx.confに以下を追加

user = hoge; #適宜変更
server {
        listen       80;
        server_name  localhost;
        root /opt/my_app/public;   #追加
        passenger_enabled on;       #追加
        rails_env development;      #追加
        charset utf-8;             #追加
...
最初rails_envがproductionモードに標準でなっていることに気づかず、
手元で作っているアプリがnginx起動後に403やらなんやらで起動できなくハマりました。

4.nginx起動スクリプトの設定
/etc/init.d/にnginxを作成し
RedHatNginxInitScriptの中身を貼り付ける

nginx="/usr/sbin/nginx"
NGINX_CONF_FILE="/etc/nginx/nginx.conf"
はそれぞれ
nginx="/opt/nginx/sbin/nginx"
NGINX_CONF_FILE="/opt/nginx/conf/nginx.conf"
に変更

5.起動確認
httpdを停止してから
nginx="/usr/sbin/nginx"
/opt/nginx/sbin/nginx -s
http://localhost/にアクセスするとrailsの初期ページが無事表示。
環境も表示されてます。

微妙にハマったけれども
お試しで作ってる簡易ブログアプリが無事動作。