Rack::Auth::IPをリリースしました

GitHubPostRecieverでGitHub以外からのpostは受け付けたくなかったのでRackでIPのアクセス制限ができるRack::Auth::IPというミドルウェアを作ってみました。

rackupのファイルの中で、

  require 'rack/auth/ip'
  # ローカルネットワークにアクセスを制限する
  use Rack::Auth::IP, %w( 192.168.0.0/24 )

  # ブロックでも使える
  # ipにはIPAddrのインスタンスが与えられる
  use Rack::Auth::IP do |ip|
    Your::Model::IP.count({ :ip => ip.to_s }) != 0
  end

のように使えます。

$ gem install rack-auth-ip

でインストールできます。

機能的に増やすということはあまりないと思いますが、
http://github.com/walf443/rack-auth-ip/tree/master
ソースコードを公開してますので、何かバグなリがあれば一報ください。

Rackのミドルウェアとして作ることで他の様々なアプリでも同じように設定できるようになるので、Rackは素晴らしいなと思います。

追記(2008/06/28)

ブロック形式がRackのMiddlewareの仕様を勘違いしていてうまく動作してなかったようです。先ほど出した0.0.3で直っています。

また、以前の例であったuse Rack::Auth::IP { ... }というのは当然ながらRubyの文法的に動きませんので、do .. endを使ってくださいw

classXを使うと何がうれしいか

http://d.hatena.ne.jp/walf443/20080520/1211239672

で触れた宣言的なアクセサをGitHubPostRecieverに試しに適用してみた

+      has :host, :is => :ro, :kind_of => String, :required => true
+      has :port, :is => :ro, :kind_of => Integer, :default => 6667
+      has :nick, :is => :ro, :kind_of => String, :required => true
+      has :user, :is => :ro, :kind_of => String, :lazy => true, :default => proc {|mine| mine.nick }
+      has :real, :is => :ro, :kind_of => String, :lazy => true, :default => proc {|mine| mine.nick }
+      has :template, :is => :ro, :kind_of => String, :required => true
+
       def run method, json
-        json['commits'].each do |sha, commit|
-          CommitPingBot.new(@config['host'], @config['port'], {
-            'nick', @config['nick'],
-            'user', @config['user'],
-            'real', @config['real'],
-          }).run("##{method}", View.new(@config['template'], commit).result)
+        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
       end
     end

元々のクラスだと、@configの中にどんな設定ができるのかということがわかりづらかったのですが、アクセサにしてしまえば、どの設定が必須なのかとかどんな値を指定すれば良いかが一目瞭然ですね。

Commit Bot for GitHub

GitHubでは、プロジェクトのコミットをfollowして興味のあるプロジェクトのコミットのみをまとめてみれるのですが、毎日それらを全部見るのはなかなか大変だったりします。

興味のあるプロジェクトの場合、割とfreenodeにIRCのチャンネルがあって、そこにjoinしていたりするので、#codereposみたいにコミットを流したくなり、今まではplaggerでGitHubのフィードをチェックしてNotify::IRCでポストしていたのですが、3つ、4つと設定するたびに、cronやplagger-ircdがどんどん増えていき、管理が大変になりました。

そこでそれらをまとめられないかなと自分で書くことにしました。GitHubはプロジェクトにURLを登録しておくと、そこにコミット情報をJSONで送ってくれる機能があるので、それらを受けて動作するものにしました。その方が即時性が高いし、定期アクセスさせないですむので向こうのサーバーにも優しいからです。

使ってるGemは、json、net-irc、rack ( mongrel )と比較的インストールも楽なのでぜひお試しください。ひょっとすると、各worker ( pluginのようなもの )に設定できる項目を宣言的に書いてもっとわかりやすくするために、この前のclassXを今後使うようにするかもしれません。( まだ gemにもなってないですがw )

http://github.com/walf443/github-post-reciever/tree/master

この手のプロジェクトはGitHubに色々あるのですが、自分のやつの特徴としては、サーバーがrequestを受けてresponseはすぐに返してしまい、非同期に処理をさせるために、Drb、Threadを使っていることです。この方がblockしないので向こうのサーバーには優しいのではないかなと思っている次第です。

Mooseっぽい柔軟なアクセサ

最近perl界隈で話題のMooseをちょくちょく弄ってみたりしているのですが、宣言的なアクセサは見通しも良くなるし、なかなか良いなと思ったんでRubyでもそれっぽく動作するやつを書いてみました。

Mooseっぽくコンストラクタも勝手に用意するようにしてみたのですが、これは好みが分かれるところのような気もするので、アクセサの部分は分離しても良いかなと思いました。ただ、あらかじめコンストラクタで引数を固定しておくとあとから拡張したいときや、デフォルトでは必要ないのだけど、テストのときとか書き換えてやりたいときにめんどくさかったりするのでHashを渡せばいいという割り切り方はなかなか良いなと思います。

とりあえずネタで書いてみたという程度なので、エラーメッセージなどは例によって今のところてきとうです。あと黒魔術はもっと減らせる気がしますw

