tracking dirty attributes

339 views
Skip to first unread message

Peter Ehrlich

unread,
Mar 5, 2012, 9:42:35 PM3/5/12
to spi...@googlegroups.com
Hello!

I've been doing some thinking on Spine Models, and have come to the conclusion that tracking attribute changes could be really cool.  

For example, you load a resource in to spine, then navigate elsewhere, and come back.  But in that time the other resource has changed slightly.  So what you do is show the old version while loading the new version via ajax, and when the new version comes in, update only the correct parts of the page, based off of the differences between the two versions.

It seems like this would be best handled in the active controller hooking to after_update on its current model.  Such a hook would show the changed attributes, and the controller could respond accordingly.

This could be doubly good, because those same hooks could be used during regular page interactions.  For example, if you edit content on a page, the controller simply updates the model, and the controller's callback handles updating the view.  This as a coding practice would be good because it would enforce consistency between the saved state of the model and the sate of the view.

Thoughts?
--Peter

Ian White

unread,
Mar 5, 2012, 11:26:36 PM3/5/12
to spi...@googlegroups.com, spi...@googlegroups.com
Spine handles update events on models, so I think you are talking about an event per field change?

I would store the "current" attributes in your controller and track changes to the model (via model 'update' events), iterating through attributes to check for equality.

It feels right to put it in my own business logic because 1) you usually only care about tracking a few items (eg. the selected one) 2) tracking field changes unnecessarily complicates the framework, and 3) the REST way is to send all of the attributes with an update, so internally it would have to check for equality on each field anyway.

Also, you can update your models independently of views, as many times as you want, and this is a good thing. How would the model know which views have "recognized" the change if you updated something more than once since it was last visible?

Wherever you deal with update/render you should be able to handle this kind of UI sugar, and it would be easier there than tracking version history in the model.

Unless I missed the point :)

Ian

Sent from my iPhone

Peter Ehrlich

unread,
Mar 6, 2012, 12:29:45 AM3/6/12
to spi...@googlegroups.com
see inline..

On Mon, Mar 5, 2012 at 11:26 PM, Ian White <fuzzy...@gmail.com> wrote:
Spine handles update events on models, so I think you are talking about an event per field change?


I would store the "current" attributes in your controller and track changes to the model (via model 'update' events), iterating through attributes to check for equality.

No, no, no no no!  Its very easy to store data in the controller, and the more I see it, the more strongly I become convinced this is a bad idea.  Controllers in Rails are stateless, they only modify request and response objects and pass them along, and for good reason.  Controllers in the front-end should be exactly the same, they just take a click as input and put out an ajax request (usually), or take in an ajax request and update the view.

Data handling is the job of the model.
 

It feels right to put it in my own business logic because 1) you usually only care about tracking a few items (eg. the selected one) 2) tracking field changes unnecessarily complicates the framework, and 3) the REST way is to send all of the attributes with an update, so internally it would have to check for equality on each field anyway. 

Also, you can update your models independently of views, as many times as you want, and this is a good thing. How would the model know which views have "recognized" the change if you updated something more than once since it was last visible?

The model doesn't, and shouldn't know anything about the view.  It just does the updates, and responds either directly or via a hook with the changed attributes.  If the controller doesn't have the model open, then I imagine that it would re-render it in its entirety when becoming visible again, meaning tracking changes would not be necessary.

Mitch

unread,
Mar 6, 2012, 8:54:50 PM3/6/12
to spi...@googlegroups.com
Ian, 

It's an interesting idea to track the changes in the controllers and I can see the advantages.  On the other hand, just hooking up to something like [@model.bind 'update[name]', @renderName] seems like it would be nicer than performing checks in render.  If I start duplicating the same code pattern of storing the old attributes in many controllers I bet I going to wish the logic was in Spine.Model.  I'll try it your way first, since it would be simple to implement and then I can see how often I really need to do it before I do the work of putting it into the model.  Have you actually implemented this?  If so do you mind sharing a snippet?

Peter, 

I've spent a lot of time with Rails and I struggled when I started using Spine because I was thinking of the controllers as stateless Rails controllers.  Once I realized that it's OK to keep view state in the controllers things got a lot easier.  For instance when something is "selected" for the current user, that state belongs in the controller and not in the model.  This example of comparing attributes though is probably more of a grey area. 

Peter Ehrlich

unread,
Mar 6, 2012, 9:05:24 PM3/6/12
to spi...@googlegroups.com
Hey Mitch

I'd be interested to hear more about how you use controllers in spine.  

I'm having a great time keeping everything stateless.  My controllers are able to do quite complex things while being almost entirely devoid of jquery selectors.  I've got a nice mixin that allows events to be fired easily by specifying data- attributes (such as data-click, data-submit, etc), and a second mixin called a widget which essentially allows a JST template to be tied to a place in the DOM, allowing easy re-rendering with new-data.  

Thus the only data held in my controller is a reference to the model currently displayed, accessible under a function @resource().

