Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Update Ruby class attributes hash when a property changes

I'm trying to write a Ruby class that works similarly to Rails AactiveRecord model in the way that attributes are handled:

class Person
  attr_accessor :name, :age

  # init with Person.new(:name => 'John', :age => 30)
  def initialize(attributes={})
    attributes.each { |key, val| send("#{key}=", val) if respond_to?("#{key}=") }
    @attributes = attributes
  end

  # read attributes
  def attributes
    @attributes
  end

  # update attributes
  def attributes=(attributes)
    attributes.each do |key, val| 
      if respond_to?("#{key}=")
        send("#{key}=", val) 
        @attributes[key] = name
      end
    end
  end
end

What I mean is that when I init the class, an "attributes" hash is updated with the relevant attributes:

>>> p = Person.new(:name => 'John', :age => 30)
>>> p.attributes
 => {:age=>30, :name=>"John"}
>>> p.attributes = { :name => 'charles' }
>>> p.attributes
 => {:age=>30, :name=>"charles"}

So far so good. What I want to happen is for the attributes hash to update when I set an individual property:

>>> p.attributes
 => {:age=>30, :name=>"John"}
>>> p.name
 => "John"
>>> p.name = 'charles' # <--- update an individual property
 => "charles"
>>> p.attributes
 => {:age=>30, :name=>"John"} # <--- should be {:age=>30, :name=>"charles"}

I could do that by writing a setter and getter for every attribute instead of using attr_accessor, but that'll suck for a model that has a lot of fields. Any quick way to accomplish this?

like image 542
sa125 Avatar asked Jul 15 '10 14:07

sa125


1 Answers

The problem is that you keep your attributes both as separate ivars, and within a @attributes hash. You should choose and use only one way.

If you want to use a hash, you should make your own way of creating accessors, which would "reroute" them to a single method which would set and get from a hash:

class Class  
 def my_attr_accessor(*accessors)
   accessors.each do |m|

     define_method(m) do  
       @attributes[m]
     end        

     define_method("#{m}=") do |val| 
       @attributes[m]=val
     end
   end
 end
end

class Foo
  my_attr_accessor :foo, :bar

  def initialize
    @attributes = {}
  end
end

foo = Foo.new

foo.foo = 123
foo.bar = 'qwe'
p foo
#=> #<Foo:0x1f855c @attributes={:foo=>123, :bar=>"qwe"}>

If you want to use ivars, you should, again, roll your own attr_accessor method which would, in addition, remember which ivars should be "attributes", and use that list in attributes method. And attributes method would create a hash out of them on-the-fly, and return it.

Here you can find a nice article about implementing accessors.

like image 118
Mladen Jablanović Avatar answered Oct 05 '22 20:10

Mladen Jablanović