みんなのちからになりたい

コピペでブログラムつくっていきたい

sinatraを使う

そろそろwebアプリケーションを作ってみたいのでやってみる。
rubyでやるとなるとrailsがいちばん有名みたいなんだけど、数年前rubyに最初にトライしたとき参考書にrailsを使うとdbを使ったサイトが簡単に出来ますって良く書いてあって、db使わないサイト作るのは出来るけどめんどいですって書いてあったのであきらめた経緯がある。
いまはどうか知らないけどそのトラウマからrailsは敷居高い感じがするので、その次に有名っぽいsinatraでやってみる。

一応ここらへんを参考にひととおりざっくりとだけ読んでやってみる。
ちなみにだれも教えてくれるひとがいない環境でやってるので、どれが最新でどういう組み方がトレンドでスタンダードかは己のググり力に頼るしかないので、あわよくばすべてが間違っている可能性もあるけどとりあえずいまは気にしない。

ざっくり読んだ限りだとこんな感じで作るのがいいっぽい。

require 'sinatra/base'

class MyApp < Sinatra::Base
  set :sessions, true
  set :foo, 'bar'

  get '/' do
    'Hello world!'
  end
end

config.ruはこんな感じにした。

app_dir = File.expand_path(File.dirname(__FILE__))
require app_dir + '/app'

MyApp.run! :host => 'localhost', :port => 9090

実行してみる。

$ bundle exec rackup
[2013-04-01 20:10:42] INFO  WEBrick 1.3.1
[2013-04-01 20:10:42] INFO  ruby 1.9.3 (2013-02-06) [x86_64-darwin12.2.0]
== Sinatra/1.4.2 has taken the stage on 9090 for development with backup from WEBrick
[2013-04-01 20:10:42] INFO  WEBrick::HTTPServer#start: pid=1570 port=9090
localhost - - [01/Apr/2013:20:11:02 JST] "GET / HTTP/1.1" 200 12
- -> /
localhost - - [01/Apr/2013:20:11:02 JST] "GET /favicon.ico HTTP/1.1" 404 482
- -> /favicon.ico

動いた。

Sinatra::Baseのところをみてみると

Sinatra::Baseのサブクラスで使えるメソッドはトップレベルのDSLを経由して確実に使うことができます。 ほとんどのトップレベルで記述されたアプリは、以下の2点を修正することでSinatra::Baseコンポーネントに変えることができます。

sinatraの代わりにsinatra/baseを読み込む
(そうしない場合、SinatraのDSLメソッドの全てがメインネームスペースにインポートされます)

ルート、エラーハンドラー、フィルター、オプションをSinatra::Baseのサブクラスに書く
Sinatra::Base はまっさらです。ビルトインサーバを含む、ほとんどのオプションがデフォルト で無効になっています。オプション詳細についてはOptions and Configuration をご覧下さい。

補足: SinatraのトップレベルDSLはシンプルな委譲(delgation)システムで実装されています。 Sinatra::Applicationクラス(Sinatra::Baseの特別なサブクラス)は、トップレベルに送られる :get、 :put、 :post、:delete、 :before、:error、:not_found、 :configure、:set messagesのこれら 全てを受け取ります。 詳細を閲覧されたい方はこちら(英語): Sinatra::Delegator mixin included into the main namespace.

Options and Configuration -> http://www.sinatrarb.com/configuration.html

たしかにソースみてみるとほとんどfalseになってる。

sinatra/lib/sinatra/base.rb at master · sinatra/sinatra · GitHub

気になったのが、Configuring SettingsのUsing set, enable, and disableに

set :foo, 'bar'

get '/foo' do
  "foo is set to " + settings.foo
end

って書いてあって、set呼ぶだけでなんでsettingsにfooのメソッドが生えるのかわからなくてソースみたらsetの最後でdefine_singleton呼んでclass_evalってやつでメソッド生やしてるみたいだった。
ソースの難易度高すぎてあんまり理解できないけどそういうやりかたもあるんだってすごい勉強になった。

settings
https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L896

set
sinatra/lib/sinatra/base.rb at master · sinatra/sinatra · GitHub

define_singleton
sinatra/lib/sinatra/base.rb at master · sinatra/sinatra · GitHub

やってみる。

class MyApp
  def self.add_method(name, logic)
    (class << self; self; end).class_eval do
      class_eval("def #{name}() #{logic}; end");
    end
  end
end

MyApp.add_method "umaibou", "puts 'umaibou'"
MyApp.umaibou

$ ruby test.rb
umaibou


できた。すごい。
でもclass << self; self; endってわざわざ書くのいまいち意味がわからない。
selfで普通にやってだめなんだろうか。
やってみる。

class MyApp
  def self.add_method(name, logic)
    self.class_eval do
      class_eval("def self.#{name}() #{logic}; end");
    end
  end
end

MyApp.add_method "umaibou", "puts 'umaibou'"
MyApp.umaibou

$ ruby test.rb
umaibou

普通にできた。
ただこっちはclass_evalの中にselfを書かなきゃならない。
どっちも同じことやってると思うんだけどなんか意味があってやってるのかなあ。
ソースの難易度が高すぎてよくわからない。

まだソース読んでるけど難易度高すぎてよく理解できない。
とりあえずいま意味わからないのが set :logging, true にしたときなんでloggerが有効になるのか。

ざっくりソースを検索すると

  • setup_default_middlewareが呼ばれないとloggerが設定されないっぽい
  • setup_default_middlewareはbuildから呼ばれる
  • buildはnewから呼ばれる
  • じゃあnewしてんのっていうとrun!を呼んでるからしてないと思う...
  • newしてるとしたらprototypeの中
  • prototype呼んでるのはcall
  • callの中でprototype.callしてる
  • callってどこで呼んでるんだよ...

というところで闇にはまってずっと悩んでたんだけど、ふとnewのコメント読むと

# Create a new instance of the class fronted by its middleware
# pipeline. The object is guaranteed to respond to #call but may not be
# an instance of the class new was called on.

ざっくり理解するとclassのnewじゃなくcallのとき動くんすよって書いてある。
callってどこで呼ぶんだよ...ってまた思ったけど、そういえばかなりまえにドラクエでrackを覚えるとかなんとかそんなのをみた気がすると思ってぐぐってみたらあった!

エラーメッセージから学ぶRack - 最初の一歩

1.rackupコマンドはWebサーバを起動する。
2.その際、config.ruという設定ファイル(Rubyスクリプト)を読み込む。
3.config.ruでは、Webアプリのインスタンスをrunメソッドに渡す。
4.Webアプリのインスタンスは、1引数のcallメソッドを持っている必要がある。
5.callメソッドは、3つの返り値、すなわち(1)100以上の数字からなるステータスコード、(2)少なくとも"Content-Type"をキーに、文字列を値に持つハッシュによるヘッダー、および(3)eachできるボディを返す。

なるほど、rackで動かすにはrunとcallがいるのか...
たまたまrunって名前でメソッドを定義して呼んでるからだってしか思ってなかったし、config.ruもそういうファイルがないと動かないって思ってただけだけど、ちゃんと理由があったのか...

すごい勉強になる。

ということでなんとなくひととおり理解できたので次から使っていくことにする。