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をリリースしたのでそれに入っています。