東京Ruby会議01で発表しました

Akasaka.rbの活動の一環として東京Ruby会議01でClassXについて発表してきました。

スライドは http://github.com/walf443/classx/tree/master%2Fdoc%2FTokyoRubyKaigi01_classX.pdf?raw=true

からDLできます。

発表すると、 http://github.com/walf443/classx/tree/28f1694a54968cb56c674fe62a6f2b6ad35a46b9/lib/classx/declare.rb

みたいなフィードバックが受けられるので素晴しいですね。

追記

早くも動画がUpされているようです。cojiさん仕事早すぎwww

盛大にDISっていただけると光栄です。 http://www.nicovideo.jp/watch/sm4367766

ClassX::Declare

東京Ruby会議01で高井さんにどうよといわれたやつ。


http://github.com/walf443/classx/tree/28f1694a54968cb56c674fe62a6f2b6ad35a46b9/lib/classx/declare.rb

↑のやつには使うとどんな風にできるかの説明がなかったので書いておきます。

http://gist.github.com/6541

# with ClassX::Declare
# cool!! thanks Naoto Takai
 
require 'classx'
require 'classx/declare'
include ClassX::Declare
  
classx "Hoge" do
 has :x
end
 
Hoge.new(:x => 0)
  #=> #<Hoge:0x111a7dc @__attribute_of={"x"=><#ClassX::Attribute {}:8966980 @data=0 >}>

個人的には普通に書いてもいいような気もしつつ、何回もclass書くのがたるいという意見もそうかもなぁと思ったので、defaultでは有効になりませんが、オプションで使いたい人はつかえばいいよという方針で入れてみました。

RubyCocoaでFluent Interfaceはどうか

http://subtech.g.hatena.ne.jp/secondlife/20080709/1215530958のFluent InterfaceはRubyCocoaの呼びだしで使えたらうれしいかもと思った。

今はobjc_sendをつかって、

    NSTimer.objc_send(:scheduledTimerWithTimeInterval, 120,-
      :target, self,
      :selector, 'update',
      :userInfo, nil,
      :repeats, true
    )

なふうに書いているのだけど、

NSTimer.objc_send_fluent(:scheduledTimerWithTimeInterval, 120).target(self).selector('update').userInfo(nil).repeats(true).execute

とか書けるのはどうかなと思ったけど、Cocoaに投げるまでメソッドがないかどうかは結局わからないのであまりうれしくもない気がする。

間違ったメソッドを実行した時点でエラーになってくれるとtypoを見つけやすくて助かるのだけど。。。

ClassX::ValidateでHashをチェックする

http://d.hatena.ne.jp/rubikitch/20080710/1215641240

メソッドの引数にHashをとるのはいっぱいあるときでもきれいに書けるので個人的にはあまり嫌いではない。

classXを使うとHashのチェックが柔軟にできるよというおはなし。

require 'classx/validate'

class YourClass
 include ClassX::Validate

 def run params
   validate params do
     has :x
     has :y, :default => proc { {} }, :kind_of => Hash
   end

   # do something with params

 end
end

classXのアクセサの宣言の仕方でHashをチェックできる。メソッドに渡すべきオプションを宣言的に書くことでコードも読みやすくなるはず。

実は内部ではブロックで指定したアクセサを持つclassXベースの無名クラスを作りclassXのアクセサのValidateをさせているだけ。戻り値はその無名クラスのインスタンスなのであまりかっこよくはないけど使う方が堅実に書ける。

require 'classx/validate'

class YourClass
 include ClassX::Validate

 def run params
   validated_params = validate params do
     has :x
     has :y, :default => proc { {} }, :kind_of => Hash
   end

   p validated_params.x
   validated_params.y = '' # => raise ClassX::InvalidAttrArgument
   # do something with params

 end
end

classXはhttp://github.com/walf443/classx/tree/masterで公開してます

メソッドの渡すHashもきっちりチェックしたい

http://d.hatena.ne.jp/walf443/20080524/1211650071

前の記事でコンストラクタに渡せるHashのキーと値を宣言的に書くことで見通しが良くなると書きましたが、コンストラクタに限らず、メソッドにオプションとして渡すHashもきっちりチェックしたいはず。ということで書いてみました。

内部的には無名のClassXを継承したクラスを作ってやってインスタンス化することでHashをチェックしてるので、classXと同様に使えます。

エラーのときのメッセージは調整した方がデバックなどのしやすさのために良いですが、まだ未調整です。

