How to use Ember data / JSONAPI to represent a Map of objects - ember.js

I have a data model that includes a response that has a map of answers, and each answer is associated with a question. Response, Answer, and Question are all mapped entities. The Response object might look something like:
response: {
id: 4
date: 2015-4-6
answers: {
doesSmoke: {
id: 349,
value: 'no',
question: {
id: 700,
title: 'Do you smoke?'
}
},
favoriteSport: {
id: 32,
value: 'football',
question: {
id: 701,
title: 'What's your favorite sport?'
}
}
}
}
Because there are 300+ answer names, and they change all the time, I don't want to name them all in my model. I also don't want to just use DS.attr('') for "answers", because I want them to be represented as proper answer objects so that I can update them. So how can I have it as a map with keynames, so in my templates I can still say {{response.answers.doesSmoke.value}} or {{response.answers.[doesSmoke].value}} without having to name all the possible properties individually?
Apologies if I'm missing something obvious in the Ember docs -- the only way I can think of doing this now is to create an answers: DS.attr('') property, and then manually push separately loaded answer objects into it.

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 deal with nested state in apollo-link-state (similar to Redux's combineReducers)

If we look at the todos example, imagine that the application had multiple views (a TodoList page and another page).
So instead of "todos" directly referring to an array of todo items, at the top level of the state/store/cache it would actually just be a view with some of its own state.
Inside that view, we'd define the list of todo items and visibility filter - so the state/store/cache would NOT be looking like this:
{
todos: [TodoItem]
0:▾TodoItem:0
completed: false
id: 0
text: "hh"
visibilityFilter: "SHOW_ALL"
}
but as:
{
scenes: {
TodoList: {
todos: [TodoItem]
0:▾TodoItem:0
completed: false
id: 0
text: "hh"
visibilityFilter: "SHOW_ALL"
},
SomeOtherView: { /* other state */}
}
}
It might even be isolated in its own data "module", like proposed here: https://medium.com/#alexmngn/how-to-use-redux-on-highly-scalable-javascript-applications-4e4b8cb5ef38 :
{
scenes: {
TodoList: {
data: {
todos: [TodoItem]
0:▾TodoItem:0
completed: false
id: 0
text: "hh"
}
visibilityFilter: "SHOW_ALL"
},
SomeOtherView: { /* other state */}
}
}
application wide state would be store a level further out:
{
// App global state lives as long as the app
data: { /* App global relevant data */},
someglobalstate: true,
scenes: {
TodoList: { // "view state" lives as long as the view is active, and resets when navigated away from
data: {
todos: [TodoItem]
0:▾TodoItem:0
completed: false
id: 0
text: "migrate from redux to apollo-link-state"
}
visibilityFilter: "SHOW_ALL"
},
SomeOtherView: { /* other state */}
}
}
We can achieve this easily with reducer composition in Redux, like this:
Starting from the inside: todos would have its own reducer which is combined in the data reducer, which is combined in the TodoList reducer with the key "data". The TodoList reducer would then again be combined in the scenes reducer and so forth up to the top, to make the nested state reflect the folder structure of the project.
But how would something like this be possible with apollo-link-state and resolvers without defining everything in a single "TodoList" resolver?
Additional question:
How would you clear the TodoList state once you navigate away? In Redux I guess you'd trigger an actions which would clear the given slice of the state.
P.S.
"apollo-link-state" & "apollo-link" tags are missing in stackoverflow. Maybe someone with rep > 1500 could add those?
I have the same question. It seems that apollo-link-state expect a function at the top level of the resolver, so it is not possible to created nested structures as it would be in a Redux store.
As the introduction post says, though, it is expected that apollo-link-state would manage only roughly 20% of the state, the rest being fetched data from the GraphQL Server. So it might not make sense to split the local state as much as it makes senses to split a Redux store.
For now, I've settled on using prefixes for the main domains of the local state.

What Type of Firebase Data Structure Is Better?

I'm seeing mixed tutorials in Firebase that recommends to structure data like this:
posts: {
post_1: {
title: 'Some Title',
comments: {
comment_1: true,
comment_2: true,
...
comment_x: true
}
}
}
comments: {
comment_1: {
name: 'Foo'
},
comment_2: {
name: 'Bar'
},
...
comment_x: {
name: 'X'
}
}
and
posts: {
post_1: {
title: 'Some Title',
}
}
comments: {
post_1: {
comment_1: {
name: 'Foo'
},
comment_2: {
name: 'Bar'
},
...
comment_x: {
name: 'X'
}
}
}
I think the latter is better in terms of speed when querying, bulk writing, and security flexibility. Especially that when you have the 1st data structure and you query over the blogs just to find out it's title. It's gonna load tons of data if you have a million comments even if the value is just true (unless I'm missing something here).
My question is, for heavy data like those in social networks, is the 2nd data structure really that better compared to the 1st one? I'm not even convinced that the 1st one is better at any area than the 2nd one at all.
I'm torn because some Firebase tutorial uses the 1st data structure and I'm using the Emberfire web library from Firebase which enforces it if you want to completely embrace the library.
The second example is shallow(er) which is good for Firebase. The data is separated, again a good thing. The only downside (which really isn't a downside) is if you want to hit the database only once for the post and associated comments, instead of twice as in the second example; once for the post and again for the comments. Obviously #1 wins there but other than that #2 is the way to go.

How to Model.fetch(<object>) when the returned data is a single object

I want to make an API call for searching that looks like this:
https://myapi.com/search/<query>/<token>
where query is the search term and token (optional) is an alphanumeric set of characters which identifies the position of my latest batch of results, which is used for infinite scrolling.
This call returns the following JSON response:
{
"meta": { ... },
"results" {
"token": "125fwegg3t32",
"content": [
{
"id": "125125122778",
"text": "Lorem ipsum...",
...
},
{
"id": "125125122778",
"text": "Dolor sit amet...",
...
},
...
]
}
}
content is an array of (embedded) items that I'm displaying as search results. My models look like this:
App.Content = Em.Model.extend({
id: Em.attr(),
text: Em.attr(),
...
});
App.Results = Em.Model.extend({
token: Em.attr(),
content: Em.hasMany('App.Content', {
key: 'content',
embedded: true
})
});
In order to make that API call, I figured I have to do something like this:
App.Results.reopenClass({
adapter: Em.RESTAdapter.create({
findQuery: function(klass, records, params) {
var self = this,
url = this.buildURL(klass) + '/' + params.query;
if (params.token) {
url += '/' + params.token;
}
return this.ajax(url).then(function(data) {
self.didFindQuery(klass, records, params, data);
return records;
});
}
}),
url: 'https://myapi.com/search',
});
then somewhere in my routes do this:
App.Results.fetch({query: 'query', token: '12kgkj398512j'}).then(function(data) {
// do something
return data;
})
but because the API returns a single object and Em.RESTAdapter.findQuery expects an array, an error occurs when Ember Model tries to materialize the data. So how do I do this properly? I'm using the latest build of Ember Model.
By the way, I'm aware that it would be much more convenient if the API was designed in a way so I can just call App.Content.fetch(<object>), which would return a similar JSON response, but I would then be able to set the collectionKey option to content and my data would be properly materialized.
You simply need to override your models load() method to adjust the payload hash to what Ember.Model wants. There are no serializers in Ember.Model. There is both a class level load for handling collections and an instance level load for loading the JSON specific to a single model. You want to override the instance level load method to wrap the content key value in an array if its not one already.
I have been using Ember.Mode quite heavily and enhanced it for a number of my use cases and submitted PR's for both fixes and enhancements. Those PRs have been sitting there for a while with no response from the maintainers. I have now moved to Ember.Data which has been 'rebooted' so to speak and having a lot better result with it now.
I would strongly suggest walking away from Ember.Model as it appears dead with the new pragmatic direction Ember Data has taken and because the project maintainer doesn't appear to have any interest in it anymore.

How to access complex nested json in ember.js

after thorough searching stackoverflow and reading through all of the documetation on emberjs.com I'm finding myself stuck. I have a complex json object that I'm trying to model and output in my ember project.
I don't have control over the JSON, otherwise I'd change it's format to be easier digested. That said, here is my problem.
I have the following json
[
{
"id":1,
"catId": "10051",
"catUrl": "path/to/location",
"childCount": "4",
"description": [{
"text": "Description Text"
}],
"identifier": "UNQ123456",
"partialResults": "false"
}
]
What I'm trying to get at is the text value in description. I've tried creating the hasMany and belongsTo nested model construct described on emberjs.com, as well as many other patterns that were described as answers here on stack overflow, yet none of them seem to work or match the data construct I have to work with.
I've even tried the anonymous function in the first block of code on this page. http://emberjs.com/guides/models/defining-models/ trying to traverse this to the text that I want.
Regardless, any help would be much appreciated.
You could define a custom data transform to handle your special JSON field. This can be done by using the DS.RESTAdapter.registerTransform function. Something like this should work for your use case:
DS.RESTAdapter.registerTransform('descriptionText', {
serialize: function(data) {
var text = data[0].text;
return text;
},
deserialize: function(text) {
var data = [Ember.create({text: text})];
return data;
}
});
And then use it as a custom attribute for your model:
App.MyModel = DS.Model.extend({
...
description: DS.attr('descriptionText')
});
Note that the name of the transform could be something else as descriptionText as long you use the same name for DS.attr(...).
Hope it helps.