An assignment method created using the define_singleton_method method returns an invalid value

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 #<TestEntity:0x007ffedbd0ad18> back from update"

, , 'New Name'...

, , , . , - , .

Github rspec, , , Entity .

, / .

+4
1

, = .

o = Struct.new(:key).new(1)
o.define_singleton_method("something") { @something }
o.define_singleton_method("something=") do |v|
  @something = v
  return 6
end

, "" 6 , something=. , :

o.something = 1 #=> outputs 1, not 6
o.something #=> outputs 1, so the method did indeed run

? , = , . ; :

new_val = o.something = some_val
+4

Source: https://habr.com/ru/post/1584001/


All Articles