現在使っているConnectionアダプターについて、 Migrationで利用可能なデータタイプを調べる方法のメモ。

   1  ActiveRecord::Base.connection.native_database_types.keys
   2  => [:boolean, :date, :binary, :datetime, :string, :integer, :decimal, :primary_key, :time, :float, :timestamp, :text]

posted by Png genki on Mon 14 Jul 2008 at 03:17

ブラックボックスなシステムを間に挟んで caller と callee 間で通信を行なう Wormholeのバージョン0.1.2 をリリースしました。

新しい機能

  • Named wormhole: ネストしたWormholeの呼び出しに対して、名前を付けて区別することができるようになりました
  • Syntax constraint: catchブロックの直後にreturnメソッドが呼ばれていない場合に例外を投げるようにしました
       1  w = Wormhole.catch do
       2  end #=> Wormhole::LateReturnError
       3  (snip)
       4  w.return
       5  
       6  Worrmhole.catch do
       7  end.return # => OK
    

ついでにWormholeの使用例を。

app/controllers/foo_controller.rb

   1  require 'wormhole'
   2  class FooController < ApplicationController
   3    def index
   4      Wormhole.catch do
   5        render :action => 'index'
   6      end.return do |result|
   7        result[:html] = "Hello, world!"
   8      end
   9    end
  10  end

app/views/foo/index.html.erb

   1  <% Wormhole.throw do |result| %>
   2    <%= result[:html] #=> 'Hello, world!' %>
   3  <% end %>

Viewのコードから一端Controllerのコードに戻って、 またViewに戻って結果を表示しています。

このように、おいそれとは変更できないようなフレームワークのコードを跨いで、呼び出し側と呼び出し元の間で情報のやり取りを行なう事ができます。

posted by Png genki on Sat 12 Jul 2008 at 06:05

D言語やPythonのように、複素数リテラルが欲しかったのですが、 無かったのでそれっぽい動きをさせるようにNumeric#iを定義してみました。

numeric_i.rb

   1  require 'complex'
   2  
   3  class Numeric
   4    def i
   5      self * Complex::I
   6    end
   7  end

これだけです。あとは以下のようにして使います。

   1  require 'numeric_i'
   2  
   3  1 + 1.i                        #=> Complex(1, 1)
   4  (Math::E ** (Math::PI.i)).real #=> -1.0

(自分が)簡単に使えるように、GitHubでGemを作っておきました。 利用する場合は以下の手順で導入できます。

   1  gem sources -a http://gems.github.com
   2  sudo gem install genki-numeric_i

名前空間のお陰で、シンプルなGemも気軽に公開できて良い時代ですね。

See Also

posted by Png genki on Thu 10 Jul 2008 at 22:21

昔は違った気がするのですが、手元の環境のautotest(ZenTest-3.10.0)とRSpec-1.1.4を使った状態で、テスト中に一度例外が発生した後に、全てのテストをgreenにすると、全てのテストがもう一回全部実行されるという現象が起きています。

全部テストを実行するのは大変なので、とりあえず、.autotest でパッチを当てて回避しました。

   1  class Autotest
   2    alias_method :handle_results_old, :handle_results
   3    undef_method :handle_results
   4  
   5    def handle_results(results)
   6      failed = results.scan(self.failed_results_re)
   7      completed = results =~ self.completed_re
   8  
   9      self.files_to_test = consolidate_failures failed if completed
  10  
  11      color = completed && self.files_to_test.empty? ? :green : :red
  12      hook color unless $TESTING
  13  
  14      #self.tainted = true unless self.files_to_test.empty?
  15      self.tainted = !self.files_to_test.empty?
  16    end
  17  end

L14-15行目が修正箇所です。

posted by Png genki on Sun 6 Jul 2008 at 11:46

メモもかねて。

MacBookにRailsの自動テスト環境を構築した

RAILS_ROOTでautotestを実行。起動時に全部のテストを実行するのでちょっとだけ重くなるかもしれません。それ以降はテストファイルやモデル、コントローラファイルなどを修正する度に該当のテストが走ります。

autotestを-fオプションを付けて立ち上げると、Fast startモードになり、最初に全部のテストを実行しなくなります。

posted by Png genki on Sun 6 Jul 2008 at 10:53

KagemushaでActiveRecordのfindなどのクラスメソッドをswap しようとすると、

   1  singleton method bound for a different object (TypeError)

