Undefined fields in model - ember-data

I'm learning ember.js (ember-cli and ember-data = 2.7) and facing problems using models. Some models appears as 'undefined'
import DS from 'ember-data';
export default DS.Model.extend({
revision: DS.attr('number'),
pub_date: DS.attr('date'),
contenido: DS.attr('string'),
contenidoHtml: DS.attr(),
cambios: DS.attr('string'),
cambiosHtml: DS.attr(),
notify: DS.attr('boolean'),
URI: DS.attr('string'),
firmas: DS.attr()
});
I have a simple serializer, like this:
// serializers/application.js
import DS from 'ember-data';
export default DS.JSONAPISerializer.extend({
primaryKey: 'id'
});
This is the response from the API:
{
"data": [
{
"attributes": {
"URI": "",
"cambios": "Commit inicial",
"cambiosHtml": "<p>Commit inicial</p>",
"contenido": "No por mucho _madrugar_, amanece más __temprano__.",
"contenidoHtml": "<p>No por mucho <em>madrugar</em>, amanece más <strong>temprano</strong>.</p>",
"firmas": {
...
"reviso": "Zamora"
},
"notify": false,
"pub_date": "2011-09-30",
"revision": 0
},
"id": "7a09d345-27cc-45ef-bf58-488354c25239",
...
],
"links": {
...
},
"meta": {
...
}
}
}
Now, according to Ember Inspector, the fields contenidoHtml, cambiosHtml and pub_date are undefined or empty.
Do I missing something?

In Ember Data the convention is to camelize attribute names on a model
pub_date - is not follwed this so it should be changed to pubDate
JSONAPISerializer expects attributes to be dasherized in the document
payload returned by your server:
cambiosHtml - change it to cambios-html
contenidoHtml - change it to contenido-html
pub_date - change it to pub-date
If you dont have control over the API, then you might want to create model specific serializers by running the below ember-cli command,
ember generate serializers 'model-name'
app/serializers/model-name.js
import DS from 'ember-data';
export default DS.JSONAPISerializer.extend({
attrs: {
cambiosHtml: 'cambiosHtml',
contenidoHtml: 'contenidoHtml',
pubDate: 'pub_date'
}
});
https://guides.emberjs.com/v2.8.0/models/customizing-serializers/#toc_attribute-names

Related

GraphQL queries with multiple aliases and Apollo (Vue.js)

I'm trying to fetch data from a single collection type of my Strapi backend into a Vue.js project using Apollo. It works well with a single alias, but I'm having troubles making it work with multiple aliases.
I'm getting my data from a collection type of "campaigns" which has a boolean field of "archive". I want to create an array of "campaigns" that contains all of the campaigns that haven't been archived (archive = false) as well as an array of "archive" that contains all of the archived ones (archive = true).
This is my code:
import gql from "graphql-tag";
export default {
name: "Campaigns",
data() {
return {
campaigns: [],
archive: []
};
},
apollo: {
campaigns: gql`
query getCampaigns {
campaigns: campaigns(where: { archive: "false" }, sort: "order:DESC") {
name
url
}
archive: campaigns(where: { archive: "true" }, sort: "order:DESC") {
name
url
}
}
`
}
The query returns an array of "campaigns", but the array of "archive" is still empty.
I've tried switching things up (put the archive alias first, switched the boolean values to make sure I can generally access the data of the archived campaigns etc.). The problem apparently lies with the "archive"-alias.
When I use the same query with Strapi's GraphQL playground I get the desired result:
{
campaigns: campaigns(where: { archive: "false" }, sort: "order:DESC") {
name
}
archive: campaigns(where: { archive: "true" }, sort: "order:DESC") {
name
}
}
... returns ...
{
"data": {
"campaigns": [
{
"name": "2020"
},
{
"name": "2019"
},
{
"name": "2018"
},
{
"name": "2017"
}
],
"archive": [
{
"name": "2016"
},
{
"name": "2015"
}
]
}
}
How can I make the query work in Vue.js with Apollo?
I think I've found a solution. Technically speaking I guess these are separate queries (which sort of defeats the purpose of aliases if I'm correct) but it does what I want:
apollo: {
campaigns: {
query: gql`
query {
campaigns: campaigns(
where: { archive: "false" }
sort: "order:desc"
) {
name
url
}
}
`
},
archive: {
query: gql`
query {
archive: campaigns(where: { archive: "true" }, sort: "order:desc") {
name
url
}
}
`
}
}
Apparently under some circumstance the initialization "apollo: { XYZ:" and the alias "query { XYZ:" have to match. I've seen in the docs that they don't necessarily have to match, but I don't fully understand when and why.
I guess I can't really tell what the initial parameter does.
You're using campaigns as the key for your entire query, so you need to initialize your data like this:
data() {
return {
campaigns: {
campaigns: [],
archive: [],
},
};
},
Then you can access each list through the key (i.e. campaigns.campaigns and campaigns.archive).
I believe the best way to do this is to use the update property: https://apollo.vuejs.org/guide/apollo/queries.html#name-matching
apollo: {
campaigns: {
query: gql`
query {
campaigns: campaigns(
where: { archive: "false" }
sort: "order:desc"
) {
name
url
}
}
`
},
archive: {
update: data => data.campaigns,
query: gql`
query {
campaigns(where: { archive: "true" }, sort: "order:desc") {
name
url
}
}
`
}
}

