ember-data-url-templates - how to use snapshot API with Ember 3 - ember-data

In the add-on wi-ki it is explained that we can use belongsTo to discover the relations between models:
urlSegments: {
postId: function(type, id, snapshot, query) {
return snapshot.belongsTo('post', { id: true });
},
},
but I can't find any more in Ember 3 API docs. How to do that ?
More of that, I get the error:
Uncaught TypeError: snapshot.belongsTo is not a function
at Class.shopId (shop-language.js:13)
at url-templates.js:39
at subFunction (uri-templates.js:103)
when using it in an adapter:
#adapters/shop-language.js
import ApplicationAdapter from './application';
import UrlTemplates from "ember-data-url-templates";
export default ApplicationAdapter.extend(UrlTemplates, {
findAllUrlTemplate: '/shops/{shopId}/languages',
createRecordUrlTemplate: '/shops/{shopId}/languages',
urlSegments: {
shopId: function(type, id, snapshot, query) {
return snapshot.belongsTo('shop', { id: true });
},
},
});

I figured out how to use it with description model that belongsTo a shop. Here is description.js adapter:
import ApplicationAdapter from './application';
import UrlTemplates from "ember-data-url-templates";
export default ApplicationAdapter.extend(UrlTemplates, {
urlTemplate: '{+host}/shops/{shopId}/descriptions',
findAllUrlTemplate: '{+host}/shops/{shopId}/descriptions',
createRecordUrlTemplate: '{+host}/shops/{shopId}/descriptions',
updateRecordUrlTemplate: '{+host}/shops/{shopId}/descriptions/{id}',
urlSegments: {
shopId: function(type, id, snapshot, query) {
if (query && query.shop_identifier) {
return query.shop_identifier;
}
return snapshot.belongsTo('shop').attr('identifier');
},
id: function(type, id, snapshot) {
return snapshot.id;
}
}
});
In the above example iwas using another shop attribute - identifier, but you can pass in shop's id instead.
Hope this helps.

Related

Insert Many to Many Data into Shopware 6 Database using the Administration

I have created a plugin in the adminstration and I want to insert the manyToMany products with vehicles into Shopware 6 database. From the code below I am trying to insert '92961afbc50e4380b3af86b257630ade' into the 'product_id' column of the 'vehicles_product' table :
import template from './sw-vehicles-import.html.twig';
const { Component, Mixin } = Shopware;
Component.register('sw-vehicles-import', {
template,
inject: ['importExport', 'repositoryFactory', 'feature'],
mixins: [
Mixin.getByName('notification'),
],
metaInfo() {
return {
title: this.$createTitle()
};
},
data() {
return {
importFile: null,
repository: null,
entity: undefined,
};
},
computed: {
},
created() {
this.repository = this.repositoryFactory.create('vehicles');
},
methods: {
onStartProcess() {
this.entity = this.repository.create(Shopware.Context.api);
this.entity.categoryFilter = 'CategoryName';
this.entity.featureFilter = 'FeatureName';
this.entity.products.productId = '92961afbc50e4380b3af86b257630ade';
this.repository.save(this.entity, Shopware.Context.api);
}
}
});
The build process doesn't work, what am I doing wrong? Could you help me please ?
You need to create a new entity collection for the association if it doesn't exist yet.
const { EntityCollection } = Shopware.Data;
if (!this.entity.products) {
this.entity.products = new EntityCollection(
'/product',
'product',
Shopware.Context.api
);
}
const product = await this.repositoryFactory.create('product').get('92961afbc50e4380b3af86b257630ade', Shopware.Context.api);
this.entity.products.add(product);
this.repository.save(this.entity, Shopware.Context.api);

After bump ember-data from 2.13 to 2.14 the request payload not contain hasMany relations when the relation model had empty relation

