14

i want to do the following:

I want to declare the instance variables of a class iterating over a dictionary.

Let's assume that i have this hash

hash = {"key1" => "value1","key2" => "value2","key3" => "value3"}

and i want to have each key as instance variable of a class. I want to know if i could declare the variables iterating over that hash. Something like this:

class MyClass
  def initialize()
    hash = {"key1" => "value1","key2" => "value2","key3" => "value3"}
    hash.each do |k,v|
      @k = v
    end
  end
end

I know this doesn't work! I only put this piece of code to see if you could understand what i want more clearly.

Thanks!

flyer88
  • 1,073
  • 3
  • 15
  • 33

4 Answers4

28
class MyClass
  def initialize()
    hash = {"key1" => "value1","key2" => "value2","key3" => "value3"}
    hash.each do |k,v|
      instance_variable_set("@#{k}",v)
      # if you want accessors:
      eigenclass = class<<self; self; end
      eigenclass.class_eval do
        attr_accessor k
      end
    end
  end
end

The eigenclass is a special class belonging just to a single object, so methods defined there will be instance methods of that object but not belong to other instances of the object's normal class.

Chuck
  • 234,037
  • 30
  • 302
  • 389
4

Chuck's answer is better than my last two attempts. The eigenclass is not self.class like I had thought; it took a better test than I had written to realize this.

Using my old code, I tested in the following manner and found that the class was indeed manipulated and not the instance:

a = MyClass.new :my_attr => 3
b = MyClass.new :my_other_attr => 4

puts "Common methods between a & b:"
c = (a.public_methods | b.public_methods).select { |v| a.respond_to?(v) && b.respond_to?(v) && !Object.respond_to?(v) }
c.each { |v| puts "    #{v}" }

The output was:

Common methods between a & b:
    my_other_attr=
    my_attr
    my_attr=
    my_other_attr

This clearly disproves my presupposition. My apologies Chuck, you were right all along.

Older answer:

attr_accessor only works when evaluated in a class definition, not the initialization of an instance. Therefore, the only method to directly do what you want is to use instance_eval with a string:

class MyClass
  def initialize(params)
    #hash = {"key1" => "value1","key2" => "value2","key3" => "value3"}
    params.each do |k,v|
      instance_variable_set("@#{k}", v)
      instance_eval %{
        def #{k}
          instance_variable_get("@#{k}")
        end
        def #{k}= (new_val)
          instance_variable_set("@#{k}", new_val)
        end
      }
    end
  end
end

To test this try:

c = MyClass.new :my_var => 1
puts c.my_var
Robert K
  • 30,064
  • 12
  • 61
  • 79
  • 1
    `self.class` is **not** the eigenclass. Modifying `self.class` will affect every object belonging to the class, while modifying an object's eigenclass will only affect that object. They're interchangeable in some cases, but not generally. – Chuck Oct 25 '09 at 07:01
  • As of 1.8.7, I tested that and found your assertion to be false. Nonetheless, that is the only version I tested. – Robert K Oct 26 '09 at 00:33
  • Then your test was flawed. Run this: `"Hello".class.class_eval {def size() 9000 end}; puts "Bye".size` and you will see that overriding the method in "Hello".class also affected the behavior of the completely unrelated string "Bye". This is true in any version of Ruby you're likely to encounter. – Chuck Oct 27 '09 at 00:18
  • I apologize Chuck, better tests show that you are correct. But that doesn't seem to make any sense, so I'll have to ask a question about it. – Robert K Oct 27 '09 at 13:22
4
class MyClass
  def initialize
    # define a hash and then
    hash.each do |k,v|
      # attr_accessor k # optional
      instance_variable_set(:"@#{k}", v)
    end
  end
end
Eimantas
  • 48,927
  • 17
  • 132
  • 168
  • thanks for that optional remark! but the attr_accessor in the hash iterator BLOCK doesnt work me... How can i do that? (create attr_accessors iterating) – flyer88 Oct 23 '09 at 19:07
  • In order to do an `attr_accessor`, you need to be in a class context. The way to get a class context for an instance is to use the object's eigenclass like in my answer. – Chuck Oct 23 '09 at 20:27
2

http://facets.rubyforge.org/apidoc/api/more/classes/OpenStructable.html

OpensStructable is a mixin module which can provide OpenStruct behavior to any class or object. OpenStructable allows extention of data objects with arbitrary attributes.

ykaganovich
  • 14,736
  • 8
  • 59
  • 96