Jeremy B.
Jeremy B.

Reputation: 9216

Ruby create methods from a hash

I have the following code I am using to turn a hash collection into methods on my classes (somewhat like active record). The problem I am having is that my setter is not working. I am still quite new to Ruby and believe I've gotten myself turned around a bit.

class TheClass
  def initialize
    @properties = {"my hash"}
    self.extend @properties.to_methods
  end
end

class Hash
  def to_methods
    hash = self
    Module.new do
      hash.each_pair do |key, value|
        define_method key do
          value
        end
        define_method("#{key}=") do |val|
          instance_variable_set("@#{key}", val)
        end
      end
    end
  end
end

The methods are created and I can read them on my class but setting them does not work.

myClass = TheClass.new
item = myClass.property # will work.
myClass.property = item # this is what is currently not working.

Upvotes: 3

Views: 4823

Answers (4)

Jonas Elfström
Jonas Elfström

Reputation: 31428

If your goal is to set dynamic properties then you could use OpenStruct.

require 'ostruct'

person = OpenStruct.new
person.name = "Jennifer Tilly"
person.age = 52

puts person.name     
# => "Jennifer Tilly"
puts person.phone_number 
# => nil

It even has built-in support to create them from a hash

hash = { :name => "Earth", :population => 6_902_312_042 }
planet = OpenStruct.new(hash)

Upvotes: 8

Jörg W Mittag
Jörg W Mittag

Reputation: 369458

It works just fine for me (after fixing the obvious syntax errors in your code, of course):

myClass.instance_variable_get(:@property) # => nil
myClass.property = 42
myClass.instance_variable_get(:@property) # => 42

Note that in Ruby instance variables are always private and you never define a getter for them, so you cannot actually look at them from the outside (other than via reflection), but that doesn't mean that your code doesn't work, it only means that you cannot see that it works.

Upvotes: 2

Jeff Swensen
Jeff Swensen

Reputation: 3573

This is essentially what I was suggesting with method_missing. I'm not familiar enough with either route to say why or why not to use it which is why I asked above. Essentially this will auto-generate properties for you:

def method_missing sym, *args
   name = sym.to_s
   aname = name.sub("=","")

   self.class.module_eval do 
      attr_accessor aname
   end
  send name, args.first unless aname == name
end

Upvotes: 0

Gareth McCaughan
Gareth McCaughan

Reputation: 19971

Your getter method always returns the value in the original hash. Setting the instance variable won't change that; you need to make the getter refer to the instance variable. Something like:

hash.each_pair do |key, value|
  define_method key do
    instance_variable_get("@#{key}")
  end
  # ... define the setter as before
end

And you also need to set the instance variables at the start, say by putting

@properties.each_pair do |key,val|
  instance_variable_set("@#{key}",val)
end

in the initialize method.

Note: I do not guarantee that this is the best way to do it; I am not a Ruby expert. But it does work.

Upvotes: 4

Related Questions