In an application built with Ember 2.1.0, I followed this guide to implement server side validation.
I have this route :
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
return this.store.findRecord('user', params.id);
},
actions: {
submit: function() {
this.controller.model.save().then(function() {
this.transitionTo('admin.users');
}, (data) => {
console.log(data.errors);
this.set('errors', data.errors);
});
},
cancel: function() {
this.controller.model.rollbackAttributes();
this.transitionTo('admin.users');
}
}
});
And I have this code in the template :
{{#if errors.length}}
<div class='alert alert-danger'>
<h4>There was a problem</h4>
<p>The user could not be saved due to validation errors</p>
</div>
{{/if}}
The server returns {"errors":[{"email":["can't be blank"]}]} and errors are displayed in the console but nothing on the page.
According to the documentation, I tried to return {"errors":{"email":["can't be blank"]}} but I have "InvalidError expects json-api formatted errors array."
How can I show errors with Ember 2?
I finally found the solution. The data sent by the server was not using the json API format as written in the doc like this :
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/vnd.api+json
{
"errors": [
{
"status": "422",
"source": { "pointer": "/data/attributes/first-name" },
"title": "Invalid Attribute",
"detail": "First name must contain at least three characters."
}
]
}
I wrote this code, in ruby, to format it correctly :
class ErrorSerializer
def self.serialize(errors)
result = errors.messages.map do |message|
message.last.map do |detail|
{
"source": { "pointer": "/data/attributes/#{message.first}" },
"detail": detail
}
end
end.flatten
{ errors: result }
end
end
I can be used like this :
render status: :unprocessable_entity, json: ErrorSerializer.serialize(user.errors)
Related
I am making my first Ember/Phoenix app using JSONAPIAdapter. When doing a post request Ember responds with Assertion Failed: AdapterError expects json-api formatted errors array.
Below is the relevant code:
adapter/application.js
import DS from 'ember-data';
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin';
import config from '../config/environment';
export default DS.JSONAPIAdapter.extend(DataAdapterMixin, {
});
serializers/application.js
import DS from 'ember-data';
export default DS.JSONAPISerializer.extend({
});
request payload:
{
"data": {
"attributes": {
"name": ""
},
"relationships": {
"user": {
"data": {
"type": "users",
"id": "1"
}
}
},
"type": "listings"
}
}
response payload:
{"errors":{"name":["can't be blank"]}}
Why does Ember keeps giving me this error?
Your response payload is not correct. You are using json-api. So your payloads have to follow the json-api specification. Your request-payload looks correct. But check out how errors have to be serialized.
A json-api error response must contain a root key "errors" holding an array of error-objects. Regarding the documenation an error may contain several members, but the two most important are detail and source.
Here's an example:
{
"errors":[
{
"detail": "can't be blank",
"source": {
"pointer": "data/attributes/name"
}
}
]
}
The source-key contains a json-object holding a json-api-pointer. With the help of this pointer information ember's JSONAPI-Adapter adds the errors to the corresponding attributes of your record. Note that your backend needs to send a 422 HTTP status code (Unprocessable Entity).
If that works you can do something like that at client:
{{#each model.errors.name as |error|}}
<div class="error">
{{error.message}}
</div>
{{/each}}
How do you serialize your errors at backend? Are you using ja_serializer? If not, I would recommend it, because ja_serializer can serialize json-api-errors by default.
The documentation already linked by wuarmin clearly states (capitals theirs, italics mine):
Error objects MUST be returned as an array keyed by errors
Your API response' errors key has a single object as value, not an array.
I tried setting my app in ember#2.5.0, but every time when I use store and try to use
this.store.findAll("user");
I always get the error saying Cannot read property 'type' of undefined.
Initialized the store, adapter and the model and server too.
adapters/application.js
import JSONAPIAdapter from 'ember-data/adapters/json-api';
export default JSONAPIAdapter.extend({
defaultSerialiser: "-default"
});
models/user.js
import Model from 'ember-data/model';
import attr from 'ember-data/attr'
export default Model.extend({
"name": attr()
});
and server side it is like
app.get("/users", function(req, res) {
res.send({"name": "Surya"});
});
Getting error
TypeError: Cannot read property 'type' of undefined
at _pushInternalModel (store.js:1524)
at push (store.js:1501)
at finders.js:148
at Object.Backburner.run (ember.debug.js:678)
at _adapterRun (store.js:1733)
at finders.js:145
at tryCatch (ember.debug.js:53806)
at invokeCallback (ember.debug.js:53821)
at publish (ember.debug.js:53789)
at ember.debug.js:32054
You use the JSONAPI serializer and adapter, but your response
{
"user": {
"name": "Surya"
}
}
is not a valid JSONAPI response. It should be
{
"data": {
"type": "user",
"id": "0",
"attributes": {
"name": "Surya"
}
}
}
I have two models who look like this:
// models/database.js
export default DS.Model.extend({
title: DS.attr(),
/*...*/
documents: DS.hasMany('document')
});
// models/document.js
export default DS.model.extend({
name: DS.attr(),
/*...*/
database: DS.belongsTo('database')
});
Is also have some routes
Router.map(function() {
this.route('databases', function() {
this.route('database', { path: ":database_id" }, function() {
this.route('documents');
});
});
});
When I call /database/[ID]/documents if got nothing.
I think the problem is I'm working with an JSON HAL API and when I ask for databases on API I receive this kind of response.
{
"_embedded": {
"databases": [
{
"_links": {
"self": {
"href": "<API_URL>/databases/7ec39267b544424c/",
"reference": "7ec39267b544424c",
"title": "00000006"
}
},
"database": {
"name": "00000006",
"reference": "7ec39267b544424c",
"title": "****"
}
},
{
"_links": {
"self": {
"href": "<API_URL>/databases/aace19af004144a4/",
"reference": "aace19af004144a4",
"title": "00000007"
}
},
"database": {
"name": "00000007",
"reference": "aace19af004144a4",
"title": "******"
}
}
}]
}
As you can see there no documents attributes on JSON returned.
If I want a database's documents I've got to call [API_URL]/databases/[ID]/domains/[ID]/documents
There's still a way to link documents to a database by specifying that the documents are recoverable at this URL (/databases/[ID]/domains/[ID]/Documents)?
I found the solution.
I've override extractRelationships method in hal serializer like this:
extractRelationships(modelClass, resourceHash, included) {
resourceHash._links["documents"] = {
"href" : "/databases/" + resourceHash.id + "/domains/" + (resourceHash.name || resourceHash[modelClass.modelName].name) + "/documents/"
};
return this._super(modelClass, resourceHash, included);
}
I just add a link to documents for each databases returned by the API.
I've modified my route for documents like this
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model() {
return this.modelFor('databases.database').get('documents');
}
});
And now it works perfectly.
I think what you're looking for is a HAL API to JSON API converter. To avoid doing this yourself manually I'd recommend the http://github.com/201-created/ember-data-hal-9000 Ember plugin, which I've used myself to get Ember Data to play nice with Spring Boot.
If you wanted to see a simple example here's my client-side and server-side demo app.
Additionally, if you were curious what Ember, and JSON API don't use HAL you can read here JSON API - FAQ on HAL.
I have a model record created and being saved through a route and controller. When I save the record through the controller (via a savePlace action), I am seeing this error in the JS console:
SyntaxError: JSON.parse: unexpected end of data at line 1 column 1 of the JSON data
I've tried not setting anything on the model as well as setting dummy data on the model, but I get the same error. I am also user ember-cli http-mocks as a test backend to handle JSON responses. I realize it may be the response, but I'm not sure how else to configure the response.
Here's the relevant code:
routes/places/new.js:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.createRecord('place');
},
});
controllers/places/new.js:
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
saveGeom(geom) {
this.get('model').set('geometry', geom);
},
savePlace(data) {
this.get('model').set('name', this.get('name')).set('description', this.get('description'));
this.get('model').save().then(function() {
alert("SUCCESS");
}, function(error) {
console.log(error);
});
}
}
});
server/mocks/place.js:
placeRouter.post('/places', function(req, res) {
res.setHeader('Access-Control-Allow-Methods', 'POST');
res.send({
"places": {
id: 1,
name: "Triangle",
description: "Ryan Christiani",
geometry: {
"type": "Polygon",
"coordinates": [
[
[-84.32281494140625,34.9895035675793],
[-81.73690795898438,36.41354670392876],
[-83.616943359375, 34.99850370014629],
[-84.05639648437499,34.985003130171066],
[-84.22119140625, 34.985003130171066],
[-84.32281494140625,34.9895035675793]
]
]
}
}
});
});
Thanks!
I think you are using the wrong brackets in the wrong places in your JSON Object.
Check out this page
http://www.tutorialspoint.com/json/json_syntax.htm
The http-mocks configuration is wrong. It should be this the code snippet below. The server was instead responded with an array of objects (the response for 'GET /'). Not sure why that would trigger a JSON.parse error, but this is the correct configuration.
placeRouter.post('/', function(req, res) {
res.setHeader('Access-Control-Allow-Methods', 'POST');
res.send({
'places': [
{
id: 1,
name: "Using Ember CLI to create a Fixture Adapter.",
description: "Ryan Christiani",
geometry: {
"type": "Polygon",
"coordinates": [
[
[-84.32281494140625,34.9895035675793],
[-81.73690795898438,36.41354670392876],
[-83.616943359375, 34.99850370014629],
[-84.05639648437499,34.985003130171066],
[-84.22119140625, 34.985003130171066],
[-84.32281494140625,34.9895035675793]
]
]
}
}]});
});
I'm using EmberJs and Ember-Data in a Google App Engine project which uses NDB. In the database I have Host, Probe and Check entities. The database model doesn't really matter as long as I have my REST api in order but for clarity here are my database Classes:
class Host(ndb.Model):
hostName = ndb.StringProperty()
hostKey = ndb.Key('Host', 'SomeHostId')
class Probe(ndb.Model):
checkName = ndb.StringProperty()
probeKey = ndb.Key('Host', 'SomeHostId', 'Probe', 'SomeProbeId')
class Check(ndb.Model):
checkName = ndb.StringProperty()
checkKey = ndb.Key('Host', 'SomeHostId', 'Probe', 'SomeProbeId', 'Check', 'SomeCheckId')
I've added the keys in order to show that each host has some probes running on them and each probe performs some checks.
Host
Probe
Check
In my App.Js I have defined the following models:
App.Host = DS.Model.extend({
hostName: DS.attr('string')
probes: DS.hasMany('probe',{async:true})
});
App.Probe = DS.Model.extend({
host: DS.belongsTo('host'),
probeName: DS.attr('string')
checks: DS.hasMany('check',{async:true})
});
App.Check = DS.Model.extend({
probe: DS.belongsTo('probe'),
hostName: DS.attr('string')
});
I have defined the following router:
App.Router.map(function() {
this.resource('hosts', function(){
this.resource('host', { path:':host_id'}, function(){
this.resource('probes', function(){
this.resource('probe', { path:':probe_id'}, function(){
this.resource('checks', function(){
this.resource('check', { path:':check_id'}, function(){
});
});
});
});
});
});
});
And in AppEngine if have built the following URL paths:
app = webapp2.WSGIApplication([
('/', MainHandler),
webapp2.Route('/hosts', HostsHandler),
webapp2.Route('/hosts/<hostId>/', HostHandler),
webapp2.Route('/hosts/<hostId>/probes', ProbesHandler),
webapp2.Route('/hosts/<hostId>/probes/<probeId>/checks', ChecksHandler),
webapp2.Route('/hosts/<hostId>/probes/<probeId>/checks/<checkId>/', CheckHandler)
])
http://example.com/hosts returns:
{
"hosts": [
{
"hostName": "SomeHostName1",
"id": "SomeHostId1"
},
{
"hostName": "SomeHostName2",
"id": "SomeHostId2"
}
]
}
http://example.com/hosts/SomeHostId1/probes returns:
{
"probes": [
{
"probeName": "SomeProbeName1",
"id": "SomeProbeId1",
"host_id": "SomeHostId1"
},
{
"probeName": "SomeProbeName2",
"id": "SomeProbeId2",
"host_id": "SomeHostId1"
}
]
}
http://example.com/hosts/SomeHostId1/probes/SomeProbeId1/checks returns:
{
"checks": [
{
"checkName": "SomeCheckName1",
"id": "SomeCheckId1",
"probe_id": "SomeProbeId1"
},
{
"checkName": "SomeCheckName2",
"id": "SomeCheckId2",
"probe_id": "SomeProbeId1"
}
]
}
My templates are:
<script type="text/x-handlebars" id="host">
<h3>{{hostName}}</h3>
{{#link-to 'probes' probes}}probes{{/link-to}}
{{outlet}}
</script>
<script type="text/x-handlebars" id="probes">
{{#each probe in probes}}
Probe: {{probe.probeName}}
{{#link-to 'checks' probe.checks}}checks{{/link-to}}
{{/each}}
{{outlet}}
</script>
<script type="text/x-handlebars" id="checks">
{{#each check in checks}}
Check: {{check.checkName}}
{{/each}}
</script>
Now I have all this... but no clue how to tie it up together so that Ember-Data makes the right http requests. So far I've only seen request go to http://example.com/modelName/
Currently Ember Data does not support this type of nested routes for API endpoints. There's been some talk about this, but it doesn't seem to be making any forward progress.
I don't know anything about App engine, but if you could obtain a config like this, for ember-data rest adapter
app = webapp2.WSGIApplication([
('/', MainHandler),
webapp2.Route('/hosts', HostsHandler),
webapp2.Route('/hosts/<hostId>', HostHandler),
webapp2.Route('/probes', ProbesHandler),
webapp2.Route('/probes/<probeId>', ProbesHandler),
webapp2.Route('/checks/', CheckHandler)
webapp2.Route('/checks/<checkId>/', CheckHandler)
])
And the response to http://example.com/hosts should return a json array hosts:[{},{}] and to http://example.com/hosts/1 a json representing a host object host:{} and the same for the other AppEngine routes
You have defined the Host model twice, I think that shouldn't have been the case. I am pretty new to ember and haven't used async:true feature, but I have been able to do things like(but I hadn't used nested route):
App.Host = DS.Model.extend({
hostName: DS.attr('string')
probes: DS.hasMany('probe')
});
App.Probe = DS.Model.extend({
probeName: DS.attr('string')
checks: DS.hasMany('check')
});
App.Check = DS.Model.extend({
checkName: DS.attr('string')
});
and you can spin up a rest api for host that returns :
{
"hosts": [
{
"hostName": "SomeHostName1",
"id": "SomeHostId1",
"probes":["p1","p2"]
},
{
"hostName": "SomeHostName2",
"id": "SomeHostId2",
"probes":["p2","p3"]
}
],
"probes": [
{
"probeName": "SomeProbeName1",
"id": "p1",
"checks":["c1","c2"]
},
{
"probeName": "SomeProbeName2",
"id": "p2",
"checks":["c2","c3"]
}
],
"checks": [
{
"checkName": "SomeCheckName1",
"id": "c1"
},
{
"checkName": "SomeCheckName2",
"id": "c2"
}
]
}
In my case I didn't have nested route but I think we should be able to set the controller content from the master payload somehow since all the required content are in store already! I don't know if it was of any help, but this is something I would also like to know the answer of.