Multi-level include filter with LoopBack JS - loopbackjs

My problem is that I can't figure out how to get multilevel relations structures in one request with LoopBack backend. I have 3 models: Continent, Country, County. What I would like to do is to GET a continent, and recieve all the countries, and all the counties within.
The relationship between them:
Continent hasMany Country, and Country belongsTo Continent
Country hasMany County, and County belongsTo Country
So the REST api call to /api/Continent/1 returns
{
"id": 1
"name":"Europe"
}
Now, I want to get all the countries and counties with the Continent, so I do a query to /api/Continent/1?filters[include]=country
Still, I don't get the countys.
What kind of query should I make to get a list which includes both relation levels? Like this:
{
"id": 1,
"name": "Europe",
"country": [
id: 1,
name:"United Kingdom",
county:[
{id:1,name:"Avon"},
{id:2,name:"Bedfordshire"},
...
],
...
]
}
Thanks for your help!

The syntax is:
/api/Continent/1?filter={"include": {"country": "countys"}}

Hello hoping it's not too late for an answer. After a thorough flipping of their docs inside and out on this issue, I ended up writing a remote method to achieve that deep level multiple includes. It's not so clear how to go about it at the REST api level.
Continent.listContinents = function(limit,skip,order,cb) {
Continent.find({
limit:limit,
skip:skip,
order:order,
include:[{country:'county'}],
}, cb);
};
Continent.remoteMethod('listContinents', {
description:"List continents. Include the related country and county information",
returns: {arg: 'continents', type: 'array'},
accepts: [{arg: 'limit', type: 'number', http: { source: 'query' }},
{arg: 'skip', type: 'number', http: { source: 'query' }},
{arg: 'order', type: 'string', http: { source: 'query' }}],
http: {path:'/list', verb: 'get'}
});
I have added some additional query string parameters limit, order and skip to enable pagnination and ordering..but not a must :)
Also this is assuming you already have relation types defined between Continent and Country then Country and County.

Related

Amplify AppSync: custom sorting and filtering with pagination

