細かい点ですが、メールアドレスの登録をアドレスブックから行えるようにしました。

絵文字キーボードの利用が出来なくなる可能性があるようなので("Apple issues App Store-wide Emoji take-down order")、 独自に絵文字の文字コードを入力するためのキーボードを用意する事を考えています。 次のアップグレードでは、祝日の詳細を表示する機能の実装を予定していましたが、緊急性から、絵文字キーボードの実装が完了した時点でアップデート申請を行うかもしれません。

posted by Png genki on Sat 28 Feb 2009 at 04:50

MerbでExpiresやCache-Controlを設定する場合、 アクションメソッドの中で、以下のようにすれば良いようです。

   1  headers['Cache-Control'] = 'public'
   2  headers['Expires'] = 3.days.from_now.utc.rfc2822

posted by Png genki on Thu 26 Feb 2009 at 18:41

Merbでリソースに:editのようなカスタムアクションを追加する場合、 以下のように:memberオプションを利用出来ます。

config/router.rb

   1  resources :materials, :member => {:download => :get}

これによって、GET /materials/:id/downloadが、Materials#downloadにmapされます。

posted by Png genki on Thu 26 Feb 2009 at 01:24

Merbでファイルアップロードのspecを書く場合には、 multipart_*系のAPIを利用出来ます。 以下は一例です。

   1  tmp = Tempfile.new('spec')
   2  path = tmp.path + ".txt"
   3  begin
   4    file = open(path, "w+")
   5    file.write "test"
   6    multipart_post(resource(:materials),
   7      :material => {:id => nil, :label => 'test', :file => file})
   8    tmp.close
   9  ensure
  10    file.close
  11    File.unlink path
  12  end

:content_typeはfilenameから推定されるので、拡張子をくっつけています。

posted by Png genki on Thu 26 Feb 2009 at 00:59

Merbのデフォルト構成にはRailsのような HTMLのsanitizeを行うようなヘルパーメソッドは無いのですが、 Gemで提供されている sanitize というライブラリを使うと、簡単にHTMLのsanitizeを行う事が出来ます。

   1  Sanitize.clean(html, Sanitize::Config::RELAXED)

タグ毎に有効・無効の細かい設定が出来て、使い勝手が良いです。

posted by Png genki on Tue 24 Feb 2009 at 15:28

Singleton Methodを削除する方法 についてですが、 Objectクラスに用意されているとありがたいですね。

object_ext.rb

   1  class Object
   2    def remove_singleton_method(*args)
   3      (class << self; self end).class_eval do
   4        args.each{|i| send :remove_method, i}
   5      end
   6    end
   7  end

こんな感じ。

posted by Png genki on Mon 23 Feb 2009 at 22:02

方法を探していたのですが、意外と苦戦したのでメモ。

   1  object = new Object
   2  def object.foo; end
   3  object.singleton_methods #=> [:foo]
   4  (class << object; self end).class_eval{send :remove_method, :foo}
   5  object.singleton_methods #=> []

これで行けるようです。instance_eval{undef foo}でも削除はできますが、その場合親クラスのメソッドにもアクセス出来なくなってしまうので、 singleton methodだけを削除する場合には、 メタクラスのインスタンスメソッドをremove_methodするのが良いようです。

posted by Png genki on Mon 23 Feb 2009 at 21:53

MerbのCacheはなかなか優れた設計なので面白いのですが、 開発中にログにCache Miss/Hitの具合が分かるように出力してほしかったので、 development環境用のLoggingMemcachedStoreというのを書いてみました。

   1  class LoggingMemcachedStore < Merb::Cache::MemcachedStore
   2    include Extlib::Hook
   3   
   4    before :read do |key, params|
   5      if exists?(key, params)
   6        Merb.logger.debug "Cache Hit: #{key}"
   7      else
   8        Merb.logger.debug "Cache Miss: #{key}"
   9      end
  10    end
  11   
  12    before :write do |key, data, params, conds|
  13      Merb.logger.debug "Cache Write: #{key}"
  14    end
  15  end

こんな感じでログに出ます。

   1  merb : worker (port 4000) ~ Cache Hit: Plugins#show
   2  merb : worker (port 4000) ~ Cache Write: Plugins#show

posted by Png genki on Sat 21 Feb 2009 at 01:27

EC2のサーバ上でruby-1.8.x系で動作しているpassenger(aka mod_rails) と平行して、ruby-1.9.1を動かす環境を用意するために色々と試行錯誤を繰り返していたのですが、ひとまず良さそうな構成に落ち着いたのでメモしておきます。

構成

入り口から順に並べるとこんな感じです。

  • apache2 (:80)
  • mod_proxy_balancer (:80 -> :4000)
  • swiftiply (:4000 <- :30000)
  • merb cluster with SwiftipliedMongrel adapter (:30000)