with ActiveModelSerializer usage after the ember-data upgrade, the values of has_many case on the request payload had changed when the model has no records by relation, for example:
// app/models/user.js
import Model from 'ember-data/model';
import { hasMany } from 'ember-data/relationships';
export default Model.extend(Validations, {
posts: hasMany('post'),
...
}
// app/models/post.js
import Model from 'ember-data/model';
import { belongsTo } from 'ember-data/relationships';
export default Model.extend(Validations, {
user: belongsTo('user'),
...
}
so, if User has no posts, previously was sent [] into BE, but after ember-data bump to 2.14.11 no key-values sent, for example: (request payload)
Before:
user: { id: 1, post_ids: [] }
After:
user: { id: 1 }
Note:
"active-model-adapter": "2.2.0"
"ember-data": "2.14.11"
It's caused by: https://github.com/emberjs/data/compare/v2.14.3...v2.14.4
workaround: app/serializers/application.js
import EmbeddedRecordsMixin from 'ember-data/serializers/embedded-records-mixin';
import { ActiveModelSerializer } from 'active-model-adapter';
import { pluralize } from 'ember-inflector';
export default ActiveModelSerializer.extend(EmbeddedRecordsMixin, {
isNewSerializerAPI: true,
serializeHasMany(snapshot, model, relation) {
this._super(...arguments);
let relationKey = `${pluralize(relation.key.underscore()).slice(0, -1)}_ids`;
if (relation.kind === 'hasMany' && !model[relationKey]) {
model[relationKey] = Object.freeze([]);
}
}
});

Vue Apollo "__typename" is undefined in updateQuery

I'm attempting to create a "Show More" button for my posts index. The index query loads fine with the first 5 posts, when I click the Show More button I can see new posts being returned, however I receive a bunch of errors like:
Missing field id in {
"__typename": "Post",
"posts": [
{
"id": "5f2b26600c3ec47b279d8988",
"title":
I receive one of each of these errors pretty much for each post attribute (id, title, content, slug, etc). This prevents the actual new posts from being added to the index. What causes this issue?
<script>
import postsQuery from '~/apollo/queries/blog/posts';
const pageSize = 5;
export default {
name: 'BlogIndex',
data: () => ({
loadingMorePosts: false,
page: 0,
pageSize,
}),
apollo: {
postsCount: {
prefetch: true,
query: postsQuery,
variables: {
page: 0,
pageSize,
}
},
posts: {
prefetch: true,
query: postsQuery,
variables: {
page: 0,
pageSize,
}
},
},
computed: {
morePosts() {
return this.posts.length < this.postsCount.aggregate.totalCount;
}
},
methods: {
async fetchMorePosts() {
this.page += this.pageSize;
this.$apollo.queries.posts.fetchMore({
variables: {
page: this.page,
pageSize,
},
updateQuery: (previousResult, { fetchMoreResult }) => {
const newPosts = fetchMoreResult.posts;
console.log('typename: ', previousResult.posts.__typename); <--- returns undefined
if (!newPosts.length) return previousResult;
return {
posts: {
__typename: previousResult.posts.__typename,
posts: [...previousResult.posts, ...newPosts],
}
}
}
})
},
},
}
</script>
UPDATE: added imported posts query
query Posts($page: Int!, $pageSize: Int!) {
posts(
start: $page
limit: $pageSize
sort: "published_at:desc"
where: { published: true }
) {
id
title
content
slug
published
createdAt
updatedAt
published_at
}
postsCount: postsConnection(where: { published: true }) {
aggregate {
totalCount
}
}
}
I think the problem is here:
return {
posts: {
__typename: previousResult.posts.__typename,
posts: [...previousResult.posts, ...newPosts],
}
}
I'm pretty sure __typename is supposed to belong to each post object, not part of the collection of posts. Let me know how if something like this fixes it:
return {
posts: {
posts: [...previousResult.posts, ...newPosts]
}
}
and changing the query to:
query Posts($page: Int!, $pageSize: Int!) {
posts(
start: $page
limit: $pageSize
sort: "published_at:desc"
where: { published: true }
) {
__typename // add this here
id
title
content
slug
published
createdAt
updatedAt
published_at
}
postsCount: postsConnection(where: { published: true }) {
aggregate {
totalCount
}
}
}

Getting documents with ID from firstore collection

While using Firestore, vuefire, vue-tables-2, I stuck getting document's id.
My data structure is as below.
Here is my code.
<v-client-table :columns="columns" :data="devices" :options="options" :theme="theme" id="dataTable">
import { ClientTable, Event } from 'vue-tables-2'
import { firebase, db } from '../../firebase-configured'
export default {
name: 'Devices',
components: {
ClientTable,
Event
},
data: function() {
return {
devices: [],
columns: ['model', 'id', 'scanTime', 'isStolen'],
options: {
headings: {
model: 'Model',
id: 'Serial No',
scanTime: 'Scan Time',
isStolen: 'Stolen YN'
},
templates: {
id: function(h, row, index) {
return index + ':' + row.id // <<- row.id is undefined
},
isStolen: (h, row, index) => {
return row.isStolen ? 'Y': ''
}
},
pagination: {
chunk: 5,
edge: false,
nav: 'scroll'
}
},
useVuex: false,
theme: 'bootstrap4',
template: 'default'
}
},
firestore: {
devices: db.collection('devices')
},
};
My expectation is devices should id property as vuefire docs.
But array this.devices didn't have id field even if I check it exist it console.
Basically, every document already has id attribute, but it's non-enumerable
Any document bound by Vuexfire will retain it's id in the database as
a non-enumerable, read-only property. This makes it easier to write
changes and allows you to only copy the data using the spread operator
or Object.assign.
You can access id directly using device.id. But when passing to vue-tables-2、devices is copied and lost id non-enumerable attribute.
I think you can workaround using computed property
computed: {
devicesWithId() {
if (!this.devices) {
return []
}
return this.devices.map(device => {
...device,
id: device.id
})
}
}
Then, please try using devicesWithId in vue-tables-2 instead.

GraphQL queries with tables join using Node.js

I am learning GraphQL so I built a little project. Let's say I have 2 models, User and Comment.
const Comment = Model.define('Comment', {
content: {
type: DataType.TEXT,
allowNull: false,
validate: {
notEmpty: true,
},
},
});
const User = Model.define('User', {
name: {
type: DataType.STRING,
allowNull: false,
validate: {
notEmpty: true,
},
},
phone: DataType.STRING,
picture: DataType.STRING,
});
The relations are one-to-many, where a user can have many comments.
I have built the schema like this:
const UserType = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: {
type: GraphQLString
},
name: {
type: GraphQLString
},
phone: {
type: GraphQLString
},
comments: {
type: new GraphQLList(CommentType),
resolve: user => user.getComments()
}
})
});
And the query:
const user = {
type: UserType,
args: {
id: {
type: new GraphQLNonNull(GraphQLString)
}
},
resolve(_, {id}) => User.findById(id)
};
Executing the query for a user and his comments is done with 1 request, like so:
{
User(id:"1"){
Comments{
content
}
}
}
As I understand, the client will get the results using 1 query, this is the benefit using GraphQL. But the server will execute 2 queries, one for the user and another one for his comments.
My question is, what are the best practices for building the GraphQL schema and types and combining join between tables, so that the server could also execute the query with 1 request?
The concept you are refering to is called batching. There are several libraries out there that offer this. For example:
Dataloader: generic utility maintained by Facebook that provides "a consistent API over various backends and reduce requests to those backends via batching and caching"
join-monster: "A GraphQL-to-SQL query execution layer for batch data fetching."
To anyone using .NET and the GraphQL for .NET package, I have made an extension method that converts the GraphQL Query into Entity Framework Includes.
public static class ResolveFieldContextExtensions
{
public static string GetIncludeString(this ResolveFieldContext<object> source)
{
return string.Join(',', GetIncludePaths(source.FieldAst));
}
private static IEnumerable<Field> GetChildren(IHaveSelectionSet root)
{
return root.SelectionSet.Selections.Cast<Field>()
.Where(x => x.SelectionSet.Selections.Any());
}
private static IEnumerable<string> GetIncludePaths(IHaveSelectionSet root)
{
var q = new Queue<Tuple<string, Field>>();
foreach (var child in GetChildren(root))
q.Enqueue(new Tuple<string, Field>(child.Name.ToPascalCase(), child));
while (q.Any())
{
var node = q.Dequeue();
var children = GetChildren(node.Item2).ToList();
if (children.Any())
{
foreach (var child in children)
q.Enqueue(new Tuple<string, Field>
(node.Item1 + "." + child.Name.ToPascalCase(), child));
}
else
{
yield return node.Item1;
}
}}}
Lets say we have the following query:
query {
getHistory {
id
product {
id
category {
id
subCategory {
id
}
subAnything {
id
}
}
}
}
}
We can create a variable in "resolve" method of the field:
var include = context.GetIncludeString();
which generates the following string:
"Product.Category.SubCategory,Product.Category.SubAnything"
and pass it to Entity Framework:
public Task<TEntity> Get(TKey id, string include)
{
var query = Context.Set<TEntity>();
if (!string.IsNullOrEmpty(include))
{
query = include.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Aggregate(query, (q, p) => q.Include(p));
}
return query.SingleOrDefaultAsync(c => c.Id.Equals(id));
}