Would be love to see what you come up with here!  And (although I can't speak for Ian) I haven't started anything, but will share if I do.

--Peter

Mitch

unread,
Mar 8, 2012, 8:02:12 PM3/8/12
to spi...@googlegroups.com
Sounds like you're having a good time taking Spine in sort of a Knockout.js, data-binding direction for handling events.

Regarding state, checkout something like Spine list for an example (https://github.com/maccman/spine/blob/master/src/list.coffee).  In this case the controller stores what the current, selected item in the list is.  I'm finding lots of situations where I'm storing state like this to keep track of what the user is doing.  I do agree though that sharing a second copy of the model in the controller gives pause. 

I'll try to remember to report back about the change tracking.  However, as much as I love Spine, sometimes I wonder if I'd save time in the long run by moving to backbone.js.  There are a lot of things I prefer about Spine, but clearly backbone.js is getting beat on by a lot more users and real-world applications.

Peter Ehrlich

unread,
Mar 9, 2012, 6:06:20 PM3/9/12
to spi...@googlegroups.com
Hm, thanks for reminding me about knockout.  I remember it being suggested to me when I was first investigating front-end frameworks, but I didn't spend enough time with it to get a feeling.  There are a lot of similarities there, and I really like how the page drives the interactions, rather than the controller defining the flow.  (by having events declared there.)   I don't like how knockout has its own dsl to perform complex actions-- that looks like it would serve to just make the whole application more brittle and less dry.

There are cool opportunities that come up when controllers are made simple and more internally consistent.  For example, I have the following filters: before, after, and after_ajax.  If a controller returns a deferred object, then the after_ajax callback can be used.  A further idea could be to just expect that when a controller returns an options hash, it should automatically be used to form an ajax request.  There could be conventions even on from there-- perhaps a default endpoint of /modelClass/controllerActionName! (Remember, model class the model is reliably available here thanks to @resource())


I tried backbone before spine, and ended up putting in a lot of effort to try and DRY things up.  After a few days I realized that I was going through a whole lot of work to try and turn backbone in to spine, and switched.

As a side note, has anyone tried setting up spine without hem?  It seems a like a lot of set up for potentially small commits.

Cheers,
--Peter

Mitch

unread,
Mar 11, 2012, 11:49:15 AM3/11/12
to spi...@googlegroups.com
Here's what I came up with.  I'm not sure if this will work out-of-the box for normal use cases.  My models always have an ID assigned, so I don't really mess with CIDs.

Also this doesn't really cover Peter's original use case.  All it does is trigger events like "update:name" which is what I needed right now. 

Peter Ehrlich

unread,
Mar 11, 2012, 2:13:45 PM3/11/12
to spi...@googlegroups.com
Changes are only tracked upon persistence, and I think it is understood that when a model is saved it is given an id.  (Does spine even support alternative primary keys? Does anyone still do that?)  Also, spine gives an id automatically upon save: http://spinejs.com/docs/models

When a record is saved, Spine automatically creates an ID if it doesn't already exist.


assertEqual( contact.id, "AD9408B3-1229-4150-A6CC-B507DFDF8E90" )

It looks like both giving the change data to the callback and firing a universal 'changed' event would be easy to add.  I notice you've given it its own repo-- would you accept those as pull requests?

One thought-- if a general event is fired on change (giving all the changes), it would have to be 'changed' as updated is already used.  And so it stands that the update:attr event should become changed:attr.

Cheers,
--Peter

Mitch

unread,
Mar 11, 2012, 4:35:39 PM3/11/12
to spi...@googlegroups.com
Yeah, I put it in it's own repo to encourage people to look at it and make suggestions + pull requests. Anything is welcome, but I might need to put a couple quick tests together to make sure that is keeps working for me. 

I think you're right that the ID thing shouldn't be a problem as it stands now.  The baseline of old attributes is set on the 'refresh' and 'create' and Spine assigns an ID before triggering these. 


On Sunday, March 11, 2012 2:13:45 PM UTC-4, Peter Ehrlich wrote:
Changes are only tracked upon persistence, and I think it is understood that when a model is saved it is given an id.  (Does spine even support alternative primary keys? Does anyone still do that?)  Also, spine gives an id automatically upon save: http://spinejs.com/docs/models

When a record is saved, Spine automatically creates an ID if it doesn't already exist.

assertEqual( contact.id, "AD9408B3-1229-4150-A6CC-B507DFDF8E90" )

It looks like both giving the change data to the callback and firing a universal 'changed' event would be easy to add.  I notice you've given it its own repo-- would you accept those as pull requests?

One thought-- if a general event is fired on change (giving all the changes), it would have to be 'changed' as updated is already used.  And so it stands that the update:attr event should become changed:attr.

Cheers,
--Peter

Naveen Michaud-Agrawal

unread,
Mar 27, 2012, 2:56:52 PM3/27/12
to spi...@googlegroups.com
Hi Peter,

Would you be willing to share your controller filters? I'm trying to play around with relations and I'm having trouble getting hasOne to work properly when my REST api returns a key for the contained object instead of the entire thing, and it would be good to see how other people handle similar things. Thanks.

Naveen

Peter Ehrlich

unread,
Mar 27, 2012, 3:58:16 PM3/27/12
to spi...@googlegroups.com
Hi

I don't actually use the spine relationship tools, not having a good way to address that very problem.  It is somewhat of a big issue when considering that not only are we dealing with ID vs Everything, but there can be various levels of detail in API responses: ID, small amount of data, medium info, and everything.  I think its good practice not to try and recreate database structures client-side, and think I may have seen something to that effect in the docs as well.

I have published an alpha of the Auto Actions library, here.  I apologize, it is a bit monolithic at the moment:

Quick list of features:
 - Easy Events
 - Event Target 
 - Form Data 
 - Callbacks
 - Transparent Ajax
 - Method Arguments

Peter Ehrlich

unread,
Apr 7, 2012, 12:32:37 PM4/7/12
to spi...@googlegroups.com
Finally getting to take a look at this!


I've done some refactoring to bring in the methods @was and @changes, change the event to 'change' and have a universal event fired, and attributes are now tracked in the model itself.  I've run in to a road block though, in that the duplication of Spine models does not include anything except the attributes themselves.

Now this confuses me a lot.  I remember reading about duplication as it was mentioned in the docs, but am having trouble understanding why this is a good feature to have.  What are some use cases?

Rails database adapters go through a lot of work to provide identity maps, which save time, memory, and most importantly consistency.  They ensure there can only be one instance of a resource in memory at a time.  Spine goes through a lot of work to reverse this, and I don't have any uses in my (non-trivial) app.


I'm starting to think about making a fork (Tibia?) to further some rails conventions for spine, and make it more accessible to rails developers.  Changes could include:

Removing the need for hem (allowing sprockets to be used instead).

Having controllers be used on a global scope, not for data persistence.

Tracking dirty attributes

Changing @url to be @path, allowing @path to take an include_host option, and making a new @url method to use it.  This is worth debating about, but the bottom line is that the @url method returns '/resource/id/action', and if you look up URL in wikipedia, this is clearly only half the answer.

De-emphasizing spine ajax in favor of something more controllable.

Various view-layer improvements, including auto_actions (data-binding events to controller actions), and widgets (sections of the DOM which are tied either dynamically or automatically to resources.) (already built)

Moving the router to a single separate file, provide before and after routing hooks. (already built)

Maybe a place for after_render callbacks

Form builders and better form serialization.  Data-fields_for, used to scope a set of fields, makes the syntax user[name] obsolete, allowing easier JS manipulation.  Form builders set automatically name, and value.  (already built)

Making named elements obsolete.  I've found that having a second place where the name of an element can change creates more work, not less.  There are some other ways to make element selection less of a need.

Perhaps most controversially, I've been using underscored method names, and having my entire app under the one convention.  This is surprisingly easy to get used to, but could be an issue.

I find the strong points of Spine to be its class structure, stack/manager, and simplicity, and want to be able to be able to enjoy and expand upon those.

Thoughts?

Cheers,
--Peter

Mitch

unread,
Apr 7, 2012, 10:30:37 PM4/7/12
to spi...@googlegroups.com
@was, @changes, and the 'changed' event look good, but I'm wondering what the use cases are.  I'm sure there are plenty out there, but so far I haven't run into any yet in my application. 

I've read a little about the design decision that Alex made in returning new instances after save(), update(), find(), etc...  (see https://github.com/maccman/spine/issues/10).  It seems he wanted to keep a canonical version of models and only update them with update(), save(), etc. I've often wondered if this is copying patterns from database ORMs without a good reason.  Obviously there are some gains here.  For instance, validation works well with this pattern, and there is a guarantee that nothing is ever changed without an 'update' event being fired.  However, the downside is what you discovered: you cannot store any state outside of the persisted attributes on model instances. That is why I wrote the AttributeTracking library with a class variable storing the old attributes instead of what you've tried in your gist.

Ultimately it's hard to know whether this design decision is a good one without trying both ways, which I haven't done. I trust that Alex made this decision for a good reason, but I haven't seen the argument flushed out all the way.  Alex, if you're reading I'd love to hear more about this design decision.

If you make a fork, surely you should continue the tradition of using a name that is nearly synonymous with "Backbone" or at least closely related :)  How about Vertebra or Sacrum?

And just a few other comments on some of your points:
- Are you sure you need Hem to use Spine with Rails?  Seems like it would work with Sprockets. I don't use Hem.  I use Browserify and it's been working well.
- I agree with you on @url.  
- "De-emphasizing Ajax".  I don't use the Ajax library and haven't had any problems as a result.  My favorite part of spine is how easy it is to hook into models using the events.
- The data-binding debate interests me to no end. I'm using it in my app to a limited extent.  My impression is that Alex and Jeremy Ashkenas are both not big fans of data-binding.  If you're really into data-binding make sure you take a look at ember.js, a framework built by Yehuda Katz who is a big Rails user / contributor.  
Reply all
Reply to author
Forward
0 new messages