Removing join table data in sequelize returned value

I am currently trying to remove a joint table data added when retrieving an association data.
The query is done by sequelize using a method added to the model through specifying model relationships(sequelize magic methods), for some reason, I'm not able to do that.
I have currently tried passing in attributes: {exclude: ['...']} to the method but the field still persists.
Current association
// Class sequelize model
Class.belongsToMany(models.Subject, {
through: 'ClassSubject',
foreignKey: 'class_id',
otherKey: 'subject_id',
as: 'subjects'
})
// Subject sequelize model
Subject.belongsToMany(models.Class, {
through: 'ClassSubject',
foreignKey: 'subject_id',
otherKey: 'class_id',
as: 'classes'
});
Query and Response
const subjects = await dbClass.getSubjects(); // dbClass is a Class model instance
// Response
[
{
"id": "1b89d44c-2caa-452d-a1f8-7faa11970917",
"name": "Mathematics",
"code": "MATHS",
"summary": "Mathematics for class 1",
"ClassSubject": {
"class_id": "637afc7b-40f6-478e-b35e-859ca462e2e7",
"subject_id": "1b89d44c-2caa-452d-a1f8-7faa11970917"
}
}
]
Desired output
// Response
[
{
"id": "1b89d44c-2caa-452d-a1f8-7faa11970917",
"name": "Mathematics",
"code": "MATHS",
"summary": "Mathematics for class 1"
}
]
I have tried passing options to the method as specified below but to no avail
const subjects = await dbClass.getSubjects({
attributes: { exclude: ['ClassSubject'] }
});
But it still doesn't work.
Try using the joinTableAttributes option and pass empty array to exclude everything in joint table.
const subjects = await dbClass.getSubjects({ joinTableAttributes: [] });

How to update the Strapi GraphQL cache, after creating new data?

How to update the cache, after creating new data?
Error message from Apollo
Store error: the application attempted to write an object with no provided id but the store already contains an id of UsersPermissionsUser:1 for this object. The selectionSet that was trying to be written is:
{
"kind": "Field",
"name": { "kind": "Name", "value": "user" },
"arguments": [],
"directives": [],
"selectionSet": {
"kind": "SelectionSet",
"selections": [
{ "kind": "Field", "name": { "kind": "Name", "value": "username" }, "arguments": [], "directives": [] },
{ "kind": "Field", "name": { "kind": "Name", "value": "__typename" } }
]
}
}
Nativescript-vue Front-end Details
1- Watch Book Mobile app in action on YouTube: https://youtu.be/sBM-ErjXWuw
2- Watch Question video for details on YouTube: https://youtu.be/wqvrcBRQpZg
{N}-vue AddBook.vue file
apolloClient
.mutate({
// Query
mutation: mutations.CREATE_BOOK,
// Parameters
variables: {
name: this.book.name,
year: this.book.year,
},
// HOW TO UPDATE
update: (store, { data }) => {
console.log("data ::::>> ", data.createBook.book);
const bookQuery = {
query: queries.ALL_BOOKS,
};
// TypeScript detail: instead of creating an interface
// I used any type access books property without compile errors.
const bookData:any = store.readQuery(bookQuery);
console.log('bookData :>> ', bookData);
// I pin-pointed data objects
// Instead of push(createBook) I've pushed data.createBook.book
bookData.books.push(data.createBook.book);
store.writeQuery({ ...bookQuery, data: bookData })
},
})
.then((data) => {
// I can even see ID in Result
console.log("new data.data id ::::: :>> ", data.data.createBook.book.id);
this.$navigateTo(App);
})
.catch((error) => {
// Error
console.error(error);
});
What are these "Book:9": { lines in the cache?
console.log store turns out:
"Book:9": {
"id": "9",
"name": "Hadi",
"year": "255",
"__typename": "Book"
},
"$ROOT_MUTATION.createBook({\"input\":{\"data\":{\"name\":\"Hadi\",\"year\":\"255\"}}})": {
You can see all front-end GitHub repo here
Download Android apk file
Our goal is to update the cache. Add Book Method is in here:
https://github.com/kaanguru/mutate-question/blob/c199f8dcc8e80e83abdbcde4811770b766befcb5/nativescript-vue/app/components/AddBook.vue#L39
Back-end details
However, this is a frontend question a running Strapi GraphQL Server is here: https://polar-badlands-01357.herokuapp.com/admin/
GraphQL Playground
USER: admin
PASSWORD: passw123
You can see GraphQL documentation
I have so much simple Strapi GrapQL Scheme:
If you want to test it using postman or insomnia you can use;
POST GraphQL Query URL: https://polar-badlands-01357.herokuapp.com/graphql
Bearer Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNTkwODI3MzE0LCJleHAiOjE1OTM0MTkzMTR9.WIK-f4dkwVAyIlP20v1PFoflpwGmRYgRrsQiRFgGdqg
NOTE: Don't get confused with $navigateTo() it's just a custom method of nativescript-vue.
It turns out;
all code was correct accept bookData.push(createBook);
// HOW TO UPDATE
update: (store, { data }) => {
console.log("data ::::>> ", data.createBook.book);
const bookQuery = {
query: queries.ALL_BOOKS,
};
// TypeScript detail: instead of creating an interface
// I used any type access books property without compile errors.
const bookData:any = store.readQuery(bookQuery);
console.log('bookData :>> ', bookData);
// I pin-pointed data objects
// Instead of push(createBook) I've pushed data.createBook.book
bookData.books.push(data.createBook.book);
store.writeQuery({ ...bookQuery, data: bookData })
},
})
Typescipt was helping
The point is; I shouldn't trust TypeScript errors, or at least I should read more about what it really says.
Typescript just asked me to be more specific while saying: Property 'push' does not exist on type 'unknown'
TypeScript was trying to tell me I need to be more specific while calling ROOT_MUTATION data. It said: Cannot find name 'createBook' But again I ignored it.
Solution Github Branch
https://github.com/kaanguru/mutate-question/tree/solution
Sources
how to update cache
Create interface for object Typescript

