ラベル sinatra の投稿を表示しています。 すべての投稿を表示
ラベル sinatra の投稿を表示しています。 すべての投稿を表示

2012年5月27日日曜日

HTTPメソッドとの対応

sinatraに触れると、だいたい最初の段階でCRUD操作がルーティングのpost/get/put/delete do ... endに対応する、ということを学びます。
しかしながら、多くのブラウザにはgetおよびpostの実装しかないということで、putとdeleteはもうひと工夫必要になります。
この辺りは予備知識があれば容易に分かるところですが、そうでないとなかなか辿り着くのに苦労します。
解決策としては

  • POSTによる代用
  • Javascriptによるput/deleteメソッドの生成
があります。get '/foo/delete' do ... end などとしてgetによる削除、更新処理をしている例もありますが、RESTfulなURI設計とは言い難いのでおすすめしません。
POSTによる代用は、フォームの隠しパラメータに_methodパラメータを用意しそこにメソッド名を入れます。sinatraで利用する場合は、:method_overrideを有効にすることで適切なルーティングがおこなわれます。例えば、
@@ post_delete
form method="POST"  action="/destroy_it"
  input type="hidden" name="_method" value="DELETE"
  button type="submit"
    | delete
のようなフォーム(Slim使用)あって、ボタンを押すとPOSTリクエストがサーバには届きますが、delete "/xxxx" do ... end へルーティングされます。
二番目の方法は読んでそのままですが、jQueryを使用すると下記のような形でリクエストを生成します。
$.ajax({
  type: "DELETE",
  url: "destroy_it",
  success: function(data) {
    $(".message").text(data)
  }
});
以下、両者のサンプルになります。"$ ruby app.rb"で実行できます。

app.rb

require 'sinatra/base'
require 'slim'

class MyApp < Sinatra::Base

  configure :development do
    require 'sinatra/reloader'
    register Sinatra::Reloader
  end

  configure do
    enable :inline_templates
    enable :method_override
  end

  get '/style.css' do
    scss :style
  end

  get '/post_delete' do
    slim :post_delete
  end

  get '/delete_method' do
    slim :delete_method
  end

  delete '/destroy_it' do
    content_type :text
    "delete done!!"
  end

  run! if app_file == $0
end

__END__

@@ layout
doctype html
html
  head
    title My App
    script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"
    link rel="stylesheet" href="/style.css"
  body == yield

@@ post_delete
form method="POST"  action="/destroy_it"
  input type="hidden" name="_method" value="DELETE"
  button type="submit"
    | delete

@@ delete_method
span.delete__
  | delete
div.message

javascript:
  $(function(){
    $(".delete__").click(function(){
       $.ajax({
         type: "DELETE",
         url: "destroy_it",
         success: function(data) {
           $(".message").text(data)
         }
       });
    })
  })

@@ style
span.delete__ {
  cursor: pointer;
  background-color: #dddddd;
  border: #cccccc 2px outset;
  border-radius: 5px;
  padding: 1px 4px 2px 4px;
}

div.message {
  color: red;
  margin: 2px;
  padding: 2px;
}

2012年5月21日月曜日

sinatra (10) - 雑感 -

Sinatraの魅力は何と言っても自由度の高さです。
これまでいくつかのコンポーネントとの連携を書いてきましたが、これらを自由に足し引きすることができます。
sinatraの得意分野であるWebのAPIなどはビューまわりをほとんど使用せずできることもあります。
Sinatraの作者も述べている(訳)ように、sinatraのREADMEは非常によく書かれています。一部翻訳が追いついていないようですが、日本語でも十分な内容です。
書籍としては個人的にSinatra: Up and Runningをおすすめします。私自身あまり英語が得意ではありませんが、ページ数も122とくじけずに読める量でした。値段もEbookで$12.99と今なら1,000円強なので専門誌としては格安です。

なお、前回まで書いたソースはgithubにあります。

2012年5月20日日曜日

sinatra (9) - heroku -

(8)の続き。
今回はherokuに作成したアプリケーションをデプロイします。

