danielspaniel / ember-data-change-tracker

extending ember data to track and rollback changes including objects and associations
MIT License
107 stars 44 forks source link
dirty-tracking ember-data rollback

ember-data-change-tracker

Build Status Ember Observer Score npm version

New

This addon aims to fill in the gaps in the change tracking / rollback that ember data does now.

Installation

Why?

Say there is a user model like this:

  export default Model.extend({
       name: attr('string'),  // ember-data tracks this already
       info: attr('object'),  // ember-data does not track modifications
       json: attr(),          // ember-data does not track modifications if this is object
       company: belongsTo('company', { async: false, polymorphic: true }),  // ember-data does not track replacement
       profile: belongsTo('profile', { async: true }), // ember-data does not track replacement
       projects: hasMany('project', { async: false }), // ember-data does not track additions/deletions
       pets: hasMany('pet', { async: true, polymorphic: true }) // ember-data does not track additions/deletions
   });

You can not currently rollback the info, json if they are modified or company, profile, projects and pets if they change.

model changes

Example: ( remove from a hasMany )

  user.get('projects').removeObject(firstProject); // remove project1
  user.modelChanges() //=> {projects: true }

Rollback

Usage:

    let info = {foo: 1};
    let projects = makeList('project', 2);
    let [project1] = projects;
    let pets = makeList('cat', 4);
    let [cat, cat2] = pets;
    let bigCompany = make('big-company');
    let smallCompany = make('small-company');

    let user = make('user', { profile: profile1, company: bigCompany, pets, projects });

    // manual tracking model means you have to explicitly call => startTrack
    // to save the current state of things before you edit
    user.startTrack();   

    // edit things  
    user.setProperties({
      'info.foo': 3,
      company: smallCompany,
      profile: profile2,
      projects: [project1],
      pets: [cat1, cat2]
    });

    user.rollback();

    // it's all back to the way it was
    user.get('info') //=> {foo: 1}
    user.get('profile') //=> profile1
    user.get('company') //=> bigCompany
    user.get('projects') //=> first 2 projects
    user.get('pets') //=> back to the same 4 pets

isDirty, hasDirtyRelations

Usage:


    let info = {foo: 1};
    let pets = makeList('cat', 4);
    let [cat, cat2] = pets;
    let bigCompany = make('big-company');
    let smallCompany = make('small-company');

    let user = make('user', { company: bigCompany, pets });

    user.startTrack();   

    // edit things  
    user.set('name', "new name");
    user.get('isDirty'); //=> true

    user.rollback();
    user.get('isDirty'); //=> false

    user.set('company', smallCompany);
    user.get('hasDirtyRelations'); //=> true
    user.get('isDirty'); //=> true

    user.rollback();
    user.get('isDirty'); //=> false

    user.set('pets', [cat, cat2]);
    user.get('hasDirtyRelations'); //=> true
    user.get('isDirty'); //=> true

    user.rollback();
    user.get('isDirty'); //=> false

    // things that don't work      
    user.set('info.foo', 3); 
    user.get('isDirty'); //=> false ( object/array attributes don't work for computed isDirty )

Configuration

  // file config/environment.js
  var ENV = {
    modulePrefix: 'dummy',
    environment: environment,
    rootURL: '/',
    locationType: 'auto',
    changeTracker: { trackHasMany: true, auto: true }, 
    EmberENV: {
    ... rest of config
  // file app/models/user.js
  export default Model.extend({
    changeTracker: {only: ['info', 'company', 'pets']}, // settings for user models

    name: attr('string'),
    info: attr('object'),
    json: attr(),
    company: belongsTo('company', { async: false, polymorphic: true }),
    profile: belongsTo('profile', { async: true }),
    projects: hasMany('project', { async: false }),
    pets: hasMany('pet', { async: true, polymorphic: true })
  });

Serializer extras

Example:

Let's say you set up the user model's serializer with keep-only-changed mixin

// file: app/serializers/user.js
import DS from 'ember-data';
import keepOnlyChanged from 'ember-data-change-tracker/mixins/keep-only-changed';

export default DS.RESTSerializer.extend(keepOnlyChanged);

Then when you are updating the user model

user.set('info.foo', 1);
user.serialize(); //=> '{ info: {"foo:1"} }'

Without this mixin enabled the json would look like:

  { name: "dude", info: {"foo:1"}, company: "1" companyType: "company", profile: "1" }

where all the attributes and association are included whether they changed or not

Extra's

Usage:

  user.startTrack(); // saves all keys that are being tracked
  user.savedTrackerValue('info') //=> {foo: 1}  original value of info
  user.set('info.foo', 8)      
  user.didChange('info') //=> true
  user.savedTrackerValue('info') //=> {foo: 1}  original value of info    

Known Issues

For example:


import {moduleForModel, test} from 'ember-qunit';
import {make, manualSetup} from 'ember-data-factory-guy';
import {initializer as changeInitializer} from 'ember-data-change-tracker';

moduleForModel('project', 'Unit | Model | project', {

  beforeEach() {
    manualSetup(this.container);
    changeInitializer();
  }
});