Ember Data createRecord Error on switching routes - ember.js

I'm getting the following error on createRecord
Uncaught Error: Assertion Failed: You may not pass `null` as id to the store's find method
Create record is called here
var newSchool = this.store.createRecord('deal', {
name: newSchoolName,
timeZone: newTimeZone,
locale: newLanguage,
gradeNames: newGradeNames,
standardSourceIds: newStandardSources,
contentSourceIds: newContentSources,
adminUserId: '511a48a7781200b2cd000001',
numOfToken: 1,
higherEd: higherEd,
dealType: 'institution',
parentDeal: this.get('model.deal')
});
The problem is with the parentDeal it's a belongTo relationship if I change it to null there's no error.
Also the error is only thrown on switching routes and if I log this.get('model.deal') before hand it shows the object.
The model is declared in the route
model: function() {
return Ember.RSVP.hash({
deal: this.store.find('deal', this.get('session.dealId')),
contentSources: this.store.find('contentSource'),
standardSources: this.store.find('standardSource')
});
},
Edit: After kitlers comments
I added the following to deactivate
deactivate: function() {
var model = this.get('model.deal');
if(model && model.get('isDirty')){
model.get('transaction').save()
}
}
Also before hand this is what the store looked like in ember inspector

Related

Ember DS.Errors troubleshooting on create form

