Sequelのカラム名にはシンボルを使うこと
http://sequel.rubyforge.org/rdoc/files/README_rdoc.html
Column references in Sequel
Sequel expects column names to be specified using symbols.
はまったのでメモ。
条件に沿って"field1", "field2"のようなフィールドにアクセスするときに
間違ってもStringでSequelのwhereとかselectとかに渡してはいけない。
そのまま「文字列」として処理されてしまう。
mysql> select * from foo; +------+------------+-----------------+ | id | field1 | field2 | +------+------------+-----------------+ | 1 | fooooo!!!! | FOOOOOOO!??!?!? | +------+------------+-----------------+ 1 row in set (0.00 sec)
$ sequel mysql2://user:password@localhost/hoge 2.0.0-p247 :001 > puts DB[:foo].select("field1").sql SELECT 'field1' FROM `foo` => nil # ↑field1はバッククオートではなくシングルクオート、つまり文字列! 2.0.0-p247 :002 > puts DB[:foo].select("field1").all {:field1=>"field1"} => nil # ↑得られる結果も当然そのまま! 2.0.0-p247 :003 > puts DB[:foo].select(:field1).sql SELECT `field1` FROM `foo` => nil 2.0.0-p247 :004 > puts DB[:foo].select(:field1).all {:field1=>"fooooo!!!!"} => nil # ↑本当はこうしないといけなかった! 2.0.0-p247 :005 > num = 1 => 1 2.0.0-p247 :006 > puts DB[:foo].select(:"field#{num}").sql SELECT `field1` FROM `foo` => nil 2.0.0-p247 :007 > puts DB[:foo].select(:"field#{num}").all {:field1=>"fooooo!!!!"} => nil # ↑動的に作る場合ももちろんシンボルにすること!
Oj.dumpでシンボルを吐く時は:mode => compatする
Ojを知ったきっかけ
Padrinoでスケルトンを作るとGemfileにojというgemが入っている。
Optimized JSONの略らしい。
標準ライブラリとなったjsonよりも早いらしい。
自分も、開発しているサービスで使われているJSONデータを使ってやってみたら、
確かにOjの方が早かった。
Ojのdump mode
で、改めて触ってたら、デフォルトでOj.dumpでJSONを吐くと、
シンボルはすべて":hoge"のようになることが分かったのでメモ。
もしそれが意図しておらず、JavaScriptでコロンは不要なら、
:mode => :compat
が必要である。
Oj.dump({ :hoge => :fuga }) #=> {":hoge":":fuga"} Oj.dump({ :hoge => :fuga }, :mode => :compat) #=> {"hoge":"fuga"}
他のmodeは
- :strict
- :null
- :strict modeでExceptionが投げられるデータがnullになって、例外が投げられなくなる
- :object
- オブジェクトを渡すと、オブジェクトのインスタンス変数をdumpしてくれる
- これがパフォーマンス的に最高のモードらしい
- :compat
- compatはcompatibilityの略
- 渡されたObjectがto_hashかto_jsonを呼べればそれを使う。なければOjがよしなにしてくれる
また、default_optionsを最初に設定しておくこともできる。
Oj.default_options = {:mode => :compat}
thinでsleepして止まった話
先日、スマートフォンアプリの実装の開発を助けるために、以下のようなAPIをつくった。
# app.rb require "sinatra" # ロックファイルを作る get "/lock" do FileUtils.touch("/tmp/lock") "ok" end # ロックファイルが存在する限りループ(1秒間隔でチェック、最大30秒) get "/wait" do cnt = 0 while File.exist?("/tmp/lock") # 1秒待つ sleep 1 cnt += 1 # 30秒を超えたら if cnt >= 30 halt 400 end end "ok" end # ロックファイル削除 get "/unlock" do FileUtils.rm("/tmp/lock") "ok" end
# config.ru require "./app" run Sinatra::Application
で、これをthinで起動した。
$ bundle exec thin start
クライアントとしては、/lockにアクセスしたあと、/waitでアクセスを維持しつつ、/unlockでロックファイルを削除して、/waitの返却値を期待する、みたいな感じ。(あくまで実験的な実装)
で、実際にやってみたら、/lock→/waitで待っている間に/unlockにアクセスしてもなんの返答もない。
しばらく待って、/waitの30秒が経つのと同時に/unlockのレスポンスが返ってきた。
そりゃそうか。
プロセスは1つしかないので、出口が詰まるとみんな詰まる。
async_sinatra
ググったら、GitHub - raggi/async_sinatra: A plugin for Sinatra to provide a DSL extension for using Thin for asynchronous responsesというgemをみつけた。
Thin, Rainbows, Zbateryを使っている場合に有効らしい。
使い方はいつものgetやpostと書くルーティングにaをつけて、agetやapostにするだけである。
# app.rb require "sinatra" require "sinatra/async" register Sinatra::Async aget "/wait" do # 時間がかかる処理 end
一応内部の実装とthinを見てみて、最低限の処理を抽出すると以下のような感じだった。
# app.rb require "sinatra" get "/wait" do EM.next_tick do cnt = 0 # 1秒間隔でチェック EM.add_periodic_timer(1) do cnt += 1 if cnt >= 30 request.env['async.callback'].call([200, {'Content-Type' => 'text/plain'}, ['ok']]) end end end throw :async end
最後に:asyncをぶん投げるのが肝のようだ。
あとは環境変数env['async.callback']にいつもの3つを渡せばよい。
reelというWebサーバを知った
GitHub - celluloid/reel: UNMAINTAINED: See celluloid/celluloid#779 - Celluloid::IO-powered web server
Celluloidという非同期の処理をするライブラリを使って作られているらしい。
Sinatraの1.4.4のChangesに入ってたので知った。
Rackと協調するには別途reel-rackというgemが必要。
# Gemfile source 'https://rubygems.org' gem 'sinatra' gem 'reel' gem 'reel-rack'
$ bundle exec reel-rack config.ru I, [2013-10-27T22:45:44.178102 #10300] INFO -- : A Reel good HTTP server! (Codename "Garbo") I, [2013-10-27T22:45:44.178174 #10300] INFO -- : Listening on http://0.0.0.0:3000
Celluloidベースというのが魅力なのか、webmachine-rubyと相性がいいのが良いのか、
いまひとつよくわからなかった。。。
daemonとか再起動とかもまだできないみたいである。
名前
セルロイド…合成樹脂の名前。アニメの「セル画」の「セル」らしい。
セルロイド - Wikipedia
リール…映写機の回るやつ。
Sinatraでrake routesその2
いま流行りっぽいYARDのSinatra用gem yard-sinatra | RubyGems.org | your community gem host を使うと
簡単にルーティングを出力できるみたい。
↓の"Other use cases"を見た。
GitHub - rkh/yard-sinatra: Display sinatra routes in yard documentation.
Gemfile
source 'http://rubygems.org' gem 'rake' gem 'sinatra' group :development do gem 'yard' gem 'yard-sinatra' end
Rakefile
task "routes" do require "yard/sinatra" YARD::Registry.load ["./app.rb"], true YARD::Sinatra.routes.each do |route| puts route.http_verb, route.http_path, route.file, route.docstring end end
YARD::Registry.loadは1回読み込むとキャッシュが使われて2回め以降動かなくなるので
trueをつけて毎回parseするようにした。
app.rb
サンプルシナトラ
require "sinatra" get "/" do "foooooooooo!" end get "/bar" do "baaaaaaaaaaaar!" end post "/bar" do "POST baaaaaaaaaaaar!" end put "/bar" do "PUT baaaaaaaaaaaar!" end get "/baz/:id" do "baz: #{params[:id]}" end
rake routes
$ bundle exec rake routes GET / app.rb GET /bar app.rb POST /bar app.rb PUT /bar app.rb GET /baz/:id app.rb
もっとRailsっぽく
Rakefileをこうしてみる。
task "routes" do require "yard/sinatra" YARD::Registry.load ["./app.rb"], true routes = [] YARD::Sinatra.routes.each do |route| routes << [route.http_verb, route.http_path, route.file] end max_verb_len = routes.map{|r|r[0].length}.max max_path_len = routes.map{|r|r[1].length}.max routes.each do |r| puts "%-#{max_verb_len}s %-#{max_path_len}s %s" % r end end
$ bundle exec rake routes GET / app.rb GET /bar app.rb POST /bar app.rb PUT /bar app.rb GET /baz/:id app.rb
いいね!
Sequel3.48でgraph使ったらdeprecated出たので対応した
通常のjoin
Sequelを使って、単純にAテーブルとBテーブルをjoinすると、同名のカラムが存在する場合、右側の方が有効になる。
mysql> select * from a; +----+------+-------------+ | id | z_id | name | +----+------+-------------+ | 1 | 123 | Yamada Taro | +----+------+-------------+ mysql> select * from b; +----+------+---------------+ | id | z_id | name | +----+------+---------------+ | 1 | 123 | Suzuki Hanako | +----+------+---------------+
require "sequel" DB = Sequel.connect("mysql2://user:password@localhost/foo") class A < Sequel::Model(DB[:a]) end class B < Sequel::Model(DB[:b]) end p A.join(B, :z_id => :z_id).sql #=> "SELECT * FROM `a` INNER JOIN `b` ON (`b`.`z_id` = `a`.`z_id`)" p A.join(B, :z_id => :z_id).first # Bの"Suzuki Hanako"が格納されてしまう #=> [#<A @values={:id=>1, :z_id=>123, :name=>"Suzuki Hanako"}>]
graphを使う
このときjoinの代わりにgraphメソッドを使うと、AもBも両方使えるようになる。
p A.graph(B, :z_id => :z_id).sql #=> "SELECT `a`.`id`, `a`.`z_id`, `a`.`name`, `b`.`id` AS `b_id`, `b`.`z_id` AS `b_z_id`, `b`.`name` AS `b_name` FROM `a` LEFT OUTER JOIN `b` ON (`b`.`z_id` = `a`.`z_id`)" p A.graph(B, :z_id => :z_id).first #=> {:a=>#<A @values={:id=>1, :z_id=>123, :name=>"Yamada Taro"}>, :b=>#<B @values={:id=>1, :z_id=>123, :name=>"Suzuki Hanako"}>}
が、同時にdeprecation warningも出てきた。
SEQUEL DEPRECATION WARNING: Dataset#graph_each is deprecated and will be removed in Sequel 4.0. Load the graph_each extension if you want to continue using it.
使っていたバージョンは3.48。3系の最後。
4系でも使うためには、いまからextensionに切り出された方を使うのがいいんだろう。
extensionでgraphを使う
DB.extension(:graph_each)
1行追加して終わり。
警告も出なくなった。