いろいろなDSLをつかってるものとバッティングしそうな感じですが(汗

  class Point < ClassX
    has :x, :is => :ro, :kind_of => Fixnum, :default => 10
    has :y, :required => true
    has :hoge, :default => proc { "Hoge" }
  end

  Point.new
end

追記:

なんとなくGithubの方に入れてみました。
http://github.com/walf443/classx/tree/master

ブロックの解釈の仕方をカスタマイズする

DSLみたいなものを作ろうとしてると、どうしてもブロックを俺俺文法で解釈させたくなって調べてみた。

ParseTreeというライブラリを使うとRubyのコードをS式に変換することが出来る。
S式に変換できるのは、モジュール単位、モジュールのメソッド単位、文字列を渡すの3種類の方法があり、
自分はブロックの部分のみをいじりたかったので次のようにしてみた

require 'rubygems'
require 'parse_tree'

class Hoge
 def fuga &block
    mod = Module.new
    mod.module_eval do
       mod.define_method :block, &block
    end
    s_exp = ParseTree.translate(mod, :block)
    HogeProcesssor.new(s_exp)
 end
end

無名モジュールを作ってやってメソッド名を決めうちで定義してやってParseTreeに渡している。Ambitionのコードを見てみると違うやり方をしていたっぽいのだけどよくわからなかったw

HogeProcessorはSexpProcessorを継承したクラスでS式はこのクラスでいじくり回して欲しい形に変換する。
parse_〜というメソッドを定義しておけば自動的に実行されることはわかったのだけど、それ以外はよくわからなかった。

とりあえず頑張ってやってみてもParseTreeを使ってる時点で1.9に対応できなくなってしまうので、いいやと思ってHashとArrayの組み合わせで頑張ることにしたのでした。

非常に中途半端なのですが、また使いたくなった際に忘れてそうなのでメモです

rspec-fixutre 0.0.1をリリースしました

先日言っていた機能はまだ足してはないのですが、一通り個人的に使いたい機能を動くようにして、テストも充実させたのでgemにしました。

gem install rspec-fixture

でインストールできます。

http://coderepos.org/share/changeset/6563

で、単純なテストがいっぱいある場合にrspec-fixtureを使うとこんなに見通しがよくなるというサンプルとして書き直してみました。元々のテストコードがわかりづらすぎるという意見もあるかとは思いますが、ご参考までにどうぞ。

RSpecをTest::Baseっぽく使う

RSpecでテストを書いていて単純なテストなんだけど、いろいろなデータで検証させたいといった場合に毎度毎度exampleを書くのがめんどくさいと思い、こんな感じでTest::Baseっぽく使えるやつを作ってみた。

describe Point, "detect_location" do
  with_fixtures :point => :location do
    filters({
      :point    => lambda { |val| Point.new(*val) },
      :location => :to_s,
    })

    it "should detect point :point to :location (:msg)" do |point, location|
      point.detect_location.should == location
    end

    set_fixtures([
      [ [1,   0]  => :right  ],
      [{[-1,  0]  => :left }, "border"  ],
      [{[-0.5,0]  => :left }, "inner" ],
      [ [0,   1]  => :top  ],
      [ [0,  -1]  => :bottom ],
    ])
  end
end

実行結果はこんな感じになる

Point detect_location
- should detect point ( 1, 0 ) to right () (FAILED - 6)
- should detect point ( -1, 0 ) to left (border) (FAILED - 7)
- should detect point ( -0.5, 0 ) to left (inner) (FAILED - 8)
- should detect point ( 0, 1 ) to top () (FAILED - 9)
- should detect point ( 0, -1 ) to bottom () (FAILED - 10)

6)
'Point detect_location should detect point ( 1, 0 ) to right ()' FAILED
expected: "right",
     got: "unknown" (using ==)
./examples/detect_location_spec.rb:47:

7)
'Point detect_location should detect point ( -1, 0 ) to left (border)' FAILED
expected: "left",
     got: "unknown" (using ==)
./examples/detect_location_spec.rb:47:

8)
'Point detect_location should detect point ( -0.5, 0 ) to left (inner)' FAILED
expected: "left",
     got: "unknown" (using ==)
./examples/detect_location_spec.rb:47:

9)
'Point detect_location should detect point ( 0, 1 ) to top ()' FAILED
expected: "top",
     got: "unknown" (using ==)
./examples/detect_location_spec.rb:47:

10)
'Point detect_location should detect point ( 0, -1 ) to bottom ()' FAILED
expected: "bottom",
     got: "unknown" (using ==)
./examples/detect_location_spec.rb:47:

with_fixturesの引数は省略すると{ :input => :expected }がデフォルトで使われる。
実はHashである必然性は全然ないのだけど、こう書けた方が視覚的にわかりやすいのでこう書くようにした。

filterは値がProcの場合はそれが実行され、Procじゃない場合は順番に__send__されて適用するようにしてみた。この辺は使ってみてまだ色々変わったりとかしそう。

set_fixturesで渡すデータをTest::Base互換で書けるようにするとか、exampleを複数書けるようにするとか、exampleがinput.should == expectedになるときは省略して書けるようにするとか、まだ色々やろうかなと思うことはあるのでgemにはしてないです。

いつものようにCodereposに入れてあるので色々アドバイスなり、いただけるとありがたいです。

http://coderepos.org/share/browser/lang/ruby/rspec-fixture/trunk