I have a basic create form setup which has validation on the name field. The save action for the form has a promise to gracefully hold the error:
actions: {
save: function(){
var route = this;
var createCampaign = this.store.createRecord("campaign", {
code: this.get("code"),
name: this.get("name"),
description: this.get("description"),
});
this.set("code",""),
this.set("name",""),
this.set("description",""),
// POST values to campaigns
createCampaign.save().then(function(c){
route.transitionToRoute("campaigns.view",c.id);
}, function(errors){
});
}
}
});
My defining attributes is:
TM.Campaign = DS.Model.extend({
name: DS.attr(),
code: DS.attr(),
description: DS.attr(),
});
I've read that by using a RESTAdapter, the ajaxError needs to be overwritten, so I've added the following:
ajaxError: function(jqXHR){
var error = this._super(jqXHR);
if(jqXHR && jqXHR.status === 422){
var response = Ember.$.parseJSON(jqXHR.responseText);
errors = {}
if (response.errors){
var jsonErrors = response.errors;
Ember.keys(jsonErrors).forEach(function(key){
errors[Ember.String.camelize(key)] = jsonErrors[key]
});
}
return new DS.InvalidError(errors)
} else {
return error
}
}
The response which is coming from the API is structured as:
{
"errors": {
"name": [
"The name field is required."
]
}
}
But for some reason, whenever I try to display DS.Errors (using console.log(route.get("errors")), I get undefined. It's like Ember doesn't know that a validation error has appeared within the response.
I've also made sure that the response status comes back as 422 Unprocessable Entity. Can anyone see what I'm missing??
EDIT: I have been able to create a JS Bin to demonstrate my problem: http://jsbin.com/xujari/2/
Your jsBin slightly modified :
http://jsbin.com/luwaqeyoca/1/edit?js,console,output
The description of DS.InvalidError never state about setting an error in the route. The sure part is that it sets the promise rejection error. It's also set an error property on the record it self if the property has a property maching the error key name (in breafe if your error key is name you HAVE TO have a property name in your model)
createCampaign.save().then(function(c){
route.transitionToRoute("campaigns.view",c.id);
}, function(errors){
console.log(errors.errors.name); //from the promise error
console.log(createCampaign.get("errors.name")); //from your record
});
EDIT
Here is a bin modified as you asked in comments
http://jsbin.com/sokebuzumu/1/edit?html,js,output
you need to create an empty object "errors" in your controller as in the jsbin
createCampaign.save().then(function(c){
}, function(errors){
this.set("errors",createCampaign.get("errors"));
}.bind(this));
I just answered a similar question on Ember Data Error handling here which covers a number of aspects to error handling related to your question.
You don't need to override the ajaxError handling on the adapter any more since my PR was merged into Ember Data. Errors will be applied to the model correctly now so you can refer to errors using the functions as per my previous answer.

Ember passing model using link-to to another resource throwing uncaught #error

I have a model which 'owns' other models (belongTo, hasMany) and I'm attempting to create each in a multi-step form.
I am passing the parent model, campaign, through with each link-to so it can be used when creating the other records and displaying existing children. This worked when I originally had a nested route, but when I change it to a resource the campaign model seemingly isn't sent through.
My router:
App.Router.map(function () {
this.resource('campaigns', function() {
// I originally had a this.route('step2') which I sent the model to and it works
this.resource('campaign', { path: '/:campaign_id' }, function() {
this.route('edit');
this.resource('ad-groups', function() {
this.route('new'); // new route is irrelevant in this question
});
});
});
});
So in the campaign/edit router, I save the model and transition to the ad-groups/index route with the campaign as the model:
App.CampaignEditRoute = Ember.Route.extend({
actions: {
save: function(campaign) {
campaign.save().then(function() {
this.transitionTo('ad-groups', campaign);
}.bind(this));
}
}
});
But this is where it renders the template, but the Ember inspector shows it's not loading a model. And the relevant parts of my models for completeness:
App.Campaign = DS.Model.extend({
adGroups: DS.hasMany('ad-group', { async:true }),
...
});
App.AdGroup = DS.Model.extend({
campaign: DS.belongsTo('campaign'),
...
});
If I manually return the campaign within the model hook of ad-groups/index then Ember throws an Uncaught #error, and renders the template but without any of the model data showing.
Edit: So I tried implementing the solution below by setting the model as this.modelFor('campaign') and it threw another Uncaught #error. Bizarrely, when I remove the relationship in my FIXTURES it no longer errors, but of course I lose that relationship. My fixtures:
App.Campaign.FIXTURES = [
// If I remove the adGroups: [1] it removes the Uncaught #error but I lose the relationship
{ id: 1, name: "Campaign #1", app: 1, adGroups: [1] },
{ id: 2, name: "Campaign #2", app: 1 }
];
App.AdGroup.FIXTURES = [
{ id: 1, name: 'My first AdGroup', bid: 2, budget: 100, campaign: 1 }
];
I had a similar issue to this before, and I fixed it by adding { async: true } to my campaign model as you can see above. So this looks like it's more the issue, admittedly async confuses me a little still. Could anyone shed some light on why this might be happening?
After all of this, the two part solution was:
1.) Instead of passing the model using the link-to, implementing this:
App.MyRoute = Ember.Route.extend({
model: function() {
return this.modelFor('campaign');
},
...
});
Which in my opinion shouldn't be necessary, but it's actually really nice in that it uses a parent routes' model - link for reference.
2.) The remaining Uncaught #error error that Ember was throwing was extremely unhelpful. It turns out I was linking to adgroup instead of ad-group in the template:
{{link-to 'adgroup'}} instead of {{link-to 'ad-group'}}

Ember.js not displaying errors [duplicate]