というエラーが発生するという問題がありました。

このままでは困るので、とりあえず動くようにしてみました。 Unboundにしなければ問題は起こらないと思ったので、 instance_methodの代わりにalias_methodを使っています。

kagemusha_core_ext.rb

   1  require 'uuidtools'
   2  
   3  class Kagemusha #:nodoc:
   4    def swap #:nodoc:
   5      original_class_methods    = {}
   6      original_instance_methods = {}
   7  
   8      @class_methods.each { |name, proc|
   9        if proc
  10          begin
  11            # replace method
  12            #method = @meta.instance_method(name)
  13            method = random_name
  14            @meta.instance_eval { alias_method method, name }
  15            @meta.instance_eval { define_method(name, proc) }
  16            original_class_methods[name] = method
  17          rescue NameError
  18            # insert method
  19            @meta.instance_eval { define_method(name, proc) }
  20            original_class_methods[name] = false
  21          end
  22        else
  23          begin
  24            # remove method
  25            #method = @meta.instance_method(name)
  26            method = random_name
  27            @meta.instance_eval { alias_method method, name }
  28            @meta.instance_eval { undef_method(name) }
  29            original_class_methods[name] = method
  30          rescue NameError
  31            # nop
  32          end
  33        end
  34      }
  35  
  36      @instance_methods.each { |name, proc|
  37        if proc
  38          begin
  39            # replace method
  40            method = @klass.instance_method(name)
  41            @klass.instance_eval { define_method(name, proc) }
  42            original_instance_methods[name] = method
  43          rescue NameError
  44            # insert method
  45            @klass.instance_eval { define_method(name, proc) }
  46            original_instance_methods[name] = false
  47          end
  48        else
  49          begin
  50            # remove method
  51            method = @klass.instance_method(name)
  52            @klass.instance_eval { undef_method(name) }
  53            original_instance_methods[name] = method
  54          rescue NameError
  55            # nop
  56          end
  57        end
  58      }
  59  
  60      return yield
  61    ensure
  62      original_class_methods.each { |name, method|
  63        if method
  64          # replace method
  65          #@meta.instance_eval { define_method(name, method) }
  66          @meta.instance_eval { alias_method name, method }
  67          @meta.instance_eval { undef_method method }
  68        else
  69          # remove method
  70          @meta.instance_eval { undef_method(name) }
  71        end
  72      }
  73      original_instance_methods.each { |name, method|
  74        if method
  75          # replace method
  76          @klass.instance_eval { define_method(name, method) }
  77        else
  78          # remove method
  79          @klass.instance_eval { undef_method(name) }
  80        end
  81      }
  82    end
  83  
  84  private
  85    def random_name
  86      [UUID.random_128].pack("m").tr("=\n", '')
  87    end
  88  end

posted by Png genki on Sun 6 Jul 2008 at 05:22 with 2 comments

テストのC0カバレッジを上げるために、 何度もrcovを手で実行していたのですが、 テストの数が多くなってくると非常に時間がかかって面倒なため、 自動化するRakeタスクを作りました。

   1  namespace :rcov do
   2    desc 'Automate rcov'
   3    task :auto do
   4      interval = (ENV['INTERVAL'] || 10).to_f
   5      org_path = File.join(RAILS_ROOT, 'coverage', '*')
   6      pub_path = File.join(RAILS_ROOT, 'public', 'coverage')
   7      sh "mkdir -p #{pub_path}"
   8      loop do
   9        Rake::Task['spec:rcov'].execute nil
  10        sh "cp -Rf #{org_path} #{pub_path}"
  11        sleep interval
  12      end
  13    end
  14  end

使い方はRAILS_ROOTで以下のコマンドを実行するだけです。

   1  % rake rcov:auto INTERVAL=10

指定した時間間隔(秒)ごとにspec:rcovを実行します。 実行した結果はRSpecのデフォルトでは RAILS_ROOT/coverageに出力されるので、これを public/coverageに上書きコピーしています。 あとは、ブラウザで/coverageを見ればOKです。

public/coverageは間違ってデプロイしないように.gitignoresvn:ignoreに登録しておきましょう。

posted by Png genki on Sat 5 Jul 2008 at 03:43

メソッドやProcの内部で継続(Continuation)を実行した後に、 そのメソッドの戻り値がどうなるのか理解があやふやだったので実験。