公式ではHeroku Toolbeltを使用していますが、これだとOS標準のRubyパッケージもインストールされます。
すでにRubyはrvmでインストールしているので、ここでは従来のgemのherokuをインストールします。 こちらにもあるように、gemパッケージはこれまでどおり提供し続け、Heroku ToolbeltはPythonやJavaなど他言語を使用する方へ向けてのことです。
$ gem install heroku
SSH鍵を生成して登録します。
$ ssh-keygen -t rsa
$ heroku kyes:add
認証が通るか確認してみましょう。Authentication successful.と表示されればOKです。
$ heroku login
hreokuの起動に必要なファイルを編集していきます。
.
├── config
│     ├── mongoid.yml
│     └── unicorn.rb
├── config.ru
├── .gitignore  # 追加
├── Gemfile
├── Gemfile.lock
├── Guardfile
├── lib
│     ├── app.rb
│     └── models.rb
├── Procfile  # 追加
├── public
├── spec
│     ├── app_spec.rb
│     └── spec_helper.rb
└── views
       ├── index.slim
       ├── layout.slim
       └── style.scss
$ gem install foreman

Procfile

web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb
$ foreman start
まずはこれで正しく起動できて、ブラウザから期待する表示ができているか確認してみます。
gitリポジトリを生成します。
$ git init
$ heroku create --stack cedar
通常だと、このあとファイルを追加するのですがherokuのMongoHQを使用するためにもうひと手間かけます。
$ heroku addons:add mongohq:free
各所で書かれていますが、My AccountのBilling Infoを登録しておかないとフリー版であっても使用することができません。

Procfile

web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb -E production

mongoid.yml

development:
  host: localhost
  database: mydb
production:
  uri: <%= ENV['MONGOHQ_URL'] %>
ローカルでproduction環境を確認したいときは、環境変数にMONGOHQ_URL='mongodb://localhost/mydb'などと設定すればよいです。

.gitignore

coverage
.sass-cache
$ git add .
$ git commit -m "init"
$ git push heroku master
正しく起動するとデプロイ先のURLが表示されるので、ブラウザでアクセスしてみましょう。
この時点でMongoHQのドキュメントは空なので、HerokuのMy App - Resources - Add-onsからMongoHQに適当なデータを入れると、MongoDBの部分も動作を確認することができます。
テストでアップロードしたサーバをそのまま放置しておくのも何なので
$ heroku maintenance:on
などとして、使わないときはメンテナンスモードにしておくのも手でしょう。

2012年5月18日金曜日

sinatra (8) - コードカバレッジ -

(7)の続き。
テストを実施すると必要になってくるコードカバレッジ。
今回はRuby 1.9で使用できるSimpleCovを使用します。

Gemfile

source :rubygems

gem 'sinatra', require: 'sinatra/base'
gem 'slim'
gem 'sass'
gem 'unicorn'
gem 'mongoid'
gem 'bson_ext'

group :development do
  gem 'sinatra-reloader', require: 'sinatra/reloader'
end

group :test do
  gem 'rspec'
  gem 'rack-test', require: 'rack/test'
  gem 'guard-rspec'
  gem 'simplecov', require: false
end

spec/sepc_helper.rb

require 'bundler/setup'
Bundler.require :default, :test

require 'simplecov'
SimpleCov.start

require 'app'
require 'models'

RSpec.configure do |config|
  config.include Rack::Test::Methods
end
$ bundle install
$ bundle exec guard
これでテストするたびにカバレッジを計測することができます。なお、前回のソースだと
Coverage report generated for RSpec to /home/kan/workspace/template/coverage. 22 / 23 LOC (95.65%) covered.
と表示されて100%に達していません。
coverageディレクトリ以下に結果のhtmlが生成されているので確認してみると、app.rbのget '/css/:file.css'...のブロックがテストされていないことがわかります。
簡単なテストを追加してみます。

spec/app_spec.rb

require 'spec_helper'

shared_examples_for "response code" do
  it { should be_ok }
end

describe MyApp do

  def app
    @app ||= MyApp
  end

  context "when get '/'" do
    before do
      get '/'
    end
    subject { last_response }
    it_behaves_like "response code"
    its(:body) { should include("Hello World!") }
  end

  context "when get '/css/style.css'" do
    before do
      get '/css/style.css'
    end
    subject { last_response }
    it_behaves_like "response code"
  end

  describe "MongoDB access" do
    before do
      Game.destroy_all if Game.exists?
      Game.create(title: "GOD EATER BURST", platform: "PSP")
    end
    context "when get '/games/title'" do
      before { get 'games/title' }
      subject { last_response }
      its(:body) { should == "GOD EATER BURST" }
    end
    context "when get '/games/platform'" do
      before { get 'games/platform' }
      subject { last_response }
      its(:body) { should == "PSP" }
    end
  end
end
再度テストを実行すると、カバレッジが100%に達すると思います。

2012年5月17日木曜日

