Ember Data 404 when using store - ember.js

New to Ember.js, making a simple todo app to learn it a bit better.
In my routes/todos.js I have:
import Route from '#ember/routing/route';
export default Route.extend({
model() {
return [
{
title: 'walk the dog',
completed: false,
},
];
},
});
However, when I change to:
export default Route.extend({
model() {
return this.get('store').findAll('todo');
},
});
I get the error:
ember-console.js:29 Error while processing route: todos Ember Data Request GET /todos returned a 404
What does this error mean?

findAll will always make a request to the API server. If that doesn't exist or it isn't accessible, it will return a 404. Use peekAll if you just want to reference data in the Store without making an API request.
A 404 also will cause an error state to be thrown in the route and for the model to be rejected. Here is one way I've worked around that for APIs that DO throw 404s when there is no data (rather than a 200 with an empty array, for example):
return new EmberPromise(resolve =>
this.get('store').findAll('todo').then(
// resolves successfully whether the API call succeeds or fails
todos => resolve(todos),
err => {
console.warn(err);
resolve([]);
}
)
);

Related

ember JSONAPIAdapter fails to load data properly into store

I am using ember-cli and ember-data 1.13.7 and JSONAPIAdapter.
I use http-mock to mock data during local testing.
It worked well when I used the RESTAdapter, but I ran into a problem when switching to the JSONAPIAdapter.
The problem is that the records data does not get loaded into the store and I get an error reading an undefined property.
The adpater just looks like this:
import DS from 'ember-data';
export default DS.JSONAPIAdapter.extend({
namespace: 'api',
});
The ajax call is this:
http://localhost:4200/api/users/1
The http-mock looks like this:
usersRouter.get('/:id', function(req, res) {
res.send({
'users': {
id: req.params.id,
firstname: "John",
lastname: "Doe",
email: "johndoe#example.com",
mobile: "12345678",
nextAppointment: 1
}
});
});
The response looks like this:
{"users":{"id":"1","firstname":"John","lastname":"Doe","email":"johndoe#example.com","mobile":"12345678","nextAppointment":1}}
The response data islooking good but the problem is that together with the response is the header status code of 304, and the fact that the data is not loaded into store. The object with id=1 is created, but all properties in the object are 'undefined' when I look at the stores data in the ember inspector.
Update:
The error I get is:
Error while processing route:
home.index Cannot read property '_internalModel' of undefined
TypeError: Cannot read property '_internalModel' of undefined
Update 2:
It turns out the 304 is not important. The model is still not properly loaded into store when the httpcode is 200 either.
I also fund that this call works fine:
http://localhost:4200/api/users
while this call fails:
http://localhost:4200/api/users/1
They return exactly the same JSON response.
Your call for:
http://localhost:4200/api/users
can't return exactly the same JSON response.
Your call for: http://localhost:4200/api/users/1 should return:
{
"data": {
"id":"1",
"type": "user",
"attributes" : {
"firstname":"John",
"lastname":"Doe",
"email":"johndoe#example.com",
"mobile":"12345678",
"nextAppointment":1
}
}
}
Read more about JSONAPIAdapter:
JSON API ADAPTER AND SERIALIZER in Ember Data v1.13 blog post
jsonapi.org

Custom post-request with RESTAdapter

My models work with the server via Ember's default RESTAdapter.
I just created a custom endpoint /mail on my server which sends an e-mail if provided a name, valid e-mail-adress and text.
How do I make Ember send that custom post-request? Is it possible without Ember.ajax at all?
For me personally, I wouldn't use Ember-Data to handle that scenario; I generally only use Ember-Data to handle my persisted models. If you try to use Ember-Data for other AJAX calls, it's just going to become a mess. Remember that Ember-Data's job is to manage your persisted data and one way that it can do that is with AJAX calls. That doesn't mean that anything that requires an AJAX call should be handled with Ember-Data.
I have this same issue and I wrote a utility module that has functions for all of my non-model AJAX stuff. This makes it really easy to swap out for testing. Here's a small example:
// utils/ajax.js
export function sendHelpEmail(comment) {
return new Promise((resolve, reject) => {
$.ajax({
type: 'POST',
url: '/api/contact_us',
contentType: 'application/json',
data: JSON.stringify({ comment }),
processData: false,
statusCode: {
200: () => Em.run(null, resolve),
500: () => Em.run(null, reject)
}
});
});
}
Then, I can do something like this in my controller:
import { sendHelpEmail} from '../utils/ajax.js';
export default Em.Controller.extend({
actions: {
sendEmail() {
sendHelpEmail(this.get('comment'));
}
}
});