Binding Vuex data to vue-meta

I'm working on a blog and I'm having a hard time trying to plug Vuex and Vue-meta. Here is the code :
On the article template, I use a v-for="article in selectedArticle" and a computed property to display the correct article from Vuex
selectedArticle() {
return this.$store.state.articles.filter(article => article.id == this.$route.params.id);
},
I'm trying to use Vue-meta in this way :
metaInfo: {
title: ???,
meta: [
{ charset: 'utf-8' },
{ vmid: 'description', name: 'description', content: ???.id },
{
'property': 'og:image',
'content': https://api.someurl.com/article/ + ???.id,
'vmid': 'og:image'
}
]
}
I think one way to do it would be to bind the vuex data retrieved in SelectedArticle to the actual data property of the component, and then use title : this.article.title in metaInfo.
But I don't know how to do it and I'm not sure if this is the right way.
Any other suggestions?
Thank you very much

EmberJS Data hasMany sideloading with ActiveModelSerializers

I'm using Ember Data canary build: 1.0.0-beta.8+canary.267214b9 together with a rails back-end and ActiveModelSerializer. My configuration on ember side looks like this:
App.ApplicationSerializer = DS.ActiveModelSerializer.extend()
App.ApplicationAdapter = DS.ActiveModelAdapter.extend
namespace: "api/v1"
App.Authentication = DS.Model.extend
provider: DS.attr('string')
user: DS.belongsTo('user')
App.User = DS.Model.extend
username: DS.attr('string')
email: DS.attr('string')
authentications: DS.hasMany('authentication')
I have working hasMany and belongsTo relation for a model that isn't side loaded. The JSON for the relation look like this:
{
objectA: {
property1: 'prop1',
property2: 'prop2',
objectB_ids: ['1','2']
}
}
At the moment I try to get a user model with multiple authentications to work. But there the authentications should be side loaded. It doesn't work for the following JSON:
JSON - not working
{
authentications: [{ id:1, provider:"google" }],
user: {
id: '1',
username: 'max',
email: 'max#examle.com',
authentication_ids:[1],
}
}
But it does work for this:
JSON - working
{
authentications: [{ id:1, provider:"google" }],
user: {
id: '1',
username: 'max',
email: 'max#examle.com',
authentications:[1],
}
}
The only useful information I found on the web is this SO question:
Serialising async hasMany relationships
Is this a bug in the DS.ActiveModelSerializer or did I miss some configuration?
EDIT 1:
In the docs of DS.ActiveModelSerializer you can find the following:
It has been designed to work out of the box with the activemodelserializers Ruby gem.
And the version with authentication_ids:[...] is the way, how the ActiveModelSerializers Ruby gem does it out of the box. So maybe it should be added?
I think you're confusing what ActiveModelSerializer does with other conventions of Ember Data. You're working second example is correct. This section describes the current expectation of JSON layout. The _ids is not present.
{
"post": {
"id": 1,
"title": "Rails is omakase",
"comments": ["1", "2"],
"user" : "dhh"
},
"comments": [{
"id": "1",
"body": "Rails is unagi"
}, {
"id": "2",
"body": "Omakase O_o"
}]
}
The ActiveModelSerializer adapter allows you to pass underscored keys in your JSON instead of camelcased keys. For example, if your user had a camelcased name:
App.User = DS.Model.extend
firstName: DS.attr()
Your JSON should look like this:
{
"user": {
"first_name": "kunerd"
}
}
Solved my issue and DS.ActiveModelSerializer works as expected and did accept _ids array for side loaded models.
My problem was, that I had overwritten my App.UserSerializer with that:
App.UserSerializer = DS.RESTSerializer.extend
# some custom logic
,but it has to be:
App.UserSerializer = App.ApplicationSerializer.extend
# some custom logic
Maybe someone has similar problems after changing from DS.RESTSerializer to DS.ActiveModelSerializer`