apache2はpassengerを使っているので外せないとして、 ruby-1.9.1で動かすサービスはmod_proxy_balancerを使う事にしました。 最初はバックエンドに直接merb clusterを当てていたのですが、 現状のmerb clusterにはgraceful reloadする機能が無いようだったので、 間になにか挟む事にしました。

そこで、以前から目を付けていた Swiftiply を試す事にしました。 Swiftiplyはとても面白い設計のウェブサーバで、そのへんは rakutoさんの記事 「[Rails] Swiftiplyのアーキテクチャとベンチマーク」 を読むのがわかりやすいでしょう。

merbはbuilt-inでSwiftiply用のアダプター(SwiftipliedMongrel adapter)を備えているので、Swiftiplyを起動する設定をおこなえば、使うのはわりと簡単です。

しかし、例によってswiftiplyが依存しているeventmachineがruby-1.9.1 でインストール出来なかったので、原因を調査。 単純にテストの実行だけがうまく動いてないようだったので、テスト無しでインストールしました。

あとは、swiftiplyの起動スクリプトを用意。

/etc/init.d/swiftiply

   1  #!/usr/bin/env ruby
   2  
   3  PID = "/var/run/swiftiply.pid"
   4  
   5  case ARGV[0]
   6  when 'start'
   7    if File.exist?(PID)
   8      puts "swiftiply is already started."
   9      exit 1
  10    end
  11    print "Starting swiftiply: "
  12    pid = fork do
  13      Process.setsid
  14      Process.exec "swiftiply", "-c", "/etc/swiftiply.yml"
  15    end
  16    begin
  17      open(PID, "w"){|file| file.write pid}
  18      puts "done"
  19    rescue Exception => e
  20      puts e.message
  21      Process.kill 9, pid
  22    end
  23  when 'stop'
  24    print "Stopping swiftiply: "
  25    begin
  26      pid = open(PID).read.to_i
  27      File.unlink PID
  28      Process.kill 9, pid
  29      puts "done"
  30    rescue Exception => e
  31      puts e.message
  32    end
  33  when 'restart'
  34    system $0, 'stop'
  35    system $0, 'start'
  36  else
  37    puts "usage: {start|stop|restart}"
  38  end

アプリケーションの起動、停止は以下のようにおこないます。

   1  cd #{current_path}; #{merb} -K all
   2  cd #{current_path}; #{merb} -e production -a swift -c 1 -p 30000

これで動きますが、プロセスが死んだりすると不安なので、 monitを使って死活管理をおこなうようにします。

/etc/monit/monitrc

   1  check process swiftiply
   2    with pidfile /var/run/swiftiply.pid
   3    group root
   4    start program = "/etc/init.d/swiftiply start"
   5    stop program = "/etc/init.d/swiftiply stop"
   6    if failed port 4000 then restart
   7    if 5 restarts within 5 cycles then timeout
   8  
   9  check process merbist
  10    with pidfile /mnt/app/merbist/current/log/merb.30000.pid
  11    group root
  12    start program = "/mnt/app/merbist/shared/script/start"
  13      as uid app and gid app
  14    stop program = "/mnt/app/merbist/shared/script/stop"
  15      as uid app and gid app
  16    if failed port 30000 then restart
  17    if 5 restarts within 5 cycles then timeout
  18    depends on swiftiply

以上で完了。

現在、http://merbi.st/ は上記の設定で動いています。

See Aslo

追記

しばらく運用してみたのですが、プロセスが数時間に一回ぐらいのペースで落ちる問題があったので、Swiftiplyの代わりにPoundを使う構成に変えました。 Swiftiplyは次のバージョン0.7.0から、クラスタリングのサポートが強化されるようなので、それまで待ってみた方が良いかもしれないですね。

posted by Png genki on Fri 20 Feb 2009 at 16:23

Ruby-1.9.1を使うと、 以下のようにして簡単にRubyVMのInstaractionSequenceを見る事が出来ます。

   1  >> puts RubyVM::InstructionSequence.new("puts 'Hello, world!'").disasm #=> nil
   2  == disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
   3  0000 trace            1                       (   1)
   4  0002 putnil           
   5  0003 putstring        "Hello, world!"
   6  0005 send             :puts, 1, nil, 8, <ic>
   7  0011 leave            

生成したInstractionSequence(aka iseq)は、evalメソッドを呼び出して実行する事もできます。

   1  >> RubyVM::InstructionSequence.new("puts 'Hello, world!'").eval #=> nil
   2  Hello, world!

さらに、to_aメソッドで個々のインストラクションに分解する事も出来ます。

   1  >> RubyVM::InstructionSequence.new("puts 'Hello, world!'").to_a
   2  ["YARVInstructionSequence/SimpleDataFormat", 1, 1, 1, {:arg_size=>0, :local_size=>1, :stack_max=>2}, "<compiled>", "<compiled>", :top, [], 0, [], [1, [:trace, 1], [:putnil], [:putstring, "Hello, world!"], [:send, :puts, 1, nil, 8, nil], [:leave]]]

posted by Png genki on Fri 20 Feb 2009 at 01:49