Hatena::Grouprubyist

Going My Ruby Way このページをアンテナに追加 RSSフィード

Ruby ロゴ (C) Ruby Association LLC

2011年07月22日(金)

アクセサ定義 property

| 21:42 | アクセサ定義 property - Going My Ruby Way を含むブックマーク はてなブックマーク - アクセサ定義 property - Going My Ruby Way アクセサ定義 property - Going My Ruby Way のブックマークコメント

Ruby ベストプラクティス』第3章に「2つの目的を兼ねたアクセサ」として

c = C.new
c.font_size(30)   # c.font_size = 30 と同じ働き

のような例が書かれてました。が、本の紹介は自分的にはイマイチだったので自分流カスタマイズです。(GMRW(=Going My Ruby Way)です。改題したばっかですし)

以下の日記で書いた proc_accessor の改良(?)でもあります。

----

インスタンス変数へのアクセサを定義するメソッド propertyです。

以下のように使います。

class C
  extend Property

  property :foo
end

c = C.new
c.foo(10)   # インスタンス変数 @foo に 10 が設定されます
c.foo       # => 10  (@foo を返しています)

本と変わりませんね。

さらに、こうも使えます。proc を設定できます。

c.foo do |s|  # c.foo = proc {|s| s + "!!"} と同じです。
  s + "!!"
end

c.foo.call("hello")  # => "hello!!"  

前の日記で書いた proc_accessor の機能の取り入れです。

さらに、2 番目の引数でデフォルト値を記述できます。

デフォルト値は module_eval で評価するので文字列で与えます。

シングルクォーテーションで囲って渡しましょう。

class C
  property :val, '10'
  property :str, '"hello"'
  property :ary, '[]'
  property :hsh, '{}'
  property :sym, ':x'
  property :prc, 'proc {|s| s * 2}'
  property :hol, 'Hash.new {|h,k| h[k] = k * 3}'
end

c.val           # => 10
c.str           # => "hello"
c.ary           # => []
c.hsh           # => {}
c.sym           # => :x
c.prc.call(10)  # => 20
c.hol[3]        # => 9
c.hol["A"]      # => "AAA"

数値、truefalsenil に関してはクォーテーションで囲まなくとも大丈夫です。

(リテラルを含む) [] や {} も大丈夫です。

class C
  property :x, 10
  property :y, true
  property :z, false
  property :w, nil
end

c.x    # => 10
c.y    # => true
c.z    # => false
c.w    # => nil

Symbol をクォーテーションで囲まずに渡すと(本当の)シンボルに評価されます。

class C
  property :bar, :str
end

c.bar    # => "hello"

:str はシンボル str に評価されてメソッド str を呼び出しました。(str の返す値 "hello" を返しています)

デフォルト値の評価は、最初にアクセサを呼び出した時に行なわれます。

c.str("goodbye")  # この後で、@str を変更しても
c.bar             # "hello" が返る

また、property では、foo=() の定義はされません。

必要ならば attr_writer を使いましょう。(property は attr_reader の置き換え相当です)

----

で、コードです。

property.rb

#!/usr/bin/env ruby
# -*- coding : UTF-8 -*-

module Property
  extend self

  def property(name, default='')
    module_eval %-  
      def #{name}(*a, &block)
        @#{name} = (#{default}) unless instance_variable_defined? :@#{name}

        @#{name} = !a.empty? ? a[0]
                 : block     ? block
                 : @#{name}
      end
    -
  end
end

class Object
  extend Property
end

# vi:se ts=2 sw=2 et fenc=UTF-8:

(2011/07/23 変更: class Objectextend Property するようにした。property.rb を require したら extend しなくとも各Class定義で property が使えます。(というか強制的に使えるようになってしまう)