sinatra (7) - データベース -

(6)の続き。
データベースは、MySQL、SQLiteなど選択に困るほど存在しますが、ここでは ドキュメント指向データベースのMongoDBを使用します。
インストールは公式ページにしたがっておこないます。
$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 7F0CEB10

/etc/apt/sources.list

deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen
$ sudo update
$ sudo apt-get install mongodb-10gen
次にsinatraで扱えるようにします。
.
├── config
│     ├── mongoid.yml  # 追加
│     └── unicorn.rb
├── config.ru
├── Gemfile
├── Gemfile.lock
├── Guardfile
├── lib
│     ├── app.rb
│     └── models.rb  # 追加
├── public
├── spec
│     ├── app_spec.rb
│     └── spec_helper.rb
└── views
       ├── index.slim
       ├── layout.slim
       └── style.scss
source :rubygems

gem 'sinatra', require: 'sinatra/base'
gem 'slim'
gem 'sass'
gem 'unicorn'
gem 'mongoid'
gem 'bson_ext'

group :development do
  gem 'sinatra-reloader', require: 'sinatra/reloader'
end

group :test do
  gem 'rspec'
  gem 'rack-test', require: 'rack/test'
  gem 'guard-rspec'
end
$ bundle install

config/mongoid.yml

development:
  host: localhost
  database: mydb
動かすだけであれば、設定はこの程度でも動作します。
公式により詳細な設定が書かれています。

lib/models.rb

class Game
  include Mongoid::Document
  field :title, type: String
  field :platform, type: String
end

lib/app.rb

require 'models'

class MyApp < Sinatra::Base

  configure :development do
    Bundler.require :development
    register Sinatra::Reloader
    Slim::Engine.set_default_options pretty: true
    Mongoid.logger = Logger.new(STDOUT)
    Mongoid.logger.level = Logger::INFO
  end

  configure do
    set :root, File.expand_path('../../', __FILE__)
    Mongoid.load! File.join(settings.root, 'config', 'mongoid.yml')
  end

  get '/' do
    slim :index
  end

  get '/games/:field' do
    content_type :text
    game = Game.first
    game ? "#{game.__send__ params[:field]}" : "nil"
  end

  get '/css/:file.css' do
    scss params[:file].to_sym
  end
end
Mongoid.load! File.join(settings.root, 'config', 'mongoid.yml')
で設定を読み込みます。
このまま使用するとRSpecを使用した時もクエリの内容等、デバッグログが出力されてしまうので抑制します。
Mongoid.logger = Logger.new(STDOUT)
Mongoid.logger.level = Logger::INFO
用途に合わせて出力先や出力レベルを調整するとよいでしょう。
注意しなければならないのはMongoid.load!よりも前に定義しなくてはいけないことです。Mongo Ruby Driverが公開APIを提供していないため、masterの設定より先に記述しなくてはいけないようです。ソースを除くとrailsでは別途railsで指定したloggerを参照しているようです。
models.rbでサンプルとなるモデルを定義しています。このあたりsinatraは自由なのでapp.rbに定義してもよいし、モデルごとにファイルを分割しても問題ありません。使える型についても公式に記述があります。
ルートも増えたところで対応するテストケースも追加しておきましょう。

spec/spec_helper.rb

require 'bundler/setup'
Bundler.require :default, :test

require 'app'
require 'models'

RSpec.configure do |config|
  config.include Rack::Test::Methods
end

spec/app_spec.rb

require 'spec_helper'

describe MyApp do

  def app
    @app ||= MyApp
  end

  context "when get '/'" do
    before do
      get '/'
    end
    subject { last_response }
    its(:status) { should == 200 }
    its(:body) { should include("Hello World!") }
  end

  describe "MongoDB access" do
    before do
      Game.destroy_all if Game.exists?
      Game.create(title: "GOD EATER BURST", platform: "PSP")
    end
    context "when get '/games/title'" do
      before { get 'games/title' }
      subject { last_response }
      its(:body) { should == "GOD EATER BURST" }
    end
    context "when get '/games/platform'" do
      before { get 'games/platform' }
      subject { last_response }
      its(:body) { should == "PSP" }
    end
  end
end

2012年5月16日水曜日

sinatra (6) - アプリケーションサーバ -

