Background
The Entity class is a base class that is inherited by several subclasses that contain objects obtained by the REST API. Entity classes are immutable and must return a new instance of themselves whenever an attempt to change occurs.
The Entity class has a method .update()that accepts a hash of values for updating, if the changes do not actually change, it returns itself and, if there are real changes, it returns a new instance of itself with the changes made before the specification.
To be user friendly, Entity also allows direct assignment to properties (so if a subclass of Entity has a name attribute, you can do instance.name = 'New Name'), which also returns a new instance of the class. This is implemented in terms of updating using dynamic methods that are created when the class is instantiated.
And that is the problem.
Problem
The code in the Entity class looks, in part, like this (for a complete listing of the code and tests, check out the Github repository: https://github.com/my-codeworks/fortnox-api.git ):
require "virtus"
require "ice_nine"
class Entity
extend Forwardable
include Virtus.model
def initialize( hash = {} )
super
create_attribute_setter_methods
IceNine.deep_freeze( self )
end
def update( hash )
attributes = self.to_hash.merge( hash )
return self if attributes == self.to_hash
self.class.new( attributes )
end
private
def create_attribute_setter_methods
attribute_set.each do |attribute|
name = attribute.options[ :name ]
create_attribute_setter_method( name )
end
end
def create_attribute_setter_method( name )
self.define_singleton_method "#{name}=" do | value |
self.update( name => value )
end
end
end
Performing this action:
instance.update( name: 'New Name' )
and this:
instance.name = 'New Name'
It must be the same thing, literally, since one is implemented in terms of the other.
While it .update()works fine, the methods .attr=()return the value that you assign.
, .update() Entity, .attr=() 'New Name'...
.attr=() , :
self.define_singleton_method "#{name}=" do | value |
p "Called as :#{name}=, redirecting to update( #{name}: #{value} )"
r = self.update( name => value )
p "Got #{r} back from update"
return r
end
:
"Called as :name=, redirecting to update( name: 'New Name' )"
"Got
, , 'New Name'...
, , , . , - , .
Github rspec, , , Entity .
, / .