diff --git a/lib/github_post_reciever/worker/base.rb b/lib/github_post_reciever/worker/base.rb
index 4b1ac05..1a931be 100644
--- a/lib/github_post_reciever/worker/base.rb
+++ b/lib/github_post_reciever/worker/base.rb
@@ -1,9 +1,12 @@
 require 'drb/drb'
 require 'classx'
+require 'classx/validate'
 
 class GitHubPostReciever
   module Worker
     class Base < ClassX
+      include Validate
+
       def run method, json
         raise NoImprementedError
       end
diff --git a/lib/github_post_reciever/worker/irc.rb b/lib/github_post_reciever/worker/irc.rb
index e3aa0ab..ec1d5b0 100644
--- a/lib/github_post_reciever/worker/irc.rb
+++ b/lib/github_post_reciever/worker/irc.rb
@@ -43,13 +43,20 @@ class GitHubPostReciever
       has :template, :is => :ro, :kind_of => String, :required => true
 
       def run method, json
-        json['commits'].reverse.each do |sha, commit|
+        validated_json = validate json do
+          has :commits, :kind_of => Hash, :required => true
+        end
+
+        validated_json.commits.reverse.each do |sha, commit|
           CommitPingBot.new(@host, @port, {
             'nick' => @nick,
             'user' => @user,
             'real' => @real,
           }).run("##{method}", View.new(@template, commit).result)
         end
+      rescue ClassX::InvalidSetterArgumentError => e
+      rescue ClassX::AttrRequiredError => e
+        warn e
       end
     end
   end

実装の方に興味がある方は、http://github.com/walf443/classx/tree/master/lib/classx/validate.rb
をどうぞ。

デフォルトのオプションと:isおよび:requiredオプションの廃止

classXで、Mooseをなぞるのをやめてアクセサの宣言のデフォルトの設定を変更しました。

:writableオプションを指定しない限りは、アクセサはwriterをpublicにしないようになりました。これは、単にreaderを定義する時は、オプションを明示的に定義する必要はなく、writerをpublicから触れるようなときはより明示的に指定した方が良いと思ったからです。:isを:writableに変更したのは強調させる意図です。

また、:defaultオプションを指定するか、:optionalにtrueを指定したとき以外はアクセサは必要な状態になるようにし、ないと例外が起こるようにしました。これは、オプションなアクセサは明示的に示すべきだと考えたからです。

具体例はコミットログと追加されたテストを見るのがわかりやすいです。

メソッドの渡すHashもきっちりチェックしたい

http://d.hatena.ne.jp/walf443/20080524/1211650071

前の記事でコンストラクタに渡せるHashのキーと値を宣言的に書くことで見通しが良くなると書きましたが、コンストラクタに限らず、メソッドにオプションとして渡すHashもきっちりチェックしたいはず。ということで書いてみました。

内部的には無名のClassXを継承したクラスを作ってやってインスタンス化することでHashをチェックしてるので、classXと同様に使えます。

エラーのときのメッセージは調整した方がデバックなどのしやすさのために良いですが、まだ未調整です。

diff --git a/lib/github_post_reciever/worker/base.rb b/lib/github_post_reciever/worker/base.rb
index 4b1ac05..1a931be 100644
--- a/lib/github_post_reciever/worker/base.rb
+++ b/lib/github_post_reciever/worker/base.rb
@@ -1,9 +1,12 @@
 require 'drb/drb'
 require 'classx'
+require 'classx/validate'
 
 class GitHubPostReciever
   module Worker
     class Base < ClassX
+      include Validate
+
       def run method, json
         raise NoImprementedError
       end
diff --git a/lib/github_post_reciever/worker/irc.rb b/lib/github_post_reciever/worker/irc.rb
index e3aa0ab..ec1d5b0 100644
--- a/lib/github_post_reciever/worker/irc.rb
+++ b/lib/github_post_reciever/worker/irc.rb
@@ -43,13 +43,20 @@ class GitHubPostReciever
       has :template, :is => :ro, :kind_of => String, :required => true
 
       def run method, json
-        json['commits'].reverse.each do |sha, commit|
+        validated_json = validate json do
+          has :commits, :kind_of => Hash, :required => true
+        end
+
+        validated_json.commits.reverse.each do |sha, commit|
           CommitPingBot.new(@host, @port, {
             'nick' => @nick,
             'user' => @user,
             'real' => @real,
           }).run("##{method}", View.new(@template, commit).result)
         end
+      rescue ClassX::InvalidSetterArgumentError => e
+      rescue ClassX::AttrRequiredError => e
+        warn e
       end
     end
   end

実装の方に興味がある方は、http://github.com/walf443/classx/tree/master/lib/classx/validate.rb
をどうぞ。