Sequel4.1.0とmysql2でstream
Sequel4.1.0からmysql2クライアントのstreaming機能を使えるようになったらしい。
http://sequel.rubyforge.org/rdoc/files/doc/release_notes/4_1_0_txt.html
テーブルからでっかい行数を取得しようとするときに、
一気に取ってきてメモリに展開するのではなく
ちょっとずつ取ってきて、メモリを節約できる。
例えば1行ずつループさせて、必要なら途中で打ち切ったりもできるみたい。
require "sequel" DB = Sequel.connect("mysql2://user:password@hostname/database") class User < Sequel::Model(DB[:users]) end User.where({ :gender => "male" }).each do |user| # 性別が男のレコードをすべてメモリ上に展開してからループ end User.where({ :gender => "male" }).stream.each do |user| # たとえ意図せず巨大なレコード取得になったとしても # オンデマンドにデータを取得するので、 # 上の場合よりメモリ消費が少ない end
200万レコードくらいで試したら、
streamつけた方がeachがまわりきるのが1秒くらい早かったし(約5秒と約6秒)
メモリも少なかった。(約17MBと約27MB)
デフォルト引数を入れる変数を2個
デフォルト引数を入れる変数を2個
作ることもできると知ったのでメモ。
def hoge(foo = bar = 123) puts "foo: #{foo}" puts "bar: #{bar}" end hoge(234) #=> foo: 234 #=> bar: hoge #=> foo: 123 #=> bar: 123
引数を渡してhogeをcallしたときに、barの扱いが
NameError: undefined local variable or method `bar' for main:Object
になるかと思ったけど、
bar = 1 if false
のような、宣言だけはされていて、nilが入っているみたいな感じになるみたい。
こんなんどこで使うねんと思ったけどSinatraで使われていてこの存在を知った。
elsifに条件式がない
今日面白いコードを見た。
「elsif」に条件文がなくって、「あれ? これバグじゃね?」と思ったけど結果的にちゃんと動いていた。
a = "Hello, world!" if /hoge/ === a a.downcase! elsif a.upcase! end p a #=> "HELLO, WORLD!"
こんな感じ。
たぶん「elsif」のところは「else」って書こうとしたんだと思う。
でも「elsif」の右側に式がないから、
コンテキストに沿って次の行の式が条件判定に使われて、
最初の if が false の場合は、「a.upcase!」が絶対に評価されることになる。
結果的に「else」と同じ意味になるというわけだった。
a = "Hello, world!" if /hoge/ === a a.downcase! else a.upcase! end p a #=> "HELLO, WORLD!"
こうなる予定だったのだろう。
これって文法的に間違いではない分、扱いに困る。
いや、直すべきなんだろうけれど。
残念だったのは、代入式だったら警告が出て気がついてくれただろうになーというところ。
if a = "hoge" #=> warning: found = in conditional, should be ==
ハッシュのシンボル
1.9からハッシュの生成に簡易な書き方ができるようになった。
{key:"value"} # => {:key=>"value"}
JSっぽい。
ただ言語仕様上、値がシンボルの時は空けざるを得ないので、空ける。
「::」になるとクラスの空間セパレータになってしまうから。
{ key: :value } # => {:key=>:value}
こうなるんだけど、これを見ると一瞬「ん?」となってしまう。
スペースを挟んでのコロンが「なんかsyntax間違ってんじゃねーの?」っぽく見える。
まとめ
コーディング規約としては、最初からキーと値を空けるようにしておくのが無難かな。
ちなみに
もしかすると{}の中なら「::」はキーと値の両方シンボルと見てくれるかと思ってやってみたけど、ならなかった。
2.0.0p247 :001 > {a::b} SyntaxError: (irb):1: syntax error, unexpected '}', expecting => from /Users/rightgo09/.rvm/rubies/ruby-2.0.0-p247/bin/irb:13:in `<main>'
あと
{ :key => "value" }
が一番見やすいと思う。
自分の中で、ハッシュって感じがする。
「=>」を使ってPerlっぽいからかもしれない。
GithubのRuby Styleguideもこれみたいだし。
raiseで投げる例外クラス
raiseで投げる例外クラスは、Exceptionクラスを継承しなければならないみたい。
class Hoge end raise Hoge #=> TypeError: exception class/object expected
短いけどわかりにくいコードを可読性考えて書きなおしてみた
なんかこういう感じのコードがあったので、
def readable? !Hoge.count(self) == 0 && self.title != "" end
こういう感じに書き換えた。
def readable? return false if Hoge.count(self) == 0 return false if self.title == "" true end
3行に増えたけど、否定演算子を使ってないから、読みやすくなったのではないかと思う。
Sinatraでrake routes
SinatraでRailsのroutesタスクを作ってみた。
Sinatraのバージョンは 1.4.3 で確認。
見た目重視。
トップレベルにルーティングを定義している場合
desc 'something like rake routes of Rails' task 'routes' do require "./app" Sinatra::Application.class_eval do routes = self.instance_variable_get(:@routes) routes.each do |verb, signatures| next if verb == "HEAD" signatures.each do |signature| path = signature[0].to_s path.sub!(/^\(\?-mix:(\\A)?\\/, '') path.sub!(/(\\z)?\)$/, '') puts "%-6s %s" % [verb, path] end end end end
getを定義するとheadも同時に定義するようになっていたので、そこは省いた。
出力はこんな感じ。
- app.rb
require 'sinatra' get '/' do end get '/hello' do end post '/hello' do end put '/hello' do end delete '/hello' do end get '/hello/:name' do "Hello #{params[:name]}!" end get '/say/*/to/*' do params[:splat] end get %r{/hello/([\w]+)} do "Hello, #{params[:captures].first}!" end
$ bundle exec rake routes GET / GET /hello GET /hello\/([^\/?#]+) GET /say\/(.*?)\/to\/(.*?) GET /hello\/([\w]+) POST /hello PUT /hello DELETE /hello
Sinatra::Baseを継承している場合
本格的なアプリ作るときはこちらの方が多いとおもう。
desc 'something like rake routes of Rails' task 'routes' do require "./app" #Sinatra::Application.class_eval do Myapp.class_eval do routes = self.instance_variable_get(:@routes) routes.each do |verb, signatures| next if verb == "HEAD" signatures.each do |signature| path = signature[0].to_s path.sub!(/^\(\?-mix:(\\A)?\\/, '') path.sub!(/(\\z)?\)$/, '') puts "%-6s %s" % [verb, path] end end end end
- app.rb
require 'sinatra/base' class Myapp < Sinatra::Base get '/' do end get '/hello' do end post '/hello' do end put '/hello' do end delete '/hello' do end get '/hello/:name' do "Hello #{params[:name]}!" end get '/say/*/to/*' do params[:splat] end get %r{/hello/([\w]+)} do "Hello, #{params[:captures].first}!" end end
$ bundle exec rake routes GET / GET /hello GET /hello\/([^\/?#]+) GET /say\/(.*?)\/to\/(.*?) GET /hello\/([\w]+) POST /hello PUT /hello DELETE /hello
ただでさえルーティングとコントローラの処理を一緒に書いてごちゃごちゃしがちなので
ルーティングだけでも綺麗に見たかった。
Railsのそれと違って、書いた順に並べられなかったのがちょっと残念。