I'm using Ember Data and I can't seem to get the model's 'errors' property to populate with the error messages from my REST API. I'm pretty much following the example at this guide:
http://emberjs.com/api/data/classes/DS.Errors.html
My app looks like this:
window.App = Ember.Application.create();
App.User = DS.Model.extend({
username: DS.attr('string'),
email: DS.attr('string')
});
App.ApplicationRoute = Ember.Route.extend({
model: function () {
return this.store.createRecord('user', {
username: 'mike',
email: 'invalidEmail'
});
},
actions: {
save: function () {
this.modelFor(this.routeName).save();
}
}
});
And my API returns this:
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Length: 125
{
"errors": {
"username": ["Username is taken!"],
"email": ["Email is invalid."]
}
}
After I call save() on the model, here is what I see on the user model:
user.get('isError') // true
user.get('errors.messages') // []
Even though the model is registering the isError property correctly, I can't seem to get the error messages to populate. How can I get this to work? I'm working on the latest beta build of Ember Data version 1.0.0-beta.8.2a68c63a
The docs are definitely lacking in this area, the errors aren't populated unless you're using the active model adapter.
Here's an example of it working, also check out Ember: error.messages does not show server errors on save where I say the same thing
http://jsbin.com/motuvaye/24/edit
You can fairly easily implement it on the RESTAdapter by overriding ajaxError and copying how the active model adapter does it.
App.ApplicationAdapter = DS.RESTAdapter.extend({
ajaxError: function(jqXHR) {
var error = this._super(jqXHR);
if (jqXHR && jqXHR.status === 422) {
var response = Ember.$.parseJSON(jqXHR.responseText),
errors = {};
if (response.errors !== undefined) {
var jsonErrors = response.errors;
Ember.EnumerableUtils.forEach(Ember.keys(jsonErrors), function(key) {
errors[Ember.String.camelize(key)] = jsonErrors[key];
});
}
return new DS.InvalidError(errors);
} else {
return error;
}
}
});
http://jsbin.com/motuvaye/27/edit
https://github.com/emberjs/data/blob/v1.0.0-beta.8/packages/activemodel-adapter/lib/system/active_model_adapter.js#L102
I've had a long and very frustrating experience with Ember Data's errors.messages property, so I thought I'd summarize all of my findings here in case anyone else tries to use this feature.
1) Documentation is out of date
As #kingpin2k mentioned in his answer, the documentation at http://emberjs.com/api/data/classes/DS.Errors.html is out of date. The example they provide on that page only works if you're using DS.ActiveModelAdapter. If you're using the default DS.RESTAdapter, then you need to do something like this. Note that I prefer this simpler approach instead of just copying ActiveModelAdapter's ajaxError implementation:
App.ApplicationAdapter = DS.RESTAdapter.extend({
ajaxError: function (jqXHR) {
this._super(jqXHR);
var response = Ember.$.parseJSON(jqXHR.responseText);
if (response.errors)
return new DS.InvalidError(response.errors);
else
return new DS.InvalidError({ summary: 'Error connecting to the server.' });
}
});
2) You must supply a reject callback
This is very strange, but when you call save() on your model, you need to provide a reject callback, otherwise, you'll get an uncaught 'backend rejected the commit' exception and JavaScript will stop executing. I have no idea why this is the case.
Example without reject callback. This will result in an exception:
user.save().then(function (model) {
// do something
});
Example with reject callback. Everything will work well:
user.save().then(function (model) {
// do something
}, function (error) {
// must supply reject callback, otherwise Ember will throw a 'backend rejected the commit' error.
});
3) By default, only the error properties that are part of the model will be registered in errors.messages. For example, if this is your model:
App.User = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string')
});
...and if this is your error payload:
{
"errors": {
"firstName":"is required",
"summary":"something went wrong"
}
}
Then summary will not appear in user.get('errors.messages'). The source of this problem can be found in the adapterDidInvalidate method of Ember Data. It uses this.eachAttribute and this.eachRelationship to restrict the registration of error messages to only those that are part of the model.
adapterDidInvalidate: function(errors) {
var recordErrors = get(this, 'errors');
function addError(name) {
if (errors[name]) {
recordErrors.add(name, errors[name]);
}
}
this.eachAttribute(addError);
this.eachRelationship(addError);
}
There's a discussion about this issue here: https://github.com/emberjs/data/issues/1877
Until the Ember team fixes this, you can work around this problem by creating a custom base model that overrides the default adapterDidInvalidate implementation, and all of your other models inherit from it:
Base model:
App.Model = DS.Model.extend({
adapterDidInvalidate: function (errors) {
var recordErrors = this.get('errors');
Ember.keys(errors).forEach(function (key) {
recordErrors.add(key, errors[key]);
});
}
});
User model:
App.User = App.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string')
});
4) If you return DS.InvalidError from the adapter's ajaxError (the one we overrode above), then your model will be stuck in 'isSaving' state and you won't be able to get out of it.
This problem is also the case if you're using DS.ActiveModelAdapter.
For example:
user.deleteRecord();
user.save().then(function (model) {
// do something
}, function (error) {
});
When the server responds with an error, the model's isSaving state is true and I can't figure out to reset this without reloading the page.
Update: 2014-10-30
For anyone who's struggling with DS.Errors, here's a great blog post that summarizes this well: http://alexspeller.com/server-side-validations-with-ember-data-and-ds-errors/
UPDATE: Ember Data 2.x
The above response are still somewhat relevant and generally pretty helpful but are now outdated for Ember Data 2.x(v2.5.1 at time of this writing). Here are a few things to note when working with newer versions of Ember Data:
DS.RESTAdapter no longer has an ajaxError function in 2.x. This is now handled by RESTAdapter.handleResponse(). You can override this method if any special handling or formatting of errors is required. RESTAdapter.handleResponse source code
The documentation for DS.Errors and DS.Model.errors(which is an instance of DS.Errors) is currently a little misleading. It ONLY works when errors in the response adhere to the JSON API error object specification. This means it will not be at all helpful or usable if your API error objects follow any other format. Unfortunately this behavior can't currently be overridden well like many other things in Ember Data as this behavior is handle in private APIs inside of Ember's InternalModel class within DS.Model.
DS.InvalidError will only be used if the response status code is 422 by default. If your API uses a different status code to represent errors for invalid requests you can override RESTAdapter.isInvalid() to customize which status codes(or other part of an error response) to check as representing an InvalidError.
As an alternative you can override isInvalid() to always return false so that Ember Data will always create a more generic DS.AdapterError instead. This error is then set on DS.Model.adapterError and can be leveraged as needed from there.
DS.AdapterError.errors contain whatever was returned on the errors key of the API response.