前準備。

   1  a = nil
   2  foo = proc{callcc{|a|}; 1}
   3  bar = proc{foo.call; 2}
   4  baz = proc{a.call; 3}

実験開始。

   1  >> foo.call
   2  => 1
   3  >> baz.call
   4  => 1
   5  >> bar.call
   6  => 2
   7  >> baz.call
   8  => 2

なるほど。

posted by Png genki on Fri 4 Jul 2008 at 18:57

ちょっと必要に駆られて、 Wormhole というライブラリを書きました。

350px-Worm3.jpg

インストールはGitHubから行えます。

   1  sudo gem sources -a http://gems.github.com
   2  sudo gem install genki-wormhole

メソッドの内部を実行中に、呼び出し元に一旦処理を戻し、 中断した場所から処理を継続する機能を提供します。

例えば、以下のようなプログラムに対して、

   1  require 'rubygems'
   2  require 'wormhole'
   3  
   4  def foo
   5    puts "foo"
   6    Wormhole.throw :foo => 'hello' do |data|
   7      puts data[:foo]
   8    end
   9    puts "bar"
  10  end
  11  
  12  Wormhole.catch do
  13    foo
  14  end.return do |data|
  15    puts data[:foo]
  16    data[:foo] = 'world!'
  17  end

結果として、以下のような出力を得ます。

   1  foo
   2  hello
   3  world!
   4  bar

処理を中断したいところで Wormhole.throw を呼び出して、 外側から Wormhole.catch で受け止めます。 catchが完了したら、Wormholeオブジェクトが返るので、 returnで中断箇所に戻ります。 Wormhole.throw はHashを受け渡すことができます(省略可能)。 また、catchブロックとreturnは上記の例のように続けて記述します。 catchの戻り値を適当な変数に代入して、後でreturn を呼び出すことも可能ですが、その場合、catchを抜けた直後から returnを呼ぶまでの区間が二度実行されてしまいます (この辺が最初に例外を使って実装してた理由ですね)

中断箇所から処理を再開するために callcc を使っています。 callccは面白いですね。

更新履歴

  • 2008/7/4 例外処理をやめてthrow/catchを使うようにしました。それにあわせて本文を修正しました。
posted by Png genki on Thu 3 Jul 2008 at 20:06 with 5 comments

以前笹田さんに、procのソースが見たいとお願いしてみたことがあったのですが、 それをRubyコードレベルで実現するライブラリを見つけたので紹介します。

proc_source.rb

I wrote this a while ago and it works by extracting a proc's origin file name and line number from its .inspect string and using the source code (which usually does not have to be read from disc) -- it works with procs generated in IRB, eval() calls and regular files. It does not work from ruby -e and stuff like "foo".instance_eval "lambda {}".source probably doesn't work either.

オリジナルはメールの添付ファイルとしてくっついているので、ソースを見たい場合は こちらから見るといいかもしれません。

使い方はこんな感じです、

   1  code = proc{puts "Hello World"}
   2  puts code.source #=> puts "Hello World"

これは久々に面白いものを見た気がします。

posted by Png genki on Thu 3 Jul 2008 at 13:43
Contents
Migrationで利用可能なネイティブデータタイプを調べる方法
Wormhole-0.1.2リリース
Numeric#iで虚数を簡単に使う
autotestでall_good時にrerun_all_testsをしないようにする
autotestで起動時に全部テストを実行しないオプション
KagemushaとActiveRecordの不具合の解消
RCovを自動実行させる rake rcov:auto
継続を実行した後のメソッドやProcの戻り値に関するメモ
Wormhole: An example of use for callcc
Procのソースを取り出すライブラリ:proc_source.rb
Comments
瀧内元気: MacOS版は以下にあります * [genki/ViMouse](https://githu... '23-1
dsjf: https://gist.github.com/6bf1bf2c3cbb5eb6e7a7 これ... '13-1
瀧内元気: おお、チェックしてみます。thx! '11-12
overisland: Reeder for iPhone もこの UI を実装していますね。 '11-12
瀧内元気: その情報は見たのですが、以下のサイトによると、現在はまた必要になってるっぽいんですよね。 ... '11-12
Services from s21g
twpro(ツイプロ)
Twitterプロフィールを快適検索
地価2009
土地の値段を調べてみよう
MyRestaurant
自分だけのレストラン手帳
Formula
ブログに数式を埋め込める数式コミュニティ