Dynamically creating graphql schema with circular references - graphql

By using graphql-js, I need to create graphql schema dynamically by iterating over array of some data, for example:
[{
name: 'author',
fields: [{
field: 'name'
}, {
field: 'books',
reference: 'book'
}]
}, {
name: 'book',
fields: [{
field: 'title'
}, {
field: 'author',
reference: 'author'
}]
}]
The problem is circular references. When I'm creating AuthorType I need BookType to be already created and vise versa.
So resulting schema should look like:
type Author : Object {
id: ID!
name: String,
books: [Book]
}
type Book : Object {
id: ID!
title: String
author: Author
}
How can I solve this?

Quoted from official documentation
http://graphql.org/docs/api-reference-type-system/
When two types need to refer to each other, or a type needs to refer
to itself in a field, you can use a function expression (aka a closure
or a thunk) to supply the fields lazily.
var AddressType = new GraphQLObjectType({
name: 'Address',
fields: {
street: { type: GraphQLString },
number: { type: GraphQLInt },
formatted: {
type: GraphQLString,
resolve(obj) {
return obj.number + ' ' + obj.street
}
}
}
});
var PersonType = new GraphQLObjectType({
name: 'Person',
fields: () => ({
name: { type: GraphQLString },
bestFriend: { type: PersonType },
})
});
Also look at this related answer of circular Category-Subcategory types

I solved this problem by using a thunk for the fields field.
const User = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: { type: GraphQLID }
})
});
When you make your fields a thunk rather than an object literal you can use types that are defined later in the file.
Please see this post for more info Is there a way to avoid circular type dependencies in GraqhQL?
Based on that post I think this is the correct way to do it.

Related

Mongoose: Get value of object property in array inside a required mongoose model schema function

I'm creating a support ticket system for a CRM. Every message inside the conversation array must have a customer or employee attached to it.
...
conversation: [
{
message: {
type: String,
required: [true, 'Please add a message']
},
customer: {
type: String,
ref: Customer,
required: [function() {
return this.employee === undefined;
}, 'Please add a employee or customer']
},
employee: {
type: String,
ref: Employee,
required: [function() {
return this.customer === undefined;
}, 'Please add a employee or customer']
},
created_at: {
type: Number,
default: Date.now()
}
}
],
...
This fails because this.employee and this.customer are both undefined as they are part of an array. Is there a way to rework the required function to work with the array part? Something like this.conversation.?.employee

How to properly create the Id field on a graphql to be relay compliant?

I am following a relay+graphql tutorial and I cam confused with this code:
const GraphQLTodo = new GraphQLObjectType({
name: 'Todo',
fields: {
id: globalIdField('Todo'),
text: {
type: GraphQLString,
resolve: (obj) => obj.text,
},
complete: {
type: GraphQLBoolean,
resolve: (obj) => obj.complete,
},
},
interfaces: [nodeInterface],
});
On the resolve of text and complete field, where does the obj come from? is obj parameter from the query? and also, how do I resolve id from the query? do I do not have to resolve it? for example this is the id field code:
id: {
id: globalIdField('Todo'),
resolve: (obj) => obj.id,
}
Will the above code work and isn't it redundant? how do I properly create the Id field to be relay compliant?
obj is the record itself, usually in whatever format you get from your persistent storage. In this case, it's a Todo object, with at least two properties: text and complete. This makes the resolve functions trivial. Resolve functions exist so you can do more complex things like the following:
fullName: {
type: GraphQLString,
resolve: (obj) => obj.firstName + ' ' + obj.surname,
}
For the Relay compliant id, I don't think you need to do anything. globalIdField does it for you.

How to set custom schema for custom remote methods on Strongloop