Ember canary "Uncaught Error: Assertion Failed: You can only add a 'milestone' record to this relationship"

I have the following controller:
Filters.FiltersController = Ember.ArrayController.extend({
needs: ["milestones"],
selectedMilestone: null,
selectedMilestoneChange: function() {
if(!this.get('filterTypeSelected') || !this.get('selectedMilestone')) {
return;
}
var filter = this.store.createRecord('filter', {
type: 'milestone',
value: this.get('selectedMilestone').get("id"),
negate: this.get('filterTypeSelected').negate,
milestone: this.store.find('milestone', this.get("selectedMilestone").get("id"))
});
filter.save();
this.resetFilterCreator();
}.observes('selectedMilestone')
});
Whenever the view changes the selectedMilestone property, it creates a new filter instance and saves it.
This worked until I upgraded to Ember-canary to use the query-parameters-new feature. After the upgrade the following error is thrown whenever the filter record is created:
Uncaught Error: Assertion Failed: You can only add a 'milestone' record to this relationship
The error originates from
milestone: this.store.find('milestone', this.get("selectedMilestone").get("id"))
Model is:
Filters.Filter = DS.Model.extend({
type: DS.attr('string'),
value: DS.attr('string'),
milestone: DS.belongsTo('milestone'),
negate: DS.attr('boolean')
});
How do I make this work?
The problem was that (apparently in the newer Ember versions), the return from this.store.find(...) is a promise and thus not instanceof Milestone.
Instead you can either resolve the promise or use this.store.getById('model', 'id'). This, however, requires that the model has already been retrieved (which it is onLoad in my case).

Attempted to register a view with an id already in use

Since this commit we can't registrer a view with an ID twice. This seems logical. However I got an issue.
Router
App.Router.map(function() {
this.resource('contact', { path: '/contacts/:contact_id' });
});
App.ContactShowRoute = Ember.Route.extend({});
View
App.ContactShowView = Em.View.extend({
elementId: "page-show-contact"
});
Consider that I'm already on the route App.ContactShowRoute. I would like to transitionTo() the same route but with a different context.
I expected emberjs to destroy the view and then create it again, but I got the following error:
Uncaught Error: assertion failed: Attempted to register a view with an id already in use: page-show-contact
I don't want to instantiate a view with the same ID twice. I just want ember to destroy the actual one and then create a new one.
It seems to be a bug in the current version. Maybe you should open a ticket.
Until this is fixed, this might help:
App.ContactShowRoute = Ember.Route.extend({
renderTemplate : function(controller, model) {
if(this.lastRenderedTemplate == this.routeName)
return;
return this._super();
}
});