Padrino入門した
Sinatraのapp.rbがどんどん大きくなって仕方がなくなってきた。
が、Railsまでいかないので、よくその中間と言われるPadrinoの門を叩いてみた。
Padrinoについてぼくが知っていることといえばビュッフェスタイルということだけである。
$ ruby -v ruby 2.0.0p247 (2013-06-27 revision 41674) [x86_64-darwin12.3.0] $ padrino -v Padrino v. 0.11.3
アプリ名「Daisy」
$ padrino generate project Daisy create create .gitignore create config.ru create config/apps.rb create config/boot.rb create public/favicon.ico create public/images create public/javascripts create public/stylesheets create tmp create .components create app create app/app.rb create app/controllers create app/helpers create app/views create app/views/layouts create Gemfile create Rakefile skipping orm component... skipping test component... skipping mock component... skipping script component... applying slim (renderer)... apply renderers/slim insert Gemfile skipping stylesheet component... identical .components force .components force .components ================================================================= Daisy is ready for development! ================================================================= $ cd ./Daisy $ bundle =================================================================
$ cd ./Daisy/ $ bundle install --path vendor/bundle
Railsで2個めのDBを作ってマイグレーションする
Ruby on Rails使ってて、どうしても2個めのDBを作る必要があったのでメモ。
Railsのバージョンは4.0.0。
アプリ名は「Sakura」。
- (1個目マイグレファイル)db/migrate/*
に加えて
- (2個目DB名)sakura_second
- (2個目マイグレファイル)db/second_migrate/*
としたい。
config/database.ymlに2個目の情報をかく
--- config/database.yml.org 2013-09-04 21:48:05.000000000 +0900 +++ config/database.yml 2013-09-04 21:48:54.000000000 +0900 @@ -37,3 +37,13 @@ username: root password: socket: /tmp/mysql.sock + +# The second DB +sakura2: + adapter: mysql2 + encoding: utf8 + database: sakura_second + pool: 5 + username: root + password: + socket: /tmp/mysql.sock
テストとか開発用とか必要なら追加する。
config/environments/sakura2.rbを作る
config/database.ymlに"sakura2"を追加したので、RAILS_ENVの候補となるように、
config/environments/sakura2.rbを新規作成する。
その中でmigrationファイルを置くpathを変更する。
config/environments/sakura2.rb
Sakura::Application.configure do config.paths['db/migrate'] = ['db/second_migrate'] end
db/second_migrate/にマイグレファイルを作る
db/second_migrate/20130904121339_create_inveders.rb
class CreateInveders < ActiveRecord::Migration def change create_table :inveders do |t| t.string :name t.integer :age end end end
2個目のDBをrakeで作る
$ bundle exec rake db:create RAILS_ENV=sakura2
privateメソッドはrespond_to?でfalse
タイトルどおり。一瞬詰まったのでメモ。privateなんだからオブジェクトは直接respondできません!
class Foo def bar "bar" end private def baz "baz" end end foo = Foo.new foo.respond_to?(:bar) #=> true foo.respond_to?(:baz) #=> false
Rack::MethodOverrideを少しいじった話
よくある背景
よくある理由でIE対応を迫られて、ありがちな理由でPOSTなのにURLにパラメータがくっついてくるという
ありふれた問題にぶつかったまでは良かったのだが(悪かったのだが)
RESTfulなAPIに対応するという点で少しはまったのでメモ。
試したこと
最初はよくある解決策として、PUTで送りたいときに
POST http://hostname/api/member/1?_method=put
と、POSTと_methodによるディスパッチを試みた。
とりあえすSinatraを使っていたので、公式サイトを見て
require "sinatra" configure do enable :method_override end put "/" do "koreha put desu." end post "/" do "post desu. shippai desu." end
と設定し、わくわくしながら試したけど、POST扱いになってしまうようだった。
$ curl -i -X POST http://localhost:4567/?_method=PUT post desu. shippai desu.
調べた
実際に実装を覗いてみると、中でRack::MethodOverrideが使われていて、
#rack-1.5.2/lib/rack/methodoverride.rb method = req.POST[METHOD_OVERRIDE_PARAM_KEY] || env[HTTP_METHOD_OVERRIDE_HEADER]
としてパラメータを取り出していた。
reqというのはRack::Requestオブジェクトだから、
#rack-1.5.2/lib/rack/request.rb # Returns the data received in the request body. # # This method support both application/x-www-form-urlencoded and # multipart/form-data. def POST
というわけで、URLにくっついているクエリパラメータからは判断できないようになっていた。
解決策を考える
API内の一部の問題だったら、もう少し対応を考えたけど、
API全部が全部こんな感じだったので、素直に(?)Rack::MethodOverrideの方を再オープンして書き換えた。
require "sinatra" configure do class Rack::MethodOverride def method_override(env) # req = Request.new(env) req = Rack::Request.new(env) # method = req.POST[METHOD_OVERRIDE_PARAM_KEY] || method = req.params[METHOD_OVERRIDE_PARAM_KEY] || env[HTTP_METHOD_OVERRIDE_HEADER] method.to_s.upcase end end enable :method_override end put "/" do "koreha put desu" end post "/" do "post desu. shippai desu." end
$ curl -X POST http://localhost:4567/?_method=PUT koreha put desu
動いた。
ちなみに
あと、Rack::Requestクラスを見て気がついたけど
paramsって
@params ||= self.GET.merge(self.POST)
ってURLのクエリパラメータとrequest bodyのパラメータが合体されているようである。
(今回はそれに置き換えた)
SinatraでJSONを返す
Sinatraと小さいAPIはとても相性がいい。
APIで返すものといえばJSONが最近は楽ですね。
まあ普通にrequire "json"して、#to_jsonすればよいのですが、
sinatra-contribにあるsinatra/jsonを使うのもよいかと思います。
Sinatraだけの場合
app.rb
require "sinatra" require "json" get '/' do content_type :json data = { foo: "bar" } data.to_json end
Sinatra::JSONを使う場合
Gemfile
gem "sinatra" gem "sinatra-contrib"
app.rb
require "sinatra" require "sinatra/json" get '/' do data = { foo: "bar" } json data end
こんな感じ。
json_encoderオプション
json_encoderというオプションをjsonメソッドで渡すか、set :json_encoderで設定すればそのメソッドで最後仕上げてくれる。
# setで全体的な設定をする set :json_encoder, :foooooooo get '/' do data = { foo: "bar" } def data.foooooooo '{"hoge":"fuga"}' end # あるいは実行時に渡す(優先度はこっちが上) json data, :json_encoder => :foooooooo end
json_content_typeオプション
これもjson_encoderと同じ形。APIで使うならデフォルトのまま
Content-Type: application/json;charset=utf-8
か、JSで使うように
# setで全体的な設定をする set :json_content_type, :js get '/' do data = { foo: "bar" } # あるいは実行時に渡す(優先度はこっちが上) json data, :content_type => :js #=> Content-Type: application/javascript;charset=utf-8 end
くらいがJSONP的な使いどころとして思いつくくらいでしょうか。
ドキュメントとちょっと違ってた
1
ドキュメントにはto_jsonのcallを試みると書いてあるけど、
特にそんなそぶりはない。
get '/' do data = { foo: "bar" } def data.to_json '{"hoge":"fuga"}' end json data #=> {"foo":"bar"} end
実装も、デフォルトでは、MultiJSON#encodeが呼ばれて終わりだった。
小さいけど
content_typeのオーバーライドで
get '/' do json({:foo => 'bar'}, :encoder => :to_json, :content_type => :js) end
とあったけど:encoderは:json_encoderの誤りだと思う。
ちょっと調べてからぷるりくしてみたい。
Sinatraとsinatra-contribのStreamingの違い
Sinatra標準のStreamingと、公式拡張sinatra-contribのStreamingの違いについてメモ。
Sinatra標準Streaming
使い方
# coding: utf-8 require "sinatra" get '/' do stream do |out| out << "やあ<br>" sleep 1 out << "こんにちは、<br>" sleep 1 error_message = "お前はここで終わりだがな!" out.callback { p error_message } #=> closeされたときに実行される out << "世界" end end
WEBrickだと対応してないので、thinで。
$ ruby classic_streaming.rb -s thin
まあデフォルトだと「<<」と「callback」しかできることはない。
他にはstreamに渡すときに第1引数をtrueにすると、そのコネクションが開きっぱなしになるらしい。
以下、超簡単なpub/sub実装。
require "sinatra" connections = [] # subscribe側 get '/' do # trueならなんでもいい、そう、シンボルでもね。 stream(:keep_open) do |out| connections << out end end # publish側 get '/send' do connections.each do |out| out << params[:message]+"<br>" end "send message: #{params[:message]}" end
いいね!
おわり。
sinatra-contribのStreaming
これをもう少し拡張したのがsinatra-contribのStreamingで別途インストールが必要になる。
# Gemfile gem "sinatra-contrib"
とはいえ、唯一の特徴がIOインスタンスっぽく扱える、というだけのようだ。
classicスタイルでの使い方
require "sinatra" require "sinatra/streaming" get "/" do stream do |out| out.write "妖怪が鍛えた<br>" sleep 1 out.syswrite "この楼観剣に<br>" sleep 1 out.write_nonblock "斬れぬものなど<br>" sleep 1 out.puts "あんまり無い" out.putc(33) out.putc(33) out.printf("<%s>", "br") sleep 1 out.print "#{out.pos} bytes" end end
なんか公式の例で
out.flush
とできるようになっているけど、中身の実装はなかった。。。(華麗にスルー)
まあ特別IOっぽくしたいっていうことでも無い限り、標準の方で事足りると思う。
ちなみ
また、http://blog.uu59.org/2011-10-03-sinatra-stream.htmlの通り
ちょいちょい送信することを示す「Transfer-Encoding: chunked」がない(わざと実装していない?)みたいなので
あんまりきっちりしたことには使えないとも思う。
$ curl -i localhost:4567/ HTTP/1.1 200 OK Content-Type: text/html;charset=utf-8 X-XSS-Protection: 1; mode=block X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN Connection: close Server: thin 1.5.1 codename Straight Razor
参考
http://blog.uu59.org/2011-10-03-sinatra-stream.html
ありがとうございます。
なぜRackのResponse BodyはStringではなくArrayなのか
run lambda { |env| [ 200, {"Content-Type" => "text/plain"}, ["Hello World!"] ] }
この3つ目のbody。
PSGIでもそうだけど、なんでStringでも良さそうなのにArrayなのか。
ふと気になってググったら全く同じ内容がstachoverflowにあった。
ruby - Why is rack response body an array not a string? - Stack Overflow
元になってるWSGIから来てる理由と同じく、streamingに対応するためのようだ。
でっかいファイルを一気にメモリに読み込みたくないとか。
Stringだとどうしても1個だけという制限があるので、
イテレーションできるようにArrayになってるってことかな。
つまり
Rackの場合、3つ目のbodyは#eachを呼べて中身がStringならいいらしい。
ゆえに、
class Body def self.each yield "Hello World" end end run lambda { |env| [ 200, {"Content-Type" => "text/plain"}, Body ] }
こういうのでも動く。
Sinatra Streaming Responses
Sinatra: README
Sinatraでちょっとずつclientに送るサンプルを見る。
get '/' do stream do |out| out << "It's gonna be legen -\n" sleep 0.5 out << " (wait for it) \n" sleep 1 out << "- dary!\n" end end
実装としては3つ目のbodyのところにSinatra::Helpers::Streamオブジェクトがくる。
バージョンは1.4.3
sinatra/base.rb
class Stream def each(&front) @front = front @scheduler.defer do begin @back.call(self) rescue Exception => e @scheduler.schedule { raise e } end close unless @keep_open end end def <<(data) @scheduler.schedule { @front.call(data.to_s) } self end end
@schedulerはenv["async.callback"]に何か入ってたらEMが使われるようになってた。
入ってなかったらダミーが動く。
WEBrickで動かなくてthinで動くのはたぶんその関係。
なるほどなー。