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もこれみたいだし。

短いけどわかりにくいコードを可読性考えて書きなおしてみた

なんかこういう感じのコードがあったので、

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

SinatraRailsの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のそれと違って、書いた順に並べられなかったのがちょっと残念。