Issue with Ember Data and Creating a Record

Hello,

I have a question regarding Ember-Data and creating a new Record. I have a route and a template for creating a new record that looks something like this:

App.UsersNewRoute = Ember.Route.extend({
    	model: function() {
    		return this.store.createRecord('user');
    	}
    })

The created record, as my model, binds to my template and it’s easy to create the user when they click the submit button, and easy to delete the record when they click the cancel button. But if they navigate away from the new user template through some other link, (like the page’s navbar), I find that I have the dirty record in the local ember-data store. The record shows up on the display page. I do not want this. I tried solving my problem by replacing the model function with this:

App.UsersNewRoute = Ember.Route.extend({
	model: function() {
		return App.User.create({});
	}
})

But this generates the following runtime error message:

Error while processing route: users.new You should not call create on a model. Instead, call store.createRecord with the attributes you would like to set. Error: You should not call create on a model. Instead, call store.createRecord with the attributes you would like to set.

I’m sort of at a loss on how to proceed. What is the best way to do two-way binding in a CRUD function, so that if the user navigates away via a link or back button before submitting, it doesn’t save the record to the data store?

This is what I’m doing right now:

App.UsersNewRoute = Em.Route.extend({
  model: function() {
    return this.store.createRecord('user');
  },

  resetController: function(controller) {
    var user = controller.get('model');
    if (user.get('isDirty')) user.rollback();
  }
})

The idea is using resetController to rollback model changes when route is exiting or the model is replaced. There are other solutions which use deactivate but I prefer resetController. No matter the user is a new record or an existing record, rollback will do the right thing for you.

You can also extract this logic to a mixin and mix into all your CRUD routes.

As of Ember-Data 2.1, the above solution is not accurate. Here’s an update, for an route called players/new, which is used to create new instances of the model player:

import Ember from 'ember';

export default Ember.Route.extend({
  model() {
    return this.store.createRecord('player');
  },

  actions: {
    save() {
      const record = this.modelFor('players.new');
      record.save()
        .then(() => {
          this.transitionTo('root.show');
        })
        .catch(error => {
          console.error("Error saving player", error);
        });
    },

    willTransition() {
      const record = this.modelFor('players.new');
      if (record.get('isNew')) {
        return record.destroyRecord();
      }
    },
  },
});

Points of note:

  1. rollback doesn’t exist anymore. Here I destroy the record with destroyRecord
  2. isDirty is also gone. I replaced it with isNew in this example
  3. resetController doesn’t seem to wait for the promise returned by destroyRecord, meaning the record will still exist when the new route is loaded. Fortunately, willTransition does wait for you

Unfortunately, 1 and 2 only make sense when creating records, not updating them. At the moment my app only creates, so I haven’t had to dig into that problem just yet.

I’d love to hear suggestions on how this can be improved :slight_smile:

For completeness, here’s a Router that would do the job when editing a model. Again, this has been tested with Ember-Data 2.1:

import Ember from 'ember';

export default Ember.Route.extend({
  model(params) {
    return this.store.findRecord('player', params.id);
  },

  actions: {
    save() {
      const record = this.modelFor('players.edit');
      record.save()
        .then(() => {
          this.transitionTo('root.show');
        })
        .catch(error => {
          console.error("Error saving player", error);
        });
    },

    willTransition() {
      const record = this.modelFor('players.edit');
      return record.rollbackAttributes();
    },
  },
});

As mentioned above, isDirty doesn’t exist anymore. Instead, I just rollbackAttributes() without asking. If we saved earlier, now there won’t be any changes to roll back.

I’ve been doing more research into this, which has taken me quite more work than I expected. I’m developing a general convention at https://gist.github.com/pablobm/e77a98e5f3c610953a82