From: Motomichi Matsuzaki <mzaki / e-mail.ne.jp>
Subject: [ruby-list:40794] Re: nil以外のときメソッドを呼ぶ
Date: Thu, 28 Apr 2005 06:12:42 +0900

るびきちです。

> るびきちさんの Struct からの継承を参考に
> まじめに書くことにしました。

自分も必要になったので自動型変換(など)をする構造体を書いてみました。
StructWithTypeクラスで、構造体宣言時に変換関数を指定できるます。

StructWithType.new(:x, :Float_non_nil,
                   :y, :Integer_non_nil,
                   :z, :strip_XX,
                   :a, :String)

構造体要素への代入時に指定された関数を通します。

#### コード
class StructWithType < Struct

  def self.new(*args)

    keys = []
    types = []
    args.each_with_index do |x,i|
      if i%2 == 0
        keys << x
      else
        types << x
      end
    end

    unless keys.length > 0 &&
        types.length > 0   &&
        keys.length == types.length
      raise ArgumentError, "#{self}: args.length must be even"
    end
        

    klass = super(*keys)

    klass.instance_eval do
      @@__type_dic__ = {}
      @@__keys__ = keys
      keys.each_with_index{|k,i| @@__type_dic__[k] = types[i]}
    end

    klass
  end

  def initialize(*args)
    args.each_with_index do |x, i|
      args[i] = __convert__(@@__keys__[i], x)
    end

    class << self
      @@__keys__.each do |k|
        define_method("#{k}="){|v| self[k]=v}
      end
    end

    super *args
  end

  def __convert__(k,v)
    __send__(@@__type_dic__[k.to_sym],v)
  end
  private :__convert__

  def []=(k,v)
    v = __convert__(k,v)
    super(k,v)
  end


end


#### テスト
class TestStructWithType < Test::Unit::TestCase

  def common_test
    aref_test(1,"s")

    @x.i = 10
    @x[:s] = "S"
    aref_test(10,"S")

    @x["s"] = "Str"
    aref_test(10,"Str")
  end

  def aref_test(i,s)
    assert_equal(i, @x.i)
    assert_equal(s, @x.s)

    assert_equal(i, @x[:i])
    assert_equal(s, @x[:s])

    assert_equal(i, @x["i"])
    assert_equal(s, @x["s"])

    assert_equal([i,s], @x.collect{|x| x})

    a = []
    @x.each_pair{|k,v| a << [k,v]}
    assert_equal([[:i,i],[:s,s]], a)
  end

  def test_struct
    struct_class = Struct.new(:i, :s)
    @x = struct_class.new(1, "s")
    common_test
  end

  def test_struct_with_type__normal
    struct_class = StructWithType.new(:i,:Integer, :s,:String)
    @x = struct_class.new(1, "s")
    common_test
  end

  def test_struct_with_type__convert
    struct_class = StructWithType.new(:i,:Integer, :s,:String)
    @x = struct_class.new("1", "s")
    common_test
  end

  def test_struct_with_type__aset
    struct_class = StructWithType.new(:i,:Integer, :s,:String)
    @x = struct_class.new("1", "s")
    @x[:i] = "10"
    aref_test(10,"s")
    @x['i'] = "100"
    aref_test(100,"s")
    @x.i = "1000"
    aref_test(1000,"s")

    struct_class = StructWithType.new(:a,:String, :b,:Float, :c,:Integer)
    @x = struct_class.new("a", "1.1", "2")
    assert_equal("a", @x.a)
    assert_equal(1.1, @x[:b])
    assert_equal(2, @x["c"])

  end

  def test_error
    assert_raises(ArgumentError) { StructWithType.new() }
    assert_raises(ArgumentError) { StructWithType.new(:i) }
    assert_raises(ArgumentError) { StructWithType.new(:i,:Ingteger, :s) }
    assert_raises(ArgumentError) { StructWithType.new(1) }
    assert_raises(ArgumentError) { StructWithType.new(1,2) }
    assert_raises(ArgumentError) { StructWithType.new(1,2,3) }
  end

  class Field < StructWithType.new(:x, :Float_non_nil,
                                   :y, :Integer_non_nil,
                                   :z, :strip_XX,
                                   :a, :String)
    def self.[](line)
      new(*line.split(/\t/))
    end

    def non_empty?(x)
      x && !x.empty? || nil
    end

    def Float_non_nil(x)
      non_empty?(x) && Float(x)
    end

    def Integer_non_nil(x)
      non_empty?(x) && Integer(x)
    end

    def strip_XX(x)
      non_empty?(x) && x.sub(/^XX/,'')
    end

    def String_non_nil(x)
      non_empty?(x) && x
    end

  end

  def field_compare(x,y,z,a)
    assert_equal(x, @x.x)
    assert_equal(y, @x.y)
    assert_equal(z, @x.z)
    assert_equal(a, @x.a)
  end    

  def test_field
    @x = Field["1\t2\tXX3\t4"]
    field_compare 1.0, 2, "3", "4"

    @x = Field["\t\t\t"]
    field_compare nil, nil, nil, nil

    @x = Field["1\t\t3\t"]
    field_compare 1.0, nil, "3", nil
  end

end


るびきち☆
http://www.rubyist.net/~rubikitch/ ←Ruby大衆化計画@移転