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

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
# 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__ == $0requireされたときにスクリプトが実行されるのを回避してる。
# 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)

The RSpec Book (Professional Ruby Series)

Rails開発環境 2012夏

5月に「Rails開発環境 2012初夏」という記事を公開してそこそこ好評だったので、最近導入してLife-Changingだったツールを「2012夏」バージョンとして紹介しようと思います。今回紹介するのは以下の3つです。

  • pow + xip.io
  • tmuxinator
  • ctrlp.vim

1. pow + xip.io

pow + xip.ioによって同じネットワーク内にある、iPhoneiPadのような他のデバイスからローカルサーバーに接続できるようになりました。これは、スマホ用サイトやアプリで使う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で他のデバイスからもアクセスできます。

f:id:naoty_k:20120808013751j:plain

注意点としては、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と近いのかもしれませんが、僕はこっちの方がサクサクしてて操作もわかりやすくて好きです。下はスクリーンショットです。

f:id:naoty_k:20120808095402j:plainf:id:naoty_k:20120808100021j:plain

.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をリリースしました

使い方

$ curl http://cui-about.me/naoty
name = naoty
blog = http://naoty.hatenablog.com
email = naoty.k@gmail.com
github = naoty
twitter = naoty_k
  • CUI版のabout meです。
  • curlコマンドでユーザーのプロフィール情報をダウンロードできます。
  • 詳しい使い方はこちらに載せました。

作った理由

  • Java疲れ
  • プログラマーがよく使うabout meみたいな定番サービスがなかったので作った。
  • manualhubというサービスを見つけて面白いと思ったものの使いにくかったので、似たようなものを自分なりに作ってみた。

使った技術要素

  • Rails 3.2.6
  • MongoDB
  • Heroku (addons: MongoHQ, Custom Domain)
  • お名前.com

Gemfileを公開するサイト「Gemfile Freaks」を公開しました

Gemfileを公開するサイト「Gemfile Freaks」をおととい公開しました。土曜にだいたい作って、日曜にユーザー認証をつけました。

f:id:naoty_k:20120611091354j:plain

前々からほしいと思っていたので、自分で作ることにしました。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