reform icon indicating copy to clipboard operation
reform copied to clipboard

Reform assigns nested properties in twins in a wrong way

Open tilod opened this issue 8 years ago • 0 comments

Reform version: 2.2.1

Given: I have a very complex data structure which I compose into a Twin with nested properties. The Twin then serves as model for a Form:

require 'disposable'
require 'reform'
require 'rails'
require 'reform/rails'
require 'reform/form/active_model/validations'
Reform::Form.class_eval do
  include Reform::Form::ActiveModel::Validations
end

User    = Struct.new(:email, :password, :profile) { def save; end }
Profile = Struct.new(:name) { def save; end }

class ProjectTwin < Disposable::Twin
  feature Save
  feature Sync

  property :email
  property :password
  property :profile do
    property :name
  end
end

class ProjectForm < Reform::Form
  property :email
  property :password
  property :profile do
    property :name
  end
end

user   = User.new('[email protected]', '123456', Profile.new('Test Name'))
twin   = ProjectTwin.new(user)
form   = ProjectForm.new(twin)
params = { email: '[email protected]', profile: { name: 'New Name' } }

form.validate(params)

What I expect: On form.save (or form.sync and then twin.sync), the form writes the values from the params hash to the twin and the twin then writes the values to the nested objects without reassigning the nested properties.

What actually happens: Reform assigns the nested Twin (i.e. an instance of the anonymous class it generates to wrap the nested model) to the model:

# before save:
user.profile.class
# => Profile   ... and should stay that way

# now we save:
form.save

# what happened to the nested object?
user.profile.class
# => #<Class:0x007fb403211358>

# ...which is the anonymous class generated by the twin to wrap the nested profile model:
twin.profile.class
# => #<Class:0x007fb403211358>

Additional info: If you don't use a form but assign the values to the twin manually, twin.sync will work as expected:

user = User.new('[email protected]', '123456', Profile.new('Test Name'))
twin = ProjectTwin.new(user)
twin.email = '[email protected]'
twin.profile.name = 'New Name'
twin.sync
user.profile.class
# => Profile

So the problematic behavior is in Reform.

tilod avatar Sep 21 '16 12:09 tilod