views:

30

answers:

1

Hi,

I'm trying to write a ruby class that works similarly to rails activerecord 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? thanks

EDIT: Added solution

class Person
  my_attr_accessor :name, :age, :attributes

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

  # read attributes
  def attributes
    @attributes
  end

  # update attributes
  def attributes=(options)
    options.each { |key, val| send("#{key}=", val) if respond_to?("#{key}=") }
  end
end

class Class   
 def my_attr_accessor(*accessors) 
   accessors.each do |m| 
     define_method(m)       { @attributes[m] }
     define_method("#{m}=") { |val| @attributes[m]=val }
   end 
 end 
end 
+1  A: 

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.

Mladen Jablanović
sounds cool. would you mind writing some code suggestion as to how I could do all that? thanks
sa125
I illustrated the first method, leaving the other for your practice... ;)
Mladen Jablanović
awesome, that's exactly what I needed.
sa125