Ember model relationship not matching API - ember.js

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.

Related

Define graphQLSchema properly in Node.js

Doing graphQL first time.I searched for resources but could not found a helpful one.
I have written the following schema, got some help from another stackoverflow post.
schema.js
function getDataFromUrl(){
return [
{
"EventCode": "ET00029280",
"EventType": "CT",
"EventTitle": "OYSTERS Beach Park",
"VenueName": "Newexcelsior",
"VenueRegion": "Mumbai"
},
{
"EventCode": "ET00030629",
"EventType": "CT",
"EventTitle": "Stand-Up Comedy: The Trial Room",
"VenueName": "Newexcelsior",
"VenueRegion": "Mumbai"
}
];
}
const eventType = new GraphQLObjectType({
name: 'Event',
fields: {
EventTitle: {
type: GraphQLString,
description: 'Event Title'
},
},
});
const eventListType = new GraphQLObjectType({
name: 'EventList',
fields: {
events: {
type: new GraphQLList(eventType),
description: 'List of items',
},
},
});
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
eventList: {
type: new GraphQLList(eventListType),
resolve: () => getDataFromUrl(),
}
}
})
});
module.exports = schema;
When I query
{
eventList {
events {
EventTitle
}
}
}
I get this response:
{
"data": {
"eventList": [
{
"events": null
},
{
"events": null
}
]
}
}
I am expecting some changes in my schema, however my desired response is
{
"data": [
{
"EventTitle": "OYSTERS Beach Park"
},
{
"EventTitle": "Stand-Up Comedy: The Trial Room"
}
]
}
Please also suggest some links where I learn basics.
It looks like what's tripping you up the most right now is how you're defining a list. There's no need to define a separate type called EventList -- when you specify GraphQLList(someOtherType) you are already telling GraphQL to expect an array of that particular type. Your current Schema is expecting an array of an array of types. Because the structure of the data you're passing in doesn't match your schema, GraphQL can't find a field called EventTitle to match against and so it's returning null.
The fix in this case is to just get rid of eventListType altogether and change the type of your eventList field to eventType instead.
The docs for GraphQL.js are probably going to be your best bet as far as learning the basics. The only problem is the examples they include are way too basic. Here is a list of projects on GitHub that you can checkout to see GraphQL in action.
If you are starting out, I would also highly recommend using Apollo's graphql-tools. Even if you don't use Apollo on the client-side, graphql-tools makes it ridiculously easy to set up the server. Your schema would be much more readable, since you would write it as string rather than an object like you do in vanilla GraphQL.js. And you can easily set up a GraphiQL endpoint in addition to your GraphQL one, which makes debugging much easier :)

Not able to show errors with Ember 2

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)

Ember save() throws JSON.parse error: "unexpected end of data at line 1 column 1 of the JSON data"

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]
]
]
}
}]});
});

Ember Data serialize relationship without parent ID

I'm building an adapter to wrap the Keen.io API, so far I've been able to successfully load the projects resource, however the returned object looks like this:
{
partners_url: "/3.0/projects/<ID>/partners",
name: "Project Name",
url: "/3.0/projects/<ID>",
saved_queries: [ ],
events_url: "/3.0/projects/<ID>/events",
id: "<ID>",
organization_id: "<ORG ID>",
queries_url: "/3.0/projects/<ID>/queries",
api_key: "<API KEY>",
events: [
{
url: "/3.0/projects/<ID>/events/user_signup",
name: "user_signup"
},
{
url: "/3.0/projects/<ID>/events/user_converted",
name: "user_converted"
},
{
url: "/3.0/projects/<ID>/events/user_created_project",
name: "user_created_project"
}
]
}
Excluding a lot of cruft, Ember has no trouble mapping the name and id attributes using the RESTSerializer, but if I add an events relation to my Project model it blows up with:
Error while loading route: TypeError: Cannot set property 'store' of undefined
at Ember.Object.extend.modelFor (http://localhost:3000/assets/ember-data.js?body=1:9813:23)
at Ember.Object.extend.recordForId (http://localhost:3000/assets/ember-data.js?body=1:9266:21)
at deserializeRecordId (http://localhost:3000/assets/ember-data.js?body=1:10197:27)
at deserializeRecordIds (http://localhost:3000/assets/ember-data.js?body=1:10211:9)
at http://localhost:3000/assets/ember-data.js?body=1:10177:11
at http://localhost:3000/assets/ember-data.js?body=1:8518:20
at http://localhost:3000/assets/ember.js?body=1:3404:16
at Object.OrderedSet.forEach (http://localhost:3000/assets/ember.js?body=1:3247:10)
at Object.Map.forEach (http://localhost:3000/assets/ember.js?body=1:3402:10)
at Function.Model.reopenClass.eachRelationship (http://localhost:3000/assets/ember-data.js?body=1:8517:42)
From my investigation this seems to be because it can't find the inverse relation to map an Event back to a Project because there's no parent ID.
Is it possible to create a relation in Ember Data to support this? Or is it possible to modify the Serializer to append a projectId to each event before loading?
I'm using Ember 1.5.0-beta.4 and Ember Data 1.0.0-beta.7+canary.f482da04.
Assuming your Project model is setup the following way:
App.Project = DS.Model.extend({
events: DS.hasMany('event');
});
You need to make sure that the JSON from your API is in a certain shape that Ember-Data expects.
{
"project": {
"id": 1,
"events": ["1", "2"],
},
"events": [{
"id": "1",
"name": "foo"
}, {
"id": "2",
"name": "bar"
}]
}
You can, however, implement extractArrayin your model's serializer to transform the JSON from the server into something similar like the above example.
There's a working example and an explanation in the Ember docs.

Ember Data nested Models

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.