I'm newbie on Strongloop and I can't find information for how to customize my response class (model schema for a object I built) and I don't know how to show on the API explorer the object with custom data.
For example, I have a custom remote method called score
POST /Challenges/score
I want to show for the parameter data a custom model schema instead of single parameters, not the Model Schema for Challenge, the data on the body have all the parameters and show to the user on the Data Type: Model Schema, is this possible?
{
"id": "string",
"limit": 0,
"order": "string",
"userId": "string"
}
On the other side, in Response Class I want to show the schema for the response object. Something like this:
{
"id":"string",
"userId":"string",
"user": {},
"totalScore":0,
"tags": []
}
I looked different questions (this and this), but can not find something to solve this issues.
Update
Here is the definition of the remote method
Challenge.remoteMethod('score', {
accepts: { arg: 'data', type: 'object', http: { source: 'body' } },
returns: {arg: 'scores', type: 'array'},
http: {path: '/score', verb: 'post'}
});
I believe you might have gone through the official docs of strongloop. If not, here is the link that explains the remote methods and their accepted data types. https://docs.strongloop.com/display/public/LB/Remote+methods
Assuming your custom object is Challenge, to show the object in response you have to specify the type( the type can be one of the loopback's data type or you custom model). So to return Challenge you have to add following code :
Challenge.remoteMethod('score', {
accepts: { arg: 'data', type: 'object', http: { source: 'body' } },
returns: {arg: 'scores', type: 'Challenge', root: true},
http: {path: '/score', verb: 'post'},
});
The second arrow that you have specified is the default values that you want to try out with your API call. You can pass any custom string with default as the key.
For example, if you want to pass some object :
Challenge.remoteMethod('score', {
accepts: {
arg: 'data',
type: 'object',
default: '{
"id": "string",
"userId": "string",
"user": {},
"totalScore": 0,
"tags": []
}',
http: {
source: 'body'
}
},
returns: {
arg: 'scores',
type: 'Challenge'
},
http: {
path: '/score',
verb: 'post'
}
});
So, for response you can not customize the model. But to pass default values you can put anything in the string format.
In loopback, remote arguments can identify data models which have been defined using ds.define('YourCustomModelName', dataFormat);
so for you case, write a function in a Challenge.js file which will have a remote method (in ur case score) defined.
const loopback = require('loopback');
const ds = loopback.createDataSource('memory');
module.exports = function(Challenge) {
defineChallengeArgFormat() ;
// remote methods (score) defined
};
let defineChallengeArgFormat = function() {
let dataFormat = {
"id": String,
"userId": String,
"user": {},
"totalScore": Number,
"tags": []
};
ds.define('YourCustomModelName', dataFormat);
};
Under remote arguments type use 'type': 'YourCustomModelName'
Challenge.remoteMethod('score', {
accepts: {
arg: 'data',
type: 'YourCustomModelName',
http: {
source: 'body'
}
},
returns: {
arg: 'scores',
type: 'Challenge'
},
http: {
path: '/score',
verb: 'post'
}
});
You should see it reflecting on explorer after restarting server and refreshing :)
#jrltt, Instead of using default, use object structure pointing to type under accepts and it should work. Note, http source:body is needed.
With random object:
Challenge.remoteMethod('score', {
accepts: {
arg: 'data',
type: {
"id": "string",
"userId": "string",
"user": {},
"totalScore": 0,
"tags": []
},
http: {
source: 'body'
}
},
returns: {
arg: 'scores',
type: 'Challenge'
},
http: {
path: '/score',
verb: 'post'
}
});
With a defined model which is available in model-config or create using loopback model generator, then that model name can be used to point type.
So lets use User model to show in accepts parameter,
Challenge.remoteMethod('score', {
accepts: {
arg: 'data',
type: 'User',
http: {
source: 'body'
}
},
returns: {
arg: 'scores',
type: 'Challenge'
},
http: {
path: '/score',
verb: 'post'
}
});
The way I found to solve this problem is to create a new model in this way, with the helper slc loopback: model
? Enter the model name: ArgChallenge
? Select the data-source to attach ArgChallenge to: (no data-source)
? Select model's base class PersistedModel
? Expose ArgChallenge via the REST API? No
? Common model or server only? server
And I continue putting properties, then on Challenge.js:
Challenge.remoteMethod('score', {
accepts: { arg: 'data', type: 'ArgChallenge', http: { source: 'body' } },
returns: {arg: 'scores', type: 'array'},
http: {path: '/score', verb: 'post'}
});
And that works! If anyone knows a better way to do this, please share.
I found a way to solve this problem by changing the type parameter in the accepts array.
when we create remoteMethod; we provide accepts array. there is arg, type, required, http. then we can give our request object into type parameter.
Example code
UserModel.remoteMethod(
'login',
{
description: 'Login a user with username/email and password.',
accepts: [
{
arg: 'credentials',
type: {'email': 'string', 'password': 'string'},
required: true,
http: {source: 'body'},
},
{
arg: 'include', type: ['string'], http: {source: 'query'},
description: 'Related objects to include in the response. ' +
'See the description of return value for more details.',
},
],
returns: {
arg: 'accessToken', type: 'object', root: true,
description:
g.f('The response body contains properties of the {{AccessToken}} created on login.\n' +
'Depending on the value of `include` parameter, the body may contain ' +
'additional properties:\n\n' +
' - `user` - `U+007BUserU+007D` - Data of the currently logged in user. ' +
'{{(`include=user`)}}\n\n'),
},
http: {verb: 'post'},
},
);

