Class methods like attr:

How can I create methods like "attr:" that can create methods and instance
variables?

I'd like to do this

  class B < A
    my_property :k, "K"
    def test
      p @k # should print "K"
      p @@my_properties # should print ['k']
    end
  end

I tried with this (non-) solution

  class A
    def A.my_property(sym, val)
      self.instance_eval {
        @@my_properties ||= Array.new()
        @@my_properties << sym.to_s
        eval "@#{sym.id2name} = \"#{val}\""
        # p @k1 # << this shows "K1.B" or "K1.C" as expected
      }
    end
  end

but these tests showed me that I was wrong

  class A
    def test
      p @@my_properties
    end
  end

  class B < A
    my_property :k1, "K1.B" # k1 is a string fixed to "K1"
    my_property :k2, "K2.B"
    def test
      super # should be ['k1', 'k2']
      p @k1 # should be "K1"
    end
  end
  
  class C < A
    my_property :k1, "K1.C"
    def test
      super # should be ['k1']
      p @k1 # should be "K1.C"
    end
  end

A.new.test (should print nil or a warning) => ["k1", "k2", "k1"]

B.new.test (should print ['k1', 'k2'] 'K1.B') => ["k1", "k2", "k1"]
./at.rb:28: warning: instance variable @k1 not initialized
nil

A.new.test (should print nil or a warning) => ["k1", "k2", "k1"]

C.new.test (should print ['k1'] 'K1.C') => ["k1", "k2", "k1"]
./at.rb:36: warning: instance variable @k1 not initialized
nil

Any suggestion or idea?

···

--
Gioele <dev@gioelebarabucci.com>

Check this tutorial/game/artwork out:
http://poignantguide.net/dwemthy/

By the end, you will definitely know how to create methods that behave
like attr_accessor.

···

On 1/21/06, Gioele Barabucci <ml@gioelebarabucci.com> wrote:

How can I create methods like "attr:" that can create methods and instance
variables?

I'd like to do this

  class B < A
    my_property :k, "K"
    def test
      p @k # should print "K"
      p @@my_properties # should print ['k']
    end
  end

I tried with this (non-) solution

  class A
    def A.my_property(sym, val)
      self.instance_eval {
        @@my_properties ||= Array.new()
        @@my_properties << sym.to_s
        eval "@#{sym.id2name} = \"#{val}\""
        # p @k1 # << this shows "K1.B" or "K1.C" as expected
      }
    end
  end

but these tests showed me that I was wrong

  class A
    def test
      p @@my_properties
    end
  end

  class B < A
    my_property :k1, "K1.B" # k1 is a string fixed to "K1"
    my_property :k2, "K2.B"
    def test
      super # should be ['k1', 'k2']
      p @k1 # should be "K1"
    end
  end

  class C < A
    my_property :k1, "K1.C"
    def test
      super # should be ['k1']
      p @k1 # should be "K1.C"
    end
  end

A.new.test (should print nil or a warning) => ["k1", "k2", "k1"]

B.new.test (should print ['k1', 'k2'] 'K1.B') => ["k1", "k2", "k1"]
./at.rb:28: warning: instance variable @k1 not initialized
nil

A.new.test (should print nil or a warning) => ["k1", "k2", "k1"]

C.new.test (should print ['k1'] 'K1.C') => ["k1", "k2", "k1"]
./at.rb:36: warning: instance variable @k1 not initialized
nil

Any suggestion or idea?

--
Gioele <dev@gioelebarabucci.com>

Here's a quick hack. There are probably better ways.

def self.var_with_default(variable, default)
  @@current_default = default
  class_eval <<-END_BLOCK
    @@default_#{variable.to_s} = @@current_default
    attr_writer #{variable.to_s}
    def #{variable.to_s}
      @#{variable.to_s} || @@default_#{variable.to_s}
    end
  END_BLOCK
end

Include that in the class it will be used in (or an ancestor). This
method is not thread safe.

Gioele Barabucci wrote:

How can I create methods like "attr:" that can create methods and
instance variables?

I'd like to do this

  class B < A
    my_property :k, "K"
    def test
      p @k # should print "K"
      p @@my_properties # should print ['k']
    end
  end

I tried with this (non-) solution

  class A
    def A.my_property(sym, val)
      self.instance_eval {
        @@my_properties ||= Array.new()
        @@my_properties << sym.to_s
        eval "@#{sym.id2name} = \"#{val}\""
        # p @k1 # << this shows "K1.B" or "K1.C" as expected
      }
    end
  end

but these tests showed me that I was wrong

  class A
    def test
      p @@my_properties
    end
  end

  class B < A
    my_property :k1, "K1.B" # k1 is a string fixed to "K1"
    my_property :k2, "K2.B"
    def test
      super # should be ['k1', 'k2']
      p @k1 # should be "K1"
    end
  end

  class C < A
    my_property :k1, "K1.C"
    def test
      super # should be ['k1']
      p @k1 # should be "K1.C"
    end
  end

A.new.test (should print nil or a warning) => ["k1", "k2", "k1"]

B.new.test (should print ['k1', 'k2'] 'K1.B') => ["k1", "k2", "k1"]
/at.rb:28: warning: instance variable @k1 not initialized
nil

A.new.test (should print nil or a warning) => ["k1", "k2", "k1"]

C.new.test (should print ['k1'] 'K1.C') => ["k1", "k2", "k1"]
/at.rb:36: warning: instance variable @k1 not initialized
nil

Any suggestion or idea?

One way is to define them as instance methods of class Module.

09:36:59 [~]: irb
irb(main):001:0> class Module
irb(main):002:1> def foo(sym)
irb(main):003:2> define_method(sym) {|*a| p a}
irb(main):004:2> end
irb(main):005:1> end
=> nil
irb(main):006:0> class Foo
irb(main):007:1> foo :bar
irb(main):008:1> end
=> #<Proc:0x04aa36d0@(irb):3>
irb(main):009:0> Foo.new.bar 1,2,3
[1, 2, 3]
=> nil

Then you can use them in the complete class hierarchy. If you want only
part of the class hiearchy of classes to be able to use them, you should
define them as instance methods of the starting class's singleton class:

09:38:02 [~]: irb
irb(main):001:0> class Foo
irb(main):002:1> class << self
irb(main):003:2> def foo(sym)
irb(main):004:3> define_method(sym) {|*a| p a}
irb(main):005:3> end
irb(main):006:2> end
irb(main):007:1> end
=> nil
irb(main):008:0> class Bar < Foo
irb(main):009:1> foo :bar
irb(main):010:1> end
=> #<Proc:0x04aa6bc8@(irb):4>
irb(main):011:0> Bar.new.bar

=> nil
irb(main):012:0> Bar.new.bar 1,2,3
[1, 2, 3]
=> nil

Kind regards

    robert