mock によって返ってくる値を呼び出す度に変更する

↓こんな感じでやる。

a = []
a.should_receive(:size).ordered.and_return(1)
a.should_receive(:size).ordered.and_return(2)
a.should_receive(:size).ordered.and_return(3)

5.times do
  p a.size
end

↓結果。設定した回数以上に呼び出すと、最初の物が繰り返されるらしい。

1
2
2
1
1

ordered で、メソッドの呼び出された順を確認可能というのは知っていたけど、これは知らなかった。
当たり前といえば、当たり前の動きではあるのだけど。

Server program を Thread に頼らず作る

訳あって、Server program を Thread に頼らず、fork を使って作ってみることにした。

#!/usr/bin/env ruby

require "socket"
require 'time'

Signal.trap('CHLD') do
  puts "waiting"
  Process.waitall
end


print $$.to_s + ": parent pid\n"
accepter = TCPserver.open(12345)

loop do
  io = accepter.accept
  fork do
    io.write(Time.now.iso8601 + "\n")
    io.close
    print $$.to_s + ": child pid after fork\n"
    exit!(0)
  end

  io.close
  print $$.to_s + ": parent pid after fork\n"

end

ポイントは 2 つ。

  1. socket は子プロセス側だけではなく、親プロセス側も close すること。そうでないと、クライアント側からみて、切断がされない。
  2. シグナル SIGCHLD に Process.wait を実行する trap を仕掛けること。そうでないと、ゾンビプロセスを看取ることができない(=ゾンビが残る状態になる)

後者についてはこれまで良く理解していなかった。15年ぐらい前に C で作った Server program はたぶんこの問題の影響か、数日稼働すると停止するという困った動きをしてくれていた。

SUM/MAX/MIN/AVG を GROUP BY の条件で使う

下記のような感じで記述する。

report_collection = ProductReport.aggregate(
  
   # grouping key
   :product_id,
   :date,

   # summary target
   :amount.sum,
   :tax.sum,
                              
   # condition
  :date => (start_date .. end_date)
)

# => [
#     [1, '2009-12-14', 100, 10],
#     [1, '2009-12-15', 200, 20],
#     [1, '2009-12-16',  58, 20],
#     [2, '2009-12-14', 511, 20],
#     [2, '2009-12-15',  85, 10],
# ]

集計対象の項目には :symbol に .sum/avg/count/max/min などのメソッドがあるので、それを呼び出した結果を指定する。これは、 :order とか不等号や NOT の指定と大体同じ。

一番よく理解できなかったのは、 grouping key の指定。最初は :group_by とかの特殊キーを指定するのかと思っていたが、これは上記の :product_id, :date のよう対象の項目を羅列するだけ。良く考えてみれば SQL を書くときも、 SELECT 句に書く項目と GROUP BY 句に書く項目って同じになることがほとんどなので、これは賢い指定方法だとおもった。

ちなみに、 GROUP BY の項目を明確に指定したい場合は、 :unique => true, :fields => [:product_id, :date] とか書くみたいだけど、あまり詳細に確認していない。


蛇足だけど、 :date の条件指定は上記のように Range オブジェクトを使うとわかりやすくていい。

Integer から Enum への変換表を取得する

こんな感じで OK だった。

class TargetModel
  include DataMapper::Resource

  property :status, Enum[:born, :live, :dead]
end

TargetModel.properties[:status].type.flag_map
    # => {1 => :born, 2 => :live, 3 => :dead}

蛇足ながら、Symbol から数値への変換表は、上記の結果に #invert を適用すると取得できる。

論理削除(ParanoidDateTime)がされているデータを参照する

Model の with_deleted メソッドの block 内で参照を行う。

target_model =
  TargetModel.with_deleted { TargetModel.get(2) }

複数の Model をまとめて、 with_deleted なモードで扱いたいんだけど、そういうのはないみたい。