views:

453

answers:

5

Hi, 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!

+5  A: 
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
Chuck, you forgot the @-sign prefix...
The Wicked Flea
And now I didn't.
Chuck
+4  A: 

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
The Wicked Flea
`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
As of 1.8.7, I tested that and found your assertion to be false. Nonetheless, that is the only version I tested.
The Wicked Flea
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
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.
The Wicked Flea
+3  A: 
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
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)
juanmaflyer
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
A: 

Thanks a lot guys, that work for me. Now good be nice to set attr_accessors in the hash iterator block too... The Eimantas code examples doesn't work for me... Any other way?

juanmaflyer
Don't comment in answers, that's what comments are for
ykaganovich
In this case, though, it should just be added to the question rather than being an answer or a comment.
Chuck
+2  A: 

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