I'm trying to write a schema so that I can query models filtered by multiple keys, sorted by a custom key and paginated.
an example of my model:
type Article {
id: ID!
category: String!
area: String!
publishOn: AWSDate!
}
And an example of the query I would like to do is: retrieve all the Articles which are part of both a given category AND area, returned in descending order by publishOn in chunks of 10 items each (to implement pagination server-side, and have a lightweight UI).
The response should include also the nextToken attribute that can be used to load the "next" page of the filtered articles list.
I have multiple problems with what I can do with the automatically generated schema and can't find a way to implement manually a solution that works for all what I want to do. I try and make a list of what goes wrong:
Filtering
Let's say I want to query 10 articles that belong to the category "Holiday":
listArticles(filter: {category: {eq: "Holiday} }, limit: 10)
I won't get the first 10 articles that match that category, but instead, it seems that AppSync selects the first 10 items in the table, and then it filters these 10 items by the filter criteria.
In other words, it seems that the sequence in which filtering and sorting are applied is the opposite of what expected. Expected: firstly filter the table by the filter critaria, then return the first 10 items of the filtered result sets.
Sorting
I couldn't find a way to add sorting with AppSync, so I added searchable:
type Article (
#searchable
) {
id: ID!
category: String!
area: String!
publishOn: AWSDate!
}
Now if I sort by date, that key will be used as nextToken and brake the pagination. This is a known issue: https://github.com/aws-amplify/amplify-cli/issues/4434
Do you have any good tip on how to find a workaround to these bugs? I dag into the documentation and in couple of issue, but didn't come up with a solution that works well...
Thanks in advance,
Matteo
Filtering
You will need a Global Secondary Index in DynamoDB to achieve such a behaviour. You can create them with the #key annotation. I your case I would create a composite key consisting of the category for the partition key and area and publishOn as the sort key(s).
type Article
#model
#key(fields: ["id"])
#key(name: "byCategory", fields: ["category", "publishOn"])
#key(name: "byCategoryArea", fields: ["category", "area", "publishOn"])
{
id: ID!
category: String!
area: String!
publishOn: AWSDate!
}
Sorting
Sorting is done by the sortDirection property which is either DESC or ASC and can only be done on the sort key.
The #searchable directive enables elasticsearch on the table, which is a fulltext search engine and probably a bit pricy for small applications and wouldn't be required here unless you would want to query based on e.g. the article description text.
listArticles(filter: {category: {eq: "Holiday"} }, limit: 10, sortDirection: DESC)
Amplify AppSync: filtering with pagination
let allClubsList = async (sport) => {
try {
let clubsList;
let clubsInfoList = [];
let nextTokenInfo = null;
do{
let clubs = await client.query({
query: gql(clubBySportStatus),
variables: {
sport: sport,
eq: { status: "ACTIVE" },
},
limit: 100,
nextToken: nextTokenInfo,
fetchPolicy: "network-only",
});
clubsList = clubs.data.clubBySportStatus.items;
clubsList.forEach((item) => clubsInfoList.push(item));
nextTokenInfo = clubs.data.clubBySportStatus.nextToken;
} while (Boolean(nextTokenInfo));
if (clubsInfoList && clubsInfoList.length) {
return {
success: true,
data: clubsInfoList,
};
}
} catch (eX) {
console.error(`Error in allClubsList: ${JSON.stringify(eX)}`);
return {
success: false,
message: eX.message,
};
}
};

How to create register with relation model embeded?

I'm using Loopback 3 to create my application, with Postgres.
I've created a many-to-many usign hasManyThrough relation like this:
Product has many Composition
Ingredient has many Composition
Product has many Ingredient
Composition belongs to Product
Composition belongs to Ingredient
How can I create/edit a Product with a array of Ingredient's id, like:
POST /products
{
name: "Potato Chips",
ingredients: [ 5, 7, 3, 20 ]
}
And how can I get Product with list of Ingredients embeded?
GET /products/1
{
id: 1,
name: "Potato Chpis",
ingredients: [
{ name: "Potato" },
{ name: "Vegetal Oil" }
...
]
}
1/ To create a Product with many Ingredients, I suggest to make a custom remote that takes your product in the body along with the ingredients, and make a loop over the ingredients to link them to your product one by one, using the add method (assuming that Product has many Ingredients through Composition):
Product.ingredients.add(ingredientData, callback);
2/ To get a Product with its embedded list of Ingredients, you need to include the relation property to your GET route (example given in reactjs):
const response = await Axios.get(`/api/products/${productId}`, {
params: {
filter: {
include: [
{ relation: 'ingredients' },
],
},
},
});

Loopback - Creating a get request with fields

Hello and thanks for taking your time to help me,
So I'm new to loopback, I'd like to create a request that retrieves all the data from a data source but only specifics fields.
I've read all the tutorials on the loopback guide, but I don't understand how to proceed.
Basically what i have is that :
XXXX.getUserWithXXXX = function(cb) {
cb(null, 'Greetings... ');
}
XXXX.remoteMethod('getUserWithXXXX', {
description: "Get all users who own a XXXX",
returns: {arg: 'greeting', type: 'string'},
fields: {id: true, email: true},
http: {path: '/getUserWithXXXX', verb: 'get'}
});
So first, what I want to create a request that will retrieve all the data from my model, so I could filter it
And then I don't know how to filter in the code.
If anyone has any hints I'd gladly take them.
put the GET filters in "accept" attribute and also use "fields" filter to return specific fields of the documents.
XXXX.getUserWithXXXX = function(id, email, cb) {
app.models.XXXX.find({where:{id:"id", email:"email"}, fields:{specific_field1:1, specific_field2:1}}, function(err, returnedUsers){
cb(err, returnedUsers)
})
}
XXXX.remoteMethod('getUserWithXXXX', {
description: "Get all users who own a XXXX",
returns: {arg: 'greeting', type: 'string'},
accepts: [{arg: "id",type:"string"}, {arg: "email", type:"string"}],
http: {path: '/getUserWithXXXX', verb: 'get'}
});

Apollo: Refetch queries that have multiple variable permutations after mutation

Let's say I have a table that lists a bunch of Posts using a query like:
const PostsQuery = gql`
query posts($name: string) {
posts {
id
name
status
}
}
`;
const query = apolloClient.watchQuery({query: PostsQuery});
query.subscribe({
next: (posts) => console.log(posts) // [ {name: "Post 1", id: '1', status: 'pending' }, { name: "Paul's Post", id: '2', status: 'pending'} ]
});
Then later my user comes along and enters a value in a search field and calls this code:
query.setVariables({name: 'Paul'})
It fetches the filtered posts and logs it out fine.
// [ { name: "Paul's Post", id: '2', status: 'pending'} ]
Now, in my table there is a button that changes the status of a post from 'Pending' to 'Active'. The user clicks that and it calls code like:
const PostsMutation = gql`
mutation activatePost($id: ID!) {
activatePost(id: $id) {
ok
object {
id
name
status
}
}
}
`;
apolloClient.mutate({mutation: PostsMutation});
All is well with the mutation, but now I want to refetch the table data so it has the latest, so I make a change:
apolloClient.mutate({
mutation: PostsMutation,
refetchQueries: [{query: PostsQuery, variables: {name: 'Paul'}]
});
Hurray, it works!
// [ { name: "Paul's Post", id: '2', status: 'active'} ]
But... now my user clears the search query, expecting the results to update.
query.setVariables({});
// [ {name: "Post 1", id: '1', status: 'pending' }, { name: "Paul's Post", id: '2', status: 'pending'} ]
Oh no! Because the data was not refetched in our mutation with our "original" variables (meaning none), we are getting stale data!
So how do you handle a situation where you have a mutation that may affect a query that could have many permutations of variables?
I had a similar issue, I am using Apollo with Angular, so I am not sure if this method will work with React Client, but it should.
If you look closely at refetchQueries properties on the mutate method, you will see that the function can also return a string array of query names to refetch. By returning just the query name as a string, you do not need to worry about the variables. Be advised that this will refetch all the queries matching the name. So if you had a lot queries with different variables it could end up being a large request. But, in my case it is worth the trade off. If this is a problem, you could also get access to the queryManager through apolloClient.queryManager which you could use to do some more fine grained control of what to refetch. I didn't implement it, but it looked very possible. I found the solution below fits my needs fine.
In your code, what you need to do is:
apolloClient.mutate({
mutation: PostsMutation,
refetchQueries: (mutationResult) => ['PostQueries']
});
This will refetch any query with the name 'PostQueries'. Again, it is possible to only refetch a subset of them if you dig into the queryManager and do some filtering on the active watch queries. But, that is another exercise.

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.