Configuring Sencha's Rest Proxy not to send model's ID on a POST

I have a simple Sencha Touch 2.1 model class:
Ext.define('App.model.User', {
extend: 'Ext.data.Model',
config: {
fields: [
{ name: 'id', type: 'number', defaultValue: 0 },
{ name: 'first', type: 'string' },
{ name: 'last', type: 'string' },
{ name: 'email', type: 'string' },
{ name: 'lastUpdated', type: 'auto' }
]
},
fullName: function () {
var d = this.data,
names = [d.first, d.last];
return names.join(" ");
}
});
A collection of these models is in a store that I've configured to use sencha's Rest Proxy. When I add a new model to the store, and call the Sync method on the store, the new model is posted to a ASP.NET WebAPI Users controller and the following action is hit:
// POST api/Default1
public HttpResponseMessage PostUser(User user)
{
// this is a new user -- get rid of the ID
user.Id = 0;
if (ModelState.IsValid)
{
db.Users.Add(user);
db.SaveChanges();
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, user);
response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = user.Id }));
return response;
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
The problem is that the a string is being sent as the ID for the model, so it's not passing the ModelState.IsValid check on the controller. This is what's actually getting sent up:
{"id":"ext-record-5","first":"c","last":"d","email":"e","lastUpdated":null}
Any idea why the id field is being set to a string? Also, any idea how I can tell the post action in the Users controller not to validate the id field (as it should be handling creating a new item, it makes sense for the server to create the ID for the new item, not the client).
This link helped me figure it out. The updated model should be:
Ext.define('App.model.User', {
extend: 'Ext.data.Model',
config: {
fields: [
{ name: 'id', type: 'auto', persist: false },
{ name: 'first', type: 'string' },
{ name: 'last', type: 'string' },
{ name: 'email', type: 'string' },
{ name: 'lastUpdated', type: 'auto' }
],
},
fullName: function () {
var d = this.data,
names = [d.first, d.last];
return names.join(" ");
}
});
You need to set idProperty on the model. The field for id should also be set to type auto. See documentation here.

How to validate a login page using sencha

HI Ive created a login page using sencha. But i am not sure as to how i can use a validation to use along with sencha. I would like to do a javascript validation. If so how i should do it in such a way that submit can be used to call a function. Also if there is proper validation with sencha itself please let me know.
Many thanks
Roy M J
You can add validation while you are creating a store as shown
Ext.regModel('Note', {
idProperty: 'id',
fields: [
{ name: 'id', type: 'int' },
{ name: 'date', type: 'date', dateFormat: 'c' },
{ name: 'title', type: 'string' },
{ name: 'narrative', type: 'string' }
],
validations: [
{ type: 'presence', field: 'id' },
{ type: 'presence', field: 'title', message: 'Please enter a title for this note.' }
]
});
and use this on submission
var errors = currentNote.validate();
if (!errors.isValid()) {
Ext.Msg.alert('Wait!', errors.getByField('title')[0].message, Ext.emptyFn);
return;
}
You can use this tuitorial by Jorge for more clarity
http://miamicoder.com/2011/writing-a-sencha-touch-application-part-1/
Hope it will help...

Resources