gemのパッケージを展開する
gemをインストールはしたくないけど、展開して中身を見たいときのやりかた
コマンドラインからやるなら以下のようにすれば一応展開できてるっぽい。
mkdir classx-0.0.4 tar xvf classx-0.0.4.gem -C classx-0.0.4 cd classx-0.0.4 tar xvzf data.tar.gz
意外と検索してもやりかたのってないみたいなのでメモでした。
追記
http://jarp.does.notwork.org/diary/200809c.html
ふつうにgem unpack パッケージでいいようです。
少し言い訳をすると、
% gem help unpack Usage: gem unpack GEMNAME [options] Summary: Unpack an installed gem to the current directory
"installed gem"って書いてあったので、うっかりインストールしたgemじゃないとダメなんじゃないかと思ってました。思いこみじゃなくて実行して確かめるかちゃんとソース読まないとダメという話ですね。
インスタンスからdefaultを呼ぶ
http://d.hatena.ne.jp/fbis/20080918/1221717538
metaクラスからたどって直接呼び出した方がわかりやすいんではないかと思ったりしました。
package Foo; use Moose; has hoge => ( is => 'rw', lazy => 1, default => sub { 100 }, ); __PACKAGE__->meta->make_immutable; 1; package main; use strict; use warnings; my $foo = Foo->new; print $foo->hoge . "\n"; #=> 100 $foo->hoge(200); print $foo->hoge . "\n"; #=> 200 $foo->hoge($foo->meta->get_attribute_map->{hoge}->default->()); print $foo->hoge . "\n"; #=> 100
classXでも似たようなことができるので当然Mooseもできるのだろうなと探してみたというおはなしでした。
require 'classx' class Foo has :hoge, :writable => true, :default => 100 end foo = Foo.new foo.hoge #=> 100 foo.hoge = 200 foo.hoge #=> 200 foo.hoge = foo.attribute_of['hoge'].class.default(foo) foo.hoge #=> 100
継承可能なクラス属性
Rubyのクラス属性まわりは個人的には結構ややこしいと思っています。
クラスに保存できるデータには以下のようなものがあります。
- クラス変数
- クラスローカル変数( クラスインスタンス変数? )
- メソッド
クラス変数
@@variableのように表記します。
以下1.8での挙動。
class Foo @@foo = 'foo' def self.foo @@foo end end class Bar < Foo def self.bar @@foo end def self.update val @@foo = val end end Foo.foo #=> "foo" Bar.foo #=> "foo" Bar.bar #=> "foo" Bar.update 'hoge' #=> "hoge" Bar.bar #=> "hoge" Bar.foo #=> "hoge" Foo.foo #=> "hoge"
継承下のクラスで書きかえると、その親クラスに影響がでていることが見受けられます。
ライブラリを継承して作るようなクラスでクラス変数がかぶってしまうようなことがあると悲惨なことになります。
ちなみに1.9だと継承ツリーには影響を及ぼさず、継承すると値にセットされていた値は継承したサブクラスではnilになっているようです。
クラスローカル変数
Rubyで@variableな変数は通常インスタンス変数と呼ばれますが、実はクラスメソッドのコンテキストなどでも使うことができます。( この使いかたはruby使ってる人にはクラスインスタンス変数とか言うと通じやすかったりするのですが、rubyを知らない人には混乱を招きやすい名前なのであまり正確な呼び名ではないかなぁと思ってます。あえていうとクラスローカル変数とでもいえばいいんですかねぇ。。。)
class Foo @foo = 'foo' def self.foo @foo end end class Bar < Foo def self.bar @foo end def self.update val @foo = val end end Foo.foo #=> "foo" Bar.foo #=> nil Bar.update 'bar' #=> "bar" Bar.foo #=> "bar" Foo.foo #=> "foo"
クラス変数を使った場合と違い継承されません。
メソッド
メソッドの挙動はrubyを使っている皆さんには使いなれていて、非常に直感的な挙動をしていると思います。
class Foo def self.foo "foo" end end class Bar < Foo end Foo.foo #=> "foo" Bar.foo #=> "foo"
こちらは親クラスで定義しておいたデータを継承することができるかわりに、クラスレベルでの書きかえがしにくいです。(Rubyのオープンクラスを駆使すればさほど難しくはないですが;)
わりと複雑なライブラリのようなものを書いているとごくたまに継承可能なクラス属性のようなものが欲しくなることがあります。(例えば、モードを変更するとクラスの挙動が変わったり、クラスの属性にあわせてインスタンスの挙動をまとめて変更させたいとか。)
そのようなことのために簡単に定義できる実装はいくつかあって、現時点で最もよく使われてるのはactivesupportのclass_inheritable_accessorでしょう。
個人的にはclass_inheritable_accessorを使いたいがためにactivesupportのような巨大で通常のRubyのクラスを拡張しまくるライブラリには依存したくないなぁというのがあって、classxでやっていることを応用して、クラス属性をclassxの通常のアクセサと同様に定義できるようにしてみました。
require 'classx' class YourClass extend ClassX::CAttrs class_has :x, :writalbe => true, :default => proc {|klass| klass.to_s.split(/::/).last } end YourClass.x #=> "YourClass" YourClass.x = "HogeFuga" YourClass.x #=> "HogeFuga" class YourClass2 < YourClass end #=> nil # 今のYourClass.x属性の値は継承されず、 # デフォルトのProcに従ってデフォルト値ができる。 YourClass2.x #=> "YourClass2"
機能的には、このようにclass_inheritable_accessorを使いやすいようにDSLでラップしたmaihaさんのdsl_accessorとほぼ同様の使い勝手で、classXのアクセサと同様の機能が実現できるようになってます。(すなわち、アクセサの定義と同時にデフォルト値、Validationのルール、delegateの設定などが柔軟に定義できる)
さきほどリリースしたclassx 0.0.4で入っています。
SEE ALSO
JSONからIRCにポストするやつ
以前 http://d.hatena.ne.jp/walf443/20080523/1211497763 でGithubのHookを受けてIRCにポストしたりするやつを紹介したのですが、Github以外の場合からも同じコネクションを叩きたかったりしたので、その部分を分離してJSONから叩けるようにしてみました。classXの実験台的な意味合いもあります。
色々なGemを使いまくりなので結構楽にできました。使ったgemは
- json
- rack
- net-irc
- classx
ぐらいですね。
まだ少ししか運用してないので不安定なところもあるかもしれません。
URLはこちら。
http://github.com/walf443/json2irc/tree/master
似たような他のプロダクトとしては、
とかがあるそうです。
ClassX::Role::Logger
ClassXなクラスにこいつをincludeしてやると、標準ライブラリのloggerがattributeとして使えるようになります。
require 'classx/role/logger' class YourApp include ClassX extends ClassX::Commandable include ClassX::Role::Logger def run self.logger.debug("debug!!") # do something end end
別のModuleでattributeを定義してそいつをClassXなクラスにMixinしてやるというのはそのクラスのattributeが何かわかりづらくなりがちですが、loggerみたいな定番のものだと、だいたい意味がわかるので、よいですね。
それからClassX::Commandableと合わせて使うと便利で、
$ your_app.rb --logfile log/debug.log --log_level debug
のように実行できます。
ClassXがclassからmoduleになりました
東京Ruby会議01で発表したところ、ClassXがclassじゃなくてmoduleの方が便利じゃね?とか、使いたいけど、classになってるばかりに使えなさそう。と言う声が多かったので、どうせ誰も使ってないよね?ということで変えてしまいまいた。
つまりこういうふうに使うようになります。
require 'classx' class YourClass include ClassX has :x end
もしバリバリに使っていた人がいたらごめんなさい。
gemのver 0.0.3としてリリースしたので、すこしすればrubyforgeに反映されるはずです。
それから、発表資料のPDFはmodule版に差しかえて反映してあるので、前のバージョン使っていた人は参照してみてください。
ClassX::Commandableで簡単CLIアプリ
東京Ruby会議の懇親会で一番反響が良かったのはClassX::Commandableだったということでブログでもあたらめて紹介しておく。
MooseにはMooseX::GetoptとかMooseX::App::Cmdという便利なモジュールがあったのでMooseX::GetoptをパクってClassX::Commandableというのを作ったという話。
require 'classx' $ClassXCommandableMappingOf[Symbol] = String class YourApp include ClassX extend ClassX::Commandable has :arg1, :kind_of => Symbol, :desc => 'please specify arg1', :coerce => { String => proc {|val| val.to_sym } } has :arg2, :kind_of => Integer, :desc => "this is arg2", :optional => true def run # do something!! p attribute_of end end if $0 == __FILE__ YourApp.from_argv.run end
↑のように書いて実行すると、
yoshimi% ruby example/commandable.rb [ ~/Sources/ruby/classx ] example/commandable.rb [options] -a, --arg1 String please specify arg1 --arg2 [Integer] this is arg2 -h, --help show this document
のようにヘルプが出てくる。
ヘルプに書いてあるようにこのプログラムを実行するためには--arg1は必須。
yoshimi% ruby example/commandable.rb -a hoge [ ~/Sources/ruby/classx ] {"arg1"=><#ClassX::Attribute {:kind_of=>Symbol, :coerce=>{String=>#<Proc:0x00363300@example/commandable.rb:13>}, :desc=>"please specify arg1"}:1662200 @data=:hoge >, "arg2"=><#ClassX::Attribute {:kind_of=>Integer, :desc=>"this is arg2", :optional=>true}:1662120 >}
のように指定すると実行される。
yoshimi% ruby example/commandable.rb -a hoge --arg2 fuga [ ~/Sources/ruby/classx ] example/commandable.rb [options] -a, --arg1 String please specify arg1 --arg2 [Integer] this is arg2 -h, --help show this document
マニュアルによると --arg2はIntegerだが、文字列を指定しているのでヘルプが出る。
yoshimi% ruby example/commandable.rb -a hoge --arg2 10 [ ~/Sources/ruby/classx ] {"arg1"=><#ClassX::Attribute {:kind_of=>Symbol, :coerce=>{String=>#<Proc:0x00363684@example/commandable.rb:13>}, :desc=>"please specify arg1"}:1662280 @data=:hoge >, "arg2"=><#ClassX::Attribute {:kind_of=>Integer, :desc=>"this is arg2", :optional=>true}:1662200 @data=10 >}
こんどはちゃんと数字を指定しているのでおk
ところでarg1の定義のところで
has :arg1, :kind_of => Symbol, :desc => 'please specify arg1', :coerce => { String => proc {|val| val.to_sym } }
Symbolと指定しているのにどうやって変換しているかというと、
まずこのattributeにcoerceオプションを渡してStringで引数を渡した場合にSymbolに変換できるようにしておく。
そのあと、
$ClassXCommandableMappingOf[Symbol] = String
のように$ClassXCommandableMappingOfというグローバル変数にattributeの型名がSymbolの場合はコマンドラインでは文字列を取るように指定してあげる。
これで文字列で受けとる → Symbolで変換 というのを実現している。
グローバル変数というのがどうなのかなと思いつつもっといい仕様があれば教えていただけると嬉しいです。
ちなみにショートオプションは会議後に角谷さんから「ショートオプションサポートしてほしい」といわれてつけたのでHEADにしか入ってないです。
さきほど0.0.3をリリースしたのでそれに入っています。