Skip to content
This repository was archived by the owner on Oct 19, 2018. It is now read-only.

changing record attributes is causing too many renders #47

Open
catmando opened this issue Jan 13, 2018 · 8 comments
Open

changing record attributes is causing too many renders #47

catmando opened this issue Jan 13, 2018 · 8 comments

Comments

@catmando
Copy link
Contributor

First off we need some examples in order to discuss the problem.

The basic thought is that there are many cases where one component updates a record's attribute, but other component's are reading that attribute, and so even before the record is saved the other component's are constantly re-rerendering (say as a user types a new value.)

Current work around is to keep local copies of attribute, and only transfer the value before saving.

This is not only cumbersome, but is problematic anyway because the save may fail!

Two solutions have presented themselves.

  1. Let every attribute have a "shadow" attribute that only changes when a record is successfully saved. So components can either use the dynamic value or the shadow value.

@catmando believes that this can work well, but that the "shadow" attributes should be read only. Semantics in this case would be very easy to understand. @janbiedermann is not convinced and feels the shadow attributes need to be writable... need examples to work this out.

The other small issue with this is how to access the shadow attribute. Appending "_" the end of the attribute name would be a way, but seems kind of ugly, but short and sweet.

  1. Be able create a "linked copy" of the record. Saving the linked copy will update the "original" once the save is successful.

The advantage here is that the component manipulating the record can be ignorant of whether its dealing with the "master" or the "linked" copy. Whoever provides the record can make that decision.

So if I am passing a record to a component (for example) I can pass it like this: some_record.linked_copy (or something more fun like some_record.avatar) Now changes to the avatar will have no effect until the avatar is saved.

As @mareczek points out you can now do fun things like .revert which will just revert the avatar. You would not want this with the "shadow attribute" because who knows who is using the shadow?

Of course avatar's can be chained, and doing a revert, reverts to the state of the "original" which created this avatar (not all the way back to the real original.)

Other ideas?

@mareczek
Copy link

Methods I see useful for solution no 2.:
must have
.linked_copies - all children (not descendants) of copied_from
.linked_copy - returns a new linked copy
link_changes - returns a Hash, where keys are attr_name and values of array (first element is the old value, second element the new value)
copied_from - returns object from witch it was copied

nice to have
.revert - reverts to linked object
is_linked_copy? - returns boolean accordingly

@catmando
Copy link
Contributor Author

catmando commented Jan 15, 2018

@mareczek - linked_copies could be problematic. I would hope to implement .2 such that once objects are no longer referenced they can be garbage collected.

The linked_copies method would prevent that.

What would be the use case of needing to know linked_copies? perhaps there is a solution...

@mareczek
Copy link

@catmando after thinking about it, it's not a must have. Alternatively I can always keep a separate array of linked_copies.

@catmando
Copy link
Contributor Author

Well sure, but why? Perhaps we can figure out a more built-in solution if we know what the purpose is

@mareczek
Copy link

The thing is I don't have a real-life case at the moment. I was thinking about possibilities and one of the appealing possibilities was a functionality that can track changes of a object in time (but not with the backend - paper trail). A good comparison would be when you play a game and you "make a save" before a risky mission ;-)

@catmando
Copy link
Contributor Author

so in that case you actually would want to start at the last copy, and move backwards right?

But I would hate to see the functionality overloaded for this purpose. Feels wrong.

@catmando
Copy link
Contributor Author

So here is a proposal... but I am going to follow it with something completely different :-)

We add an avatar capability to AR models on the client.

An avatar is a copy of an AR record.
You can have avatars of avatars, and a record can have multiple avatars.
When an attribute on a record (or an avatar of a record) changes the attributes of all descendants are updated recursively, but not the ancestors.
However, when an avatar is saved (i.e. persisted to the database), the attributes of the root record (and so all descendants) will now be updated with the avatars values.

In otherwords, each avatar knows and watches its parent's state, but the parent is not affected (until a validated save occurs) by the child's state changes.

methods:

ActiveRecord::Base
  .avatar  # returns a new avatar of this record.
  
  .avatar_of # returns either nil (if not an avatar, or the instance I was copied from)
  .avatar? # alias for copied_from
  .revert  # revert to the state persisted to the db
  .sync  # any state changes of the avatar will be overwritten with changes from the base
             # in otherwords replace the current state of all attributes with the parents attribute values

  .save  # because save persists to the DB, and because the DB is synced all avatars will now be at the same state after the save
  .update # same as save

@catmando
Copy link
Contributor Author

So instead of all this, how about using a just using a decorator. (see https://github.com/drapergem/draper as well)

Avatar[some_record]

returns a nice new Avatar of some_record.

The end.

This whole thing can be done up in a separate gem.

Likewise you can easily build a way to scroll up and down a set of local changes if that is what you need. Can't think of a name for that gem.

I think what we need is a standard way of doing this so that you can do like this in params:

class MyComponent < Hyperloop::Component
  param :address, decorator: Avatar # means if address is not already an Avatar then apply Avatar.new(address) first.
  param :address, decorator: -> (p) { test and decorate the param and return it }
  param :address { |p| or just attach a block as your decorator }
...
end

LOVELY

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants