How to create nested objects in GraphQL mutation? - facebook-graph-api

In GraphQL, I have two types, Author and Quotes, as follow:
type Author {
id: Int!
name: String!
last_name: String!
quotes: [Quote!]!
}
type Quote {
id: Int!
author: Author!
quote: String!
}
In the implementation an Author and a Quote could be created individually.
But I want to add the functionality to create the author and multiple quotes in the same request as follows:
mutation{
createAuthor(author:{
name:"Kent",
last_name:"Beck",
quotes:[
{
quote: "I'm not a great programmer; I'm just a good programmer with great habits."
},
{
quote: "Do The Simplest Thing That Could Possibly Work"
}
]
}) {
id
name
quotes{
quote
}
}
}
If the client want to merge the creation as shown above, what is the most perfect way of doing it?
The current implementation for author creation with multiple quotes is as follow:
resolve (source, args) {
return models.author.build({
name: args.author.name,
last_name: args.author.last_name
}).save().then(function(newAuthor) {
const quotes = args.author.quotes || [];
quotes.forEach((quote) => {
models.quote.create({
author_id: newAuthor.id,
quote: quote.quote,
});
});
return models.author.findById(newAuthor.id);
});
}
Can I somehow invoke the Quotes creation mutations automatically?

Related

How to query a list on AppSync GraphQL by a nested object

I am using AWS AppSync GraphQL and am trying to filter a list by a nested object's value.
My schema looks like this:
type Post #model {
id: ID
title: String
content: String
hidden: Boolean
}
type PinnedPost #model
{
id: ID!
userID: ID #index(name: "byUser", sortKeyFields: ["postID"])
user: User #hasOne (fields: ["userID"])
postID: ID
post: Post #hasOne (fields: ["postID"])
}
I would like to run a query to list the PinnedPost for a user, but filter out the hidden ones, like so:
const pinnedData = await API.graphql(graphqlOperation(
listPinnedPosts, {
filter: {
userID: {
eq: userInfo.attributes.sub
},
post: {
hidden: {
eq: false
},
}
}
}
))
I have updated the filterinput in my Schema through the AppSync Console to:
input ModelPinnedPostFilterInput {
id: ModelIDInput
userID: ModelIDInput
postID: ModelIDInput
post: ModelPostFilterInput
and: [ModelPinnedPostFilterInput]
or: [ModelPinnedPostFilterInput]
not: ModelPinnedPostFilterInput
}
There are no errors associated with it, but the nested filter is not being applied as it will return both true and false values for hidden.
This question was sort of answered before:
Appsync & GraphQL: how to filter a list by nested value
but it is not clear to me where I am supposed to edit the mapping template to allow this. How can I achieve this result?

Should I extend my type with extra fields or create a new type?

I have TODO templates which are used to create todos in todo lists.
Should I create one type for both templates and created todos like this:
type TODO {
id: ID
text: String
type: String
templateId: ID
completed: Boolean
}
type TODOList {
id: ID
todos: [TODO]
owner: String
}
Or should I make separate types for templates and created todos?
type TODO {
id: ID
text: String
type: String
templateId: ID
completed: Boolean
}
type TODOTemplate {
id: ID
templateType: String
templateText: String
}
type TODOList {
id: ID
todos: [TODO]
owner: String
}
Or maybe there is more convenient method and I am doing everything wrong?
the second one looks better since you've introduced extra fields and probably want access to them. in that case you'll want to revise your TODO type:
type TODO {
id: ID
text: String
type: String
template: TODOTemplate
completed: Boolean
}

How Do I Construct my Ember Models and API Data to represent data on the relationship?

