Hello, Sinatra! (1)
いまさらSinatraを始めてみた。
モチベ
- 個人で新しい何かを作り始めた。個人でやるからには、仕事じゃ使えないけど気になってる技術を使った方がいいので、RailsじゃなくてSinatraを使ってみることにした。
- Railsやってると、あんまりRackとか下のレイヤーを意識しないので、そこらへんに前から興味があった。
- Sinatraがダメだったら、Padrinoやるかも。
versions
- 2012年10月7日時点で最新のもの
sinatra install
# Gemfile source :rubygems gem 'sinatra'
$ bundle install --path vendor/bundle --binstubs
- とりあえずRailsのときと同様に
vendor/bundle
以下に入れる。
# app.rb require 'bundler' Bundler.require get '/' do 'Hello, Sinatra!' end
$ ruby -rubygems app.rb
- http://localhost:4567 で起動を確認。
- スモールスタートがいい。
rabl
# Gemfile source :rubygems gem 'sinatra' gem 'rabl'
$ bundle
- JSONのレスポンスをテンプレートで記述したかったので
rabl
をインストール
# app.rb require 'bundler' Bundler.require Rabl.register! get '/' do render :rabl, :home, :format => :json end
# views/home.rabl node(:greeting) do 'Hello, Sinatra with rabl!' end
- 最近、rabl開発メンバーがsinatraに公式サポートのpull requestを送って、sinatraでrablが公式にサポートされるようになったっぽい。
- そのせいか、公式ドキュメント通りではうまくいかず結構時間かかった。
config.ru
# config.ru require 'bundler' Bundler.require require './app' run App
# app.rb require 'sinatra/base' class App < Sinatra::Base Rabl.register! get '/' do render :rabl, :home, :format => :json end end
- Herokuやpowで起動する際config.ruが必要っぽい。
run [app name]
は必須。これがないと動かない。
pow
# Gemfile group :development do gem 'powder' gem 'guard-pow' end
$ bundle $ powder link $ guard pow init
- sinatraでは、ファイルを変更するたびにサーバーを再起動する必要があってめんどくさい。
- そこで、guard-powを使うことでその作業を自動化する。
- Guardfileはこんな感じにしてみた。
# Guardfile guard 'pow' do watch('Gemfile') watch('Gemfile.lock') watch('app.rb') watch('config.ru') end
heroku & thin
# Gemfile group :production do gem 'thin' end group :development do gem 'heroku' gem 'powder' end
# Procfile web: thin start -p $PORT -e $RACK_ENV
- herokuはすんなりデプロイできた。
- developmentはpowサーバを使うのでproductionのみthinをインストール。
- Procfileにwebを書いておくと、Herokuのデフォルトのアプリケーションサーバを変更できる。デフォルトはWEBRickなのでthinに変える。thinの方がパフォーマンスがいいらしい。
activerecord
# Gemfile gem 'sinatra-activerecord' gem 'rake' group :production do gem 'pg' end group :development, :test do gem 'sqlite3' end
$ bundle
- sinatraで使えるactiverecordと各環境用のアダプターをインストール。
# Rakefile require 'bundler' Bundler.require require 'sinatra/activerecord/rake' require './app' require './config/environment'
$ rake -T
rake db:create_migration
rake db:migrate
rake db:rollback
# config/environment.rb require 'uri' configure :development do set :database, 'sqlite:///db/development.db' end configure :test do set :database, 'sqlite:///db/test.db' end configure :production do db = URI.parse(ENV['DATABASE_URL'] || 'postgres://localhost/mydb') ActiveRecord::Base.establish_connection( :adapter => db.scheme == 'postgres' ? 'postgresql' : db.scheme, :host => db.host, :port => db.port, :username => db.user, :password => db.password, :databasee => db.path[1..-1], :encoding => 'utf8' ) end
- githubのREADMEにしたがってRakefileで必要なものを
require
すると、マイグレーションができるようになる。 - sqliteのパスは
///
とスラッシュ3つなのが注意。 - heroku用の設定はherokuのdevcenter(ここ)で紹介されたものをコピペした。
$ rake db:create_migration $ rake db:migrate
- ちなみにテストなど環境を指定する場合は
RAILS_ENV
の代わりにRACK_ENV
を使う。
$ rake db:migrate RACK_ENV=test
$ git push heroku master $ heroku run rake db:migrate Running `rake --trace db:migrate RACK_ENV=production` attached to terminal... up, run.1 ** Invoke db:migrate (first_time) ** Execute db:migrate rake aborted! could not connect to server: Connection refused Is the server running on host "localhost" and accepting TCP/IP connections on port 5432?
- DBに接続できない。。。
連休やったのはここまで。 時間があれば(2)もやる予定。
ちゃんとテスト書き始めた話
テストのモチベ=怖いからやる
- 正直に白状すると、「これまでテスト書いたことない && 会社にテストの文化がない && テスト書いてる時間ない」っていう状況で、時間を割いてでもテストを書こうっていうモチベがなかなか湧かなかった。
- そんななか、唯一、ハッキリとしたわかりやすいモチベは「デグレが怖い」という恐怖心から解放されることだった。
- プロジェクトが大きくなるほど、自分が書いたコードがどこまで影響するか把握できなくなってくる。だからといって、変更のたびにブラウザでポチポチ一個ずつ確認する作業はだるい。
- テストが通ってるという事実が抜群の安心感をもたらすことがわかってきて、ちゃんとテストを書くようになったというお話です。
500返ってないか怖い
- 一番わかりやすいテスト項目として「ユーザーにエラー画面を表示していないか」というのがまずアタマに浮かんだ。
- モデルとか変更すると、影響範囲よくわからないし、怖い。
- 手っ取り早く全部のアクションで500返ってないかテストする方法を考えてみた。
response.should be_success
的なレスポンスをチェックするテストにタグをつけて、全コントローラーをまたいでレスポンスをチェックするテストだけを実行するってやり方を考えてみた。全コントローラーのテストは時間かかって、たぶん手元では試さなくなりそうだから。
# spec/spec_helper.rb RSpec.configure do |config| config.treat_symbols_as_metadata_keys_with_true_values = true end
- RSpec 2.xだとこの設定が必要らしい。
it 'hogehoge', status: true
みたいなのをit 'hogehoge', :status
で書けるようになる。
# lib/tasks/spec/status.rake require 'rspec/core/rake_task' namespace :spec do namespace :controller do RSpec::Core::RakeTask.new(:status) do |spec| spec.pattern = 'spec/controllers/**/*_spec.rb' spec.rspec_opts = '--tag status' end end end
- 自分で
rake spec:controllers
的なテストのraketaskを定義したいときはRSpec::Core::RakeTask.new(:hoge)
使えばいいっぽい。 - 全コントローラーのテストで、
status
ってタグがついてるものだけ実行したいので、こんな感じ。
it 'returns successfully', :status do get :index response.should be_success end
$ rake spec:controllers:status
- これでコントローラーをまたいで
status
タグのついたテストだけ実行できる。 - 全コントローラーのテストは重すぎるので、これでかなり気軽にチェックできるようになると思う。
- とりあえずステータスコードをチェックするようなテストに
status
タグをつけておく、っていうルールを共有する必要はある。
スクリプトでDBを更新するの怖い
- ちょっと前に顧客のデータぜんぶ消しちゃった事件があったような気がする。
- ああいうのあるし、DBを更新する系のスクリプトはちゃんとテストしたい。
- 仕事では
rails r
でスクリプトを実行するんだけど、こういうのはどうやってテストすればいいのかわからなかったので調べたり試行錯誤した。
# config/environments/test.rb NaotySample::Application.configure do $LOAD_PATH.unshift "#{Rails.root}/script" end
script/
以下をrequire
するためにパスに追加しておく
# spec/scripts/create_naoty_spec.rb require 'spec_helper' require 'create_naoty' describe CreateNaoty do it 'creates naoty' do CreateNaoty.run User.where(name: 'naoty').first.should be_present end end
- スクリプトを読み込んでテスト内で実行する。
- モジュールのテストとたぶん同じやり方だと思う。やったことないけど。
# script/create_naoty.rb module CreateNaoty def self.run User.create(name: 'naoty') end end CreateNaoty.run if __FILE__ == $0
- 処理の中身をモジュールにまとめておいて、テスト内で実行しやすくしておく。
if __FILE__ == $0
でrequire
されたときにスクリプトが実行されるのを回避してる。
# Guardfile guard 'rspec' do watch(%r{^script/(.+)\.rb$}) {|m| "spec/scripts/${m[1]}_spec.rb" } end
- Guardを使って自動テストをやってるので、スクリプトテスト用の設定を追加しておく。
- これでスクリプトを変更したときに、それのテストを自動で実行するようになる。
まとめ
- 「怖いからテストする」というモチベはわかりやすい。
- 「怖いところをテストする」という方針であれば、「何をテストすべきか」を自ずと意識するようになる。
- rspec-railsで対応できなければ、rspecの便利機能を駆使して試行錯誤する。RSpec bookにお世話になってる。
参考
The RSpec Book (Professional Ruby Series)
- 作者: David Chelimsky,Dave Astels,Zach Dennis,角谷 信太郎,豊田 祐司,株式会社クイープ
- 出版社/メーカー: 翔泳社
- 発売日: 2012/02/22
- メディア: 大型本
- 購入: 5人 クリック: 112回
- この商品を含むブログ (12件) を見る
Rails開発環境 2012夏
5月に「Rails開発環境 2012初夏」という記事を公開してそこそこ好評だったので、最近導入してLife-Changingだったツールを「2012夏」バージョンとして紹介しようと思います。今回紹介するのは以下の3つです。
- pow + xip.io
- tmuxinator
- ctrlp.vim
1. pow + xip.io
pow + xip.ioによって同じネットワーク内にある、iPhoneやiPadのような他のデバイスからローカルサーバーに接続できるようになりました。これは、スマホ用サイトやアプリで使うAPIの開発で非常に重宝します。特に、実機でないと確認できないような場面では、pow + xip.ioがないと、ステージング環境にデプロイする必要が出てきて、非常に面倒です。
インストールは、公式ページにあるように以下のコマンドを入力するだけです。
$ curl get.pow.cx | sh
使い方としては、まず、Railsのプロジェクトルートへのシンボリックリンクを.powディレクトリに作ります。
$ cd ~/.pow $ ln -s ~/workspace/rails/cui-aboutme
すると、これだけでローカルサーバーが起動して、http://cui-aboutme.devでアクセスできます。簡単ですねー。
$ open http://cui-aboutme.dev
同じLANにあるデバイスからは、プライベートIPアドレスを使ってアクセスすることができます。
$ ifconfig ... inet 192.168.1.4 ...
ifconfig等で調べた結果、上のようになった場合、http://cui-aboutme.192.168.1.4.xip.ioで他のデバイスからもアクセスできます。
注意点としては、Lionでは「システム環境設定」→「共有」→「Web共有」を有効にしておく必要があります。これがオフになっててハマりました…><ちなみに、Mountain Lionでは「Web共有」の項目がなくなっていますが、手元では無事に成功しています。
実際にプロジェクトで使っていく中でのTipsをいくつかご紹介します。
powder
powderはpowの操作をカンタンに行うためのコマンドラインツールです。Gemfileからインストールします。
# Gemfile group :development do gem 'powder' end
シンボリックリンクを.powに作る操作や、サーバーを再起動する操作などをカンタンなコマンドで実行できます。
$ powder link
$ powder restart
詳細は公式ページを参照してください。
pry-remote
pryを使っている方は多いと思いますが、powのサーバーはrails sで起動するわけではないので、普通のやり方ではpryを使うことができません。そこで活躍するのが、pry-remoteです。これもGemfileからインストールします。
# Gemfile group :development, :test do gem 'pry-rails' gem 'pry-remote' end
使い方は、いつものbinding.pryの代わりにbinding.pry_remoteとコードに追加して、実行すると処理が止まります(見た目には分かりにくいけど…)。そこで、
$ pry-remote
と打つと、いつものpryコンソールに入れます。
ちょっと分かりにくいかもしれませんが、公式ページも見てもらって実際に使うと雰囲気がわかるとおもいます。
2. tmuxinator
tmuxinatorは、tmuxで起動するセッションをあらかじめ定義しておいて、コマンド一発で開発環境を起動することができるツールです。gemで配布されているので、bundlerでインストールします。公式ページにしたがって準備します。
$ gem install tmuxinator $ echo "[[ -s $HOME/.tmuxinator/scripts/tmuxinator ]] && source $HOME/.tmuxinator/scripts/tmuxinator" >> .zshrc $ source .zshrc
使い方としては、mux new [project name]でテンプレートを作って、起動するセッションを定義していきます。
$ mux new cui-aboutme
# .tmuxinator/cui-aboutme.yml project_name: cui-aboutme project_root: ~/workspace/rails/cui-aboutme tabs: - main: layout: tiled panes: - git fetch --prune && git status --short --branch - curl http://cui-about.me/users - tig - vim: vi - app: layout: even-horizontal panes: - rails c - tail -f log/development.log - test: guard
- 各項目で、起動時に実行するコマンドを定義しています。
- tabsで起動するタブ毎の設定を定義します。上の設定例だと、「main」「vim」「app」「test」の4つのタブを起動します。
- panesでタブ内で分割するペインを定義し、layoutでペインの配置を定義します。上の設定例だと、「main」タブに「git fetch等gitの操作」「curl等シェルの操作」「tigでコミットログのビューワー」の3つのペインを起動します。
その他、いろいろな設定ができるようなので詳しくは公式ページをご覧ください。
3. ctrlp.vim
ctrlp.vimは、Ctrl-pで起動するファイラーです。unite.vimと近いのかもしれませんが、僕はこっちの方がサクサクしてて操作もわかりやすくて好きです。下はスクリーンショットです。
.vimrcで以下のように設定しました。
" .vimrc Bundle 'kien/ctrlp.vim' let g:ctrlp_cmd = 'CtrlPMixed' let g:working_path_mode = 'rc' let g:custom_ignore = { ¥ 'dir': '¥.git¥|vendor/bundle¥|tmp', ¥ 'file': '¥.jpg$¥|¥.jpeg$¥|¥.png$¥|¥.gif$¥|¥.log' ¥ }
- Ctrl-pで起動するモードを file + mru + bufferを同時に検索するMixedにしています。これで「現在のディレクトリ以下」「よく使うファイル」「バッファ」の中から検索します。
- 'rc'モードにすることで、.gitがあるディレクトリを優先するみたいです。
- vendor/bundleやtmpといったディレクトリや*.logのような大きいファイルを無視することで、起動をスムーズにしています。
その他いろいろ設定があるようなので、ヘルプや公式ページをご覧ください。
cui-about.meをリリースしました
Gemfileを公開するサイト「Gemfile Freaks」を公開しました
Gemfileを公開するサイト「Gemfile Freaks」をおととい公開しました。土曜にだいたい作って、日曜にユーザー認証をつけました。
前々からほしいと思っていたので、自分で作ることにしました。gemってどれを使っていいのかわかんないし、あの人のGemfileとか、あのサービスのGemfileに興味があったので。
いまのところの機能はこんなところです。
- Gemfileをシンタックスハイライトする
- Markdownでコメントを残せる
- 作者で検索できる
で、時間があればこんな機能もつけていこうと思ってます。
- タグ(Heroku, MongoDB, AWS, etc...)をGemfileにつけられる、タグで検索できる
- レスポンシブ・デザインに対応して、電車とかヒマな時間にスマホで見れるようにする
- Gemfileをこのサイトに簡単にアップロード、ダウンロードできるgem
他にも要望があれば、githubでissuesを立ててくれれば答えますし、なんならpull requestしてくれるとうれしいです。
ということで、Gemfileを公開できるサイト「Gemfile Freaks」をよろしくおねがいします。
rablを使ってRailsのAPIレスポンスを簡潔に定義する
コントローラでJSONレスポンスを定義する場合、
class EntriesController < ApplicationController respond_to :json def index @entries = Entry.all respond_with @entries.to_json(:only => :title, :body, :include => { :user => { :only => :name }, :comment => { :only => :name }, :image => { :only => :file } }) end end
などと、to_jsonまたはas_jsonで:onlyや:includeオプションを使ってフィールドを指定できます。ただ、これでは冗長だし、より複雑な構造のレスポンスを定義するとなると面倒です。そこで、簡潔に複雑な構造を定義するためにrablというgemを使います。
gem 'rabl'
rablはjsonやxmlなどのAPIレスポンスを記述するためのDSLです。rablを使ってJSONレスポンスを書き換えます。
# app/views/entries/index.json.rabl collection @entries attributes :title, :body child(:user) do attributes :name end child(:comments) do attributes :name end child(:images) do node(:thumb) {|image| image.file.thumb.url } node(:main) {|image| image.file.main.url } end
nodeを使うと、to_jsonの:methodオプションと同じことができるので、構造の深い部分にある値を取得したいときに便利です。
rablで定義することによってコントローラもスリムになります。
class EntriesController < ApplicationController def index @entries = Entry.all end end
レスポンスはcurlで確認できます。
$ curl http://localhost:3000/entries.json
Rails開発環境 2012初夏
Herokuに移行したり、便利なツールを見つけて開発環境を修正したところがあるので「2012初夏バージョン」として拙者の開発環境を晒します。最後にGemfileを載せておきますが、変更したポイントは「Herokuへのデプロイ」「ソースコード公開」「ブラウザのリロードの自動化」の3つです。
Herokuへのデプロイ
VPSでの運用はいい勉強になったものの、以下のような問題がありました。
- 構築に時間がかかりすぎる。サービス開発への熱が冷めてしまう。プログラムを書くのに専念したい。
- 既に動いているシステムに支障が出るのが怖くて、新しいツール(例えばSSLとかログサーバーとか監視サーバーとか)を入れられない。
- セキュリティの問題
なるべくサービスの開発に時間をかけたいので、こうした運用をHerokuに任せてしまうことにしました。デプロイはcapistranoのように設定ファイルを書く必要すらなく、以下のようにとても簡単です。
$ heroku create -s cedar $ git push heroku master
ステージング環境を使いたい場合もcapistrano-ext入れて云々…みたいなのは必要なく、herokuコマンドで簡単に行えます。
$ heroku create -s cedar -r staging $ git push staging master
として、別アプリケーションを作ることでステージング環境を作ることができます。
Herokuへの移行に伴って問題となったのが、DBでした。今まではMySQLを使っていたのですが、Herokuでは標準のDBがPostgresqlです。それまでPostgresqlは使ったことがなかったのでHomebrewでインストールしたんですがけっこう苦労しました。Herokuへの移行で環境構築に時間をとられるようではVPSからHerokuに移った意味がなくなってしまうので、ローカルのDBをSQLiteにして本番のみPostgresqlで運用するようにしました。具体的には、database.ymlを以下のように設定しました。
development: adapter: sqlite3 database: db/development.sqlite3 pool: 5 timeout: 5000 test: adapter: sqlite3 database: db/test.sqlite3 pool: 5 timeout: 5000 production: adapter: postgresql encoding: unicode database: xxx_production username: xxx password: pool: 5 timeout: 5000
ソースコードの公開
自分の成果物をちゃんと他人に見える形で残そうと思い、ソースコードをGithubに公開するようにしました。公開にあたって問題となるのは、APIキーやステージング環境の認証パスワードといった機密情報の扱いでした。こうした情報をソースコードにハードコーディングせずにアプリケーションを動かす工夫が必要でした。そこで、僕は環境変数にこれらの情報を保存するようにしました。幸い、Herokuでは環境変数を簡単に設定する方法があります。
$ heroku config:add USERNAME=admin PASSWORD=xxx
そして、ローカルの開発環境でこれらの環境変数をロードするための方法としてforemanというgemを使いました。foremanはappサーバーや後述するguardなど複数のプロセスを同時に起動するのに便利なのですが、多くの環境変数をいっぺんにロードするのにも便利です。プロジェクトのルートディレクトリに.envというファイルを用意し、そこに環境変数をセットします。この.envを.gitignoreでgit管理下から除外しておけば、機密情報を公開せずにソースコードを公開することができます。
# Basic auth USERNAME=admin PASSWORD=xxx # S3 S3_ACCESS_KEY=xxx S3_SECRET_ACCESS_KEY=xxx
foremanを使ってappサーバーを起動する場合は以下のようにします。
$ foreman run rails s
また、Procfileを用意すればguardなど複数のプロセスを一度に起動できます。詳細は参考として載せたリンクを参照してください。
ブラウザのリロードの自動化
viewやscssを編集してデザインを細かく調整する際、なんどもブラウザをリロードするのが煩わしかったのですが、最近便利なツールを見つけました。LiveReloadというツールです。chrome, firefox, safariなど各種ブラウザの拡張機能でLiveReloadをインストールし、guard-livereloadというgemを使うことで、viewやscssの変更が保存されると自動的にブラウザをリロードしてくれます。言葉で説明するより、以下の動画を見た方がわかりやすいです。
感動的ですね!さっそく使いましょう。まずGemfileに追記します。
group :development do gem 'guard-livereload' end
インストールしてGuardfileに設定を加えます。
$ bundle install $ guard init livereload
あとは、以下の参考のリンクで各種ブラウザにLiveReloadをインストールします。
Gemfile
最後に僕が使っているGemfileの基本型を載せておきますので参考にどうぞ。
source :rubygems gem 'rails' # 標準のWebrickよりもパフォーマンスがよく、passengerなどよりも導入が簡単なので採用 gem 'thin' gem 'haml-rails' gem 'jquery-rails' group :assets do gem 'sass-rails' gem 'coffee-rails' gem 'uglifier' end group :development, :test do # HerokuのためにPostgresqlを入れるのは骨が折れるので開発時はSQLiteを採用 gem 'sqlite3' gem 'rspec-rails' # 設定をロードしたサーバーによってテストを高速化する gem 'spork' # ファイルの変更を検知する。OSX用 gem 'rb-fsevent' # テスト結果をGrowlで通知する gem 'growl' # ファイルの変更を監視してテストを自動的に実行する gem 'guard-rspec' # 設定ファイルの変更を監視してテストサーバーを再起動する gem 'guard-spork' end group :development do gem 'heroku' # 環境変数をロードして複数のプロセスを実行する作業を自動化 gem 'foreman' # viewやcssの変更を監視してブラウザを自動的にリロードする gem 'guard-livereload' # デバッガー gem 'pry-rails' end group :production do # Herokuの標準DBはPostgresql gem 'pg' end