Update Ruby class attributes hash when a property changes
I'm trying to write a Ruby class that works simila开发者_StackOverflow中文版rly 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?
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.
精彩评论