Ember: Promise (destroyRecord().then()) failing on successful request

I am very new to ember, but I've spent hours with this problem and can't solve it on my own. Here's my route (using ember-cli):
import Ember from 'ember';
export default Ember.Route.extend({
actions: {
save: function() {
var controller = this.controller;
controller.get('model').save().then(function(account) {
console.log('account saved');
controller.transitionToRoute('accounts.index');
}, function(response) {
console.log('account NOT saved');
});
return false;
},
deleteAccount: function() {
var controller = this.controller;
controller.get('model').destroyRecord().then(function(account) {
console.log('account deleted');
controller.transitionToRoute('accounts.index');
}, function(response) {
console.log('account NOT deleted');
});
return false;
},
cancel: function() {
this.controller.get('model').rollback();
this.transitionToRoute('accounts.index');
return false;
},
}
});
I am triggering the deleteAccount action in my template (button). The interesting thing is that the code is actually deleting the record. It sends a successful delete request and the api deletes the account. But it never transitions to accounts.index. Instead it logs "account NOT deleted". If I manually go to account.index then the model isn't there any more (as one would expect).
I got the code from the official ember docs. See: http://emberjs.com/api/data/classes/DS.Model.html#method_destroyRecord
So why is the promise always failing when the model is actually deleted? Your help would be very much appreciated!
Btw.: It is an edit route with account_id passed as param, so no need for manually defining a "model function" on the route. Just in case someone was wondering.
I guess I've just solved it. The reason for the failing of the destroyRecord() promise seemed to be that my API responded with an EMPTY HTTP 200 response. But 200 usually implies that an entity is returned which isn't the case. So I adapted the API to return an empty 204 response and this did the trick. This SO answer actually helped a lot: HTTP status code for update and delete?
A successful response SHOULD be 200 (OK) if the response includes an
entity describing the status, 202 (Accepted) if the action has not yet
been enacted, or 204 (No Content) if the action has been enacted but
the response does not include an entity.

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.

Route is not calling rest webservice

I have started a simple example using the fixture Adapter. When I navigate to my route /teams, I can see all Teams i have registered in my adapter.
Now I changed my code to register the Default restadapter:
Football.ApplicationAdapter = DS.RESTAdapter.reopen({
host: "http://localhost:8080/MatchService.svc/json"
});
When I now navigate to my route /Teams, I can see no Errors, the site looks as it should, just without no Teams listed in the list.
Then I started to have a look at the Network-Traffic: No calls are being sent, nothing.
But: When I try to add a team (I havent implemented this Method in rest service yet), I can see that there is a call going to
localhost:8080/MatchService.svc/json...
and offcourse, I get a 404.
But why dont I get a call against
localhost:8080/MatchService.svc/json/teams
when I navigated to my Teams-route?
Here is my route:
Football.TeamsRoute = Ember.Route.extend({
model: function() {
return this.store.all('team');
}
});
I changed it already from this.store.find .... to this.store.all... because after using the restadapter I got an "error while loading the route : undefined".
I also added the current ember.js -Version as reference from the ember site.
Thats the json which gets returned by the Server:
{"TeamsResult":[
{"ID":1,"Name":"Deutschland"},
{"ID":2,"Name":"Frankreich"}
]}
I also tried a a not wrapped json result from the WebService:
{[{"ID":1,"Name":"Deutschland"},{"ID":2,"Name":"Frankreich"}]}
all doesn't make a call to the server, it just returns all of the records already fetched client side. you should use this.store.find('team')
Additionally you should define your adapter using extend:
Football.ApplicationAdapter = DS.RESTAdapter.extend({
host: "http://localhost:8080/MatchService.svc/json"
});
reopen applies it to every instance created of the rest adapter, which may not be appropriate.
Here's a small example that shows the basic structure of your app:
Football = Ember.Application.create();
Football.Router.map(function() {
this.resource('teams', {path:'/teams'});
});
Football.TeamsRoute = Ember.Route.extend({
model: function() {
return this.store.find('team');
}
});
Football.ApplicationAdapter = DS.RESTAdapter.extend({
host: "http://localhost:8080/MatchService.svc/json"
});
Football.Team = DS.Model.extend({
name: DS.attr()
});
For convenience sake I mocked the call to http://localhost:8080/MatchService.svc/json/teams with what Ember Data is expecting as a response:
{
teams:[
{
id: 1,
name: "Miami Heat"
},
{
id: 2,
name: "Seattle Seahawks"
},
{
id: 3,
name: "Texas Longhorns"
}
]
}
http://emberjs.jsbin.com/OxIDiVU/425/edit