(5)の続き。
これまでWEBrickで動かしましてきましたが、これをunicornに変更します。
他にもthin、passenger等々あるので、この辺は用途と好みで。
公式でもunicornはnginx等を手前に置いて使いましょう、とのことですが heroku上で単体で載せている例もあります。
.
├── config
│     └── unicorn.rb  # 追加
├── config.ru
├── Gemfile
├── Gemfile.lock
├── Guardfile
├── lib
│     └── app.rb
├── public
├── spec
│     ├── app_spec.rb
│     └── spec_helper.rb
└── views
       ├── index.slim
       ├── layout.slim
       └── style.scss

Gemfile

source :rubygems

gem 'sinatra', require: 'sinatra/base'
gem 'slim'
gem 'sass'
gem 'unicorn'

group :development do
  gem 'sinatra-reloader', require: 'sinatra/reloader'
end

group :test do
  gem 'rspec'
  gem 'rack-test', require: 'rack/test'
  gem 'guard-rspec'
end
$ bundel install
$ bundle exec unicorn
動作確認するだけなら、これだけでも動作します。
設定ファイルは公式でミニマム版と、フルセット版が掲載されています。 ここではごく簡単に記述します。

config/unicorn.rb

worker_processes 2
timeout 30
$ bundle exec unicorn -c ./config/unicorn.rb
デーモンで起動するときは-Dオプション付きで起動し、停止するときはkill -QUIT (pid)で停止することができます。

2012年5月15日火曜日

sinatra (5) - CSSフレームワーク -

(4)の続き。

SCSSはCSSとほぼ同じ文法で記述でき、なおかつ冗長かなと思えばSCSS記法を使え重宝します。
同様のフレームワークにLESSもありますので、好みに応じて使えばよいでしょう。
ググると両者の特徴や違いを説明しているサイトが見つかります。

.
├── config
├── config.ru
├── Gemfile
├── Gemfile.lock
├── Guardfile
├── lib
│     └── app.rb
├── public
├── spec
│     ├── app_spec.rb
│     └── spec_helper.rb
└── views
       ├── index.slim
       ├── layout.slim
       └── style.scss  # 追加

Gemfile

source :rubygems

gem 'sinatra', require: 'sinatra/base'
gem 'slim'
gem 'sass'

group :development do
  gem 'sinatra-reloader', require: 'sinatra/reloader'
end

group :test do
  gem 'rspec'
  gem 'rack-test', require: 'rack/test'
  gem 'guard-rspec'
end

views/style.scss

html {
  font-size: 62.5%;
}
body {
  font-size: 1.4rem;
}
h1 {
  font-size: 2.4rem;
}

views/layout.slim

doctype html
html
  head
    title My App
    link rel='stylesheet' href='css/style.css'
  body == yield

lib/app.rb

class MyApp < Sinatra::Base

  configure do
    set :root, File.expand_path('../../', __FILE__)
  end

  configure :development do
    Bundler.require :development
    register Sinatra::Reloader
    Slim::Engine.set_default_options :pretty => true
  end

  get '/' do
    slim :index
  end

  get '/css/:file.css' do
    scss params[:file].to_sym
  end
end
$ bundle install
$ bundle exec rackup

コマンドラインでscssファイルからスタイルシートに変換もできますが、こう記述しておけば実行時に変換してくれます。
また、スタイルシートを追加、分割してもlayout.slimに追加するだけで、app.rbはそのままで構いません。railsだと一括でスタイルシートを読むヘルパーが用意されていますが、同様にヘルパーを定義するなり別途用意してもよいと思います。

2012年5月14日月曜日

sinatra (4) - テンプレートエンジン -

(3)の続き。
テンプレートエンジンを追加します。ErbやHamlなど色々ありますが、今回はSlimを使用します。
.
├── config
├── config.ru
├── Gemfile
├── Gemfile.lock
├── Guardfile
├── lib
│     └── app.rb
├── public
├── spec
│     ├── app_spec.rb
│     └── spec_helper.rb
└── views
        └── index.slim  # 追加
        └── layout.slim  # 追加

Gemfile

source :rubygems

gem 'sinatra', require: 'sinatra/base'
gem 'slim'

group :development do
  gem 'sinatra-reloader', require: 'sinatra/reloader'
end

group :test do
  gem 'rspec'
  gem 'rack-test', require: 'rack/test'
  gem 'guard-rspec'
end

lib/app.rb

class MyApp < Sinatra::Base

  configure do
    set :root, File.expand_path('../../', __FILE__)
  end

  configure :development do
    Bundler.require :development
    register Sinatra::Reloader
  end

  get '/' do
    slim :index
  end
end

views/index.slim

h1 Hello World!
$ bundle install
$ bundle exec rackup
これで表示できますが、画面のソースを確認するとヘッダ等は記述されていません。
index.slimに直接追記してもよいですが、ここではlayout.slimとして分割します。

views/layout.slim

doctype html
html
  head
    title My App
  body == yield
展開されたhtmlのソースは圧縮されているので、インデント表示させたい時はオプションを変更しましょう。
  configure :development do
    # (略)
    Slim::Engine.set_default_options :pretty => true
  end

2012年5月13日日曜日

sinatra (3) - テスト -

(2)の続き。
テスト環境を整備します。
.
├── config
├── config.ru
├── Gemfile
├── Gemfile.lock
├── Guardfile  # 追加
├── lib
│     └── app.rb
├── public
├── spec
│     ├── app_spec.rb  # 追加
│     └── spec_helper.rb  # 追加
└── views

Gemfile

source :rubygems

gem 'sinatra', require: 'sinatra/base'

group :development do
  gem 'sinatra-reloader', require: 'sinatra/reloader'
end

group :test do
  gem 'rspec'
  gem 'rack-test', require: 'rack/test'
  gem 'guard-rspec'
end

spec/spec_helper.rb

require 'bundler/setup'
Bundler.require :default, :test

require 'app'

RSpec.configure do |config|
  config.include Rack::Test::Methods
end

spec/app_spec.rb

require 'spec_helper'

describe MyApp do

  def app
    @app ||= MyApp
  end

  context "when get '/'" do
    before do
      get '/'
    end
    subject { last_response }
    its(:status) { should == 200 }
    its(:body) { should include("Hello World!") }
  end
end
$ bundle install
$ bundle exec rspec
これで2つのテストが通ると思います。
加えて、テストもオートロードされるようGuardを使用します。

Guardfile

guard 'rspec', :version => 2, :cli => "-c -fs" do
  watch(%r{^spec/.+_spec\.rb$})
  watch(%r{^lib/(.+)\.rb$})     { |m| "spec/#{m[1]}_spec.rb" }
  watch('spec/spec_helper.rb')  { "spec" }
  watch(%r{^views/(.+)\.(slim|scss)$})
end
$ bundle exec guard
Xubuntu 12.04になってnotificationもまともに動くようになっているようです。 (11.10まで私の環境では不安定でした)

2012年5月12日土曜日

sinatra (2) - オートリロード -

(1)の続き。
アプリケーションを書きなおすたびにsinatraを立ち上げ直すのは面倒臭いのでオートリロードに対応させます。

Gemfile

source :rubygems

gem 'sinatra', require: 'sinatra/base'

group :development do
  gem 'sinatra-reloader', require: 'sinatra/reloader'
end

lib/app.rb

class MyApp < Sinatra::Base

  configure do
    set :root, File.expand_path('../../', __FILE__)
  end

  # http://www.sinatrarb.com/contrib/reloader
  configure :development do
    Bundler.require :development
    register Sinatra::Reloader
  end

  get '/' do
    'Hello World!'
  end
end
$ bundle install
$ bundle exec rackup
起動後に'Hello World!'を適当な文字に書き換え、ブラウザをリロードすれば表示が変更されると思います。

2012年5月11日金曜日

sinatra (1)

sinatraはちょっとしたアプリケーションや、少しずつ育てていきたい時に重宝します。
規模感が合致すればrailsを使えばよいですし、sinatraをよりrailsっぽく使うのであればPadrinoあたりを使えばよいと思います。
個人的にはPadrinoの初期生成でも煩わしさを感じたので、自分で生成しています。
Github等で様々な人がテンプレートを紹介しているので、参考にしてみるとよいでしょう。
ミニマムで以下のような構成です。config, public, spec, viewsは今のところ空のディレクトリです。
.
├── config
├── config.ru
├── Gemfile
├── Gemfile.lock
├── lib
│     └── app.rb
├── public
├── spec
└── views

Gemfile

source :rubygems

gem 'sinatra', require: 'sinatra/base'

config.ru

$:.unshift File.join(File.dirname(__FILE__), 'lib')
require 'bundler'
Bundler.require

require 'app'
run MyApp

lib/app.rb

class MyApp < Sinatra::Base

  # http://www.sinatrarb.com/configuration.html
  configure do
    set :root, File.expand_path('../../', __FILE__)
  end

  get '/' do
    'Hello World!'
  end
end
$ bundle install
$ bundle exec rackup
WEBRickが立ち上がるので、ブラウザ経由のhttp://localhost:9292で結果を確認できると思います。