I'm new to jsonapi and how the structure works and trying to get a relationship to load properly. I'm expecting ember-data to follow provided url's in the relationship.links of my objects to fetch the required information but I'm getting unexpected results.
I have Users, Territories, and a User/Territory relationship defined like this:
// User Model
const UserModel = DS.Model.extend({
username: DS.attr('string'),
territories: DS.hasMany('user-territories')
}
// Territory Model
const TerritoryModel = DS.Model.extend({
name: DS.attr('string')
}
// User-Territory Model
const UserTerritoryModel = DS.Model.extend({
notes: DS.attr('string'),
startDate: DS.attr('date'),
user: DS.belongsTo('user'),
territory: DS.belongsTo('territory')
}
I then have mock data (using http-mock) that looks like this:
// api/users/
data: {
type: "users",
id: 1,
attributes: {
username: "thisIsMyUsername"
}
},
relationships: {
territories: {
links: {
self: "http://localhost:4200/api/users/1/territories"
}
}
}
// api/users/1/territories
data: {
type: "user-territories",
id: 1,
attributes: {
notes: "This is a note",
startDate: "2017-01-01"
}
},
relationships: {
user: {
link: {
self: "http://localhost:4200/api/users/1"
}
},
territory: {
link: {
self: "http://localhost:4200/api/territories/1"
}
}
}
// api/territories/1
data: {
type: "territories",
id: 1,
attributes: {
name: "Territory #1"
}
}
In my User route, I want to request the UserModel and have access to the UserTerritory Relationship data and the Territory itself. The api calls are not what I expect though:
this.get('store').findRecord('user', 1, { include: "territories" });
EXPECTED:
api/users/1
api/users/1/territories
ACTUAL:
api/users/1
api/users/1?include=territories
If I call the user-territories model I get this:
EXPECTED:
api/users/1/territories
ACTUAL:
api/user-territories/1
If you use included, ember-data basically thinks you want to tell the server to side-load data. If you return a links, just resolve the relationship. However the relationships have to be inside the data. Also the self link is for the relationship itself, to return the data use related.
So first you do something like user = store.findRecord('user', '1'), this will fetch to api/users/. Then you should return something like this:
// api/users/
{
data: {
type: "users",
id: 1,
attributes: {
username: "thisIsMyUsername"
}
relationships: {
territories: {
links: {
related: "http://localhost:4200/api/users/1/territories"
}
}
}
}
}
Next you do user.get('territories'). This will return a promise, and fetch http://localhost:4200/api/users/1/territories, or whatever was inside that related link. However know, what ember-data will expect you to return user-territories here, because thats what you specified with territories: DS.hasMany('user-territories'). You should know what you directly can model an many-to-many relationship in ember-data without a third table.

EmberJS: how to remove records with many-to-many relationship

I stumbled across an issue when I tried to remove a record whose model is in many-to-many relationship to another.
I have Book:
App.Book = DS.Model.extend({
title: DS.attr('string'),
isbn: DS.attr('string'),
category: DS.attr('string'),
publishDate: DS.attr('date'),
authors: DS.hasMany('author', {inverse: 'books'})
});
as well as Author:
App.Author = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
birthDate: DS.attr('date'),
books: DS.hasMany('book', {async: true, inverse: 'authors'})
});
I am removing books like this:
actions: {
delete: function (book) {
var authors = book.get('authors')
authors.forEach(function(author) {
var books = author.get('books')
books.forEach(function(book){
console.log(book.toJSON());
})
books.removeObject(book);
//books.save doesn't work
})
book.destroyRecord();
this.transitionToRoute('books.index');
},
and it correctly removes this book, with DELETE request to backing REST server, but there are no PUT requests for all those authors which had this book in their 'books' collection. When I change view to authors and go back to books one dummy book is created with id of the one I previously removed and 'authors' set to old author as well, other properties are undefined.
How do I correctly remove books so that authors are updated as well?
You have to remember that DS.hasMany returns a promise, so it has to be resolved before you can use it:
actions: {
delete: function (book) {
if (!book.get('isDeleted')) {
book.destroyRecord()
.catch(function(error) {
console.log(error);
book.rollback();
})
.then(function(book) {
book.get('authors').then(function (authors) {
authors.mapBy('books').forEach(function(books) {
if (typeof books.then === "function") {
books.then(function(books) {
books.removeObject(book);
});
} else {
books.removeObject(book);
}
});
});
});
}
this.transitionToRoute('books.index');
}
},
Looks like I've solved it.
First issue was that on the server-side of my project I incorrectly populated data - the ids were wrong and didn't match those from their relation.
Another thing was saving incorrect object. I was trying to save only the array with books, had to save whole author instead (actually that makes sense). Working action is this:
console.log('Removing book: ' + book.get('id') + book.get('title'));
var authors = book.get('authors')
var id = book.get('id');
authors.forEach(function (author) {
var books = author.get('books').removeObject(book);
author.save();
})
book.destroyRecord();
this.transitionToRoute('books.index');
My only doubt was that the delete action is defined in BookController, whose model is the book I am trying to delete, so I shouldn't put the book in a parameter but use controller's model instead. And I've just found that it's easily achievable by just removing the parameter and declaring book as this.get('model'), that's why the final, working solution is this:
delete: function () {
var book = this.get('model')
console.log('Removing book: ' + book.get('id') + book.get('title'));
var authors = book.get('authors')
var id = book.get('id');
authors.forEach(function (author) {
var books = author.get('books').removeObject(book);
author.save();
})
book.destroyRecord();
this.transitionToRoute('books.index');
}

Can the Knockout Concurrency plugin track newly added or deleted rows?

I'm trying to use the Knockout Concurrency plugin in my project, and I'm currently fiddling with the example code, but I'm not getting it to work:
https://github.com/AndersMalmgren/Knockout.Concurrency/wiki/Getting-started
ViewModel = function() {
this.name = ko.observable("John").extend({ concurrency: true});
this.children = [{ name: ko.observable("Jane").extend({concurrency: true })}, { name: ko.observable("Bruce").extend({concurrency: true })}];
this.getData = function() {
//Simulate backend data
var data = { name: "John Doe", children: [{ name: "Jane Doe"},{ name: "Bruce Wayne"}, { name: "New row"}]};
new ko.concurrency.Runner().run(this, data);
}
}
ko.applyBindings(new ViewModel());
http://jsfiddle.net/rCVk4/3/
Nothing happens and the newly added item is not tracked by the plugin, does anyone know why?
Thanks for trying out my Plugin, really fast too, I uploaded the code today!
The plugin does indeed support tracking of deleted and added rows. But for it to know which rows are what It needs you to supply it with a mapper
var mappings = {
children: {
key: function(item) {
return ko.utils.unwrapObservable(item.id);
},
create: function(data) {
return { id: data.id, name: data.name };
}
}
};
The name children corresponds to the name of the array.
The Key method is used to identify the property used as an identifier.
The Create method is used to create new rows (Added rows).
You can download the MVC3 sample from Github for a fully featured Demo, also please try out this Fiddle
http://jsfiddle.net/7atZT/