Ember, Ember Data - Updating hasMany relation - ember-data

I'm trying to update a hasMany relation in Ember but I'm having a bit of trouble getting the data to send correctly.
I have the following Ember models:
// models/post.js
export default DS.Model.extend({
title: DS.attr('string'),
tags: DS.hasMany('tag', { async: true })
});
// models/tag.js
export default DS.Model.extend({
title: DS.attr('string'),
post: DS.belongsTo('post', { async: true })
});
And then I have the following action in my route to create a new tag and update the post:
addTag: function() {
var post = this.get('currentModel');
var newTag = this.store.createRecord('tag', {
title: 'Lorem Ipsum',
post: post
});
newTag.save().then(function() {
post.get('tags').pushObject(newTag);
post.save();
});
}
The new tag is created successfully and saved by my Express api but the post doesn't get saved correctly. It's received by the api but the request payload made by Ember never contains the tag IDs (it does send the tag title though). What am I doing wrong? Would really appreciate any help!
Edit
It turns out the RESTSerializer by default doesn't serialize and include the related IDs for a hasMany relationship. It only includes them for the belongsTo side as it expects the API to take care of saving it where needed. I will probably change my API to fit this behaviour as it's more efficient but in case any one else comes across this, it is possible to make the serializer include the IDs by extending the serializer and using the DS.EmbeddedRecordsMixin mixin - http://emberjs.com/api/data/classes/DS.EmbeddedRecordsMixin.html - Which would look something like this:
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
tags: { serialize: 'ids' }
}
});

You don't need to call .save() on post. When you call createRecord to create a tag, your backend receives id of post and should persist dependencies accordingly.
addTag: function() {
var post = this.get('currentModel');
this.store.createRecord('tag', {
title: 'Lorem Ipsum',
post: post})
.save()
.then(function(tag) {
post.get('tags').pushObject(tag);
});

Meet the same problem.
Currently I solve it by serializeHasMany hook.
// app/serializers/application
import {ActiveModelSerializer} from 'active-model-adapter';
export default ActiveModelSerializer.extend({
serializeHasMany: function(snapshot, json, relationship){
this._super.apply this, arguments
if (!json[relationship.type + '_ids']){
var ids = []
snapshot.record.get(relationship.key).forEach(function(item){
ids.push item.get 'id'
});
json[relationship.type + '_ids'] = ids
}
}
})

Related

Understanding hooks in Sequelize (Returning vs. Attaching)

I am following my school's workshop regarding how to integrate Sequelize with Express. There is a section where we are learning to leverage hooks in our models—and in it I was confused by this:
Returning vs. Attaching
A hook runs with the instance of a Page being
saved given as an argument. We want to, therefore, attach a created
urlTitle to this page instance instead of returning it from the
function.
var Sequelize = require('sequelize');
var db = new Sequelize('postgres://localhost:5432/__wikistack__', {
logging: false,
});
const Page = db.define(
'page',
{
title: {
type: Sequelize.STRING,
},
urlTitle: {
type: Sequelize.STRING,
},
content: {
type: Sequelize.TEXT,
},
status: {
type: Sequelize.ENUM('open', 'closed'),
},
},
{
hooks: {
beforeValidate: function(page) {
if (page.title) {
// Removes all non-alphanumeric characters from title
// And make whitespace underscore
return (page.urlTitle = page.title.replace(/\s/g, '_').replace(/\W/g, ''));
} else {
// Generates random 5 letter string
return (urlTitle = Math.random()
.toString(36)
.substring(2, 7));
}
},
},
}
);
Can someone explain this? How can the function in the hook not return something? The above works, so the hook/function is returning something.
Thanks in advance!
Hooks are just code that gets run at certain life cycle points of a record instance. You can have them be purely side effects. In your case, all you need to do is modify the page object that the hook is passed, return doesn't help or hurt.
However, the return value of a hook is not useless. If you need to do anything async inside a hook, you have to return a promise.

How do I update the Apollo data store/cache from a mutation query, the update option doesn't seem to trigger

I have a higher order component in my react native application that retrieves a Profile. When I call an "add follower" mutation, I want it to update the Profile to reflect the new follower in it's followers collection. How do I trigger the update to the store manually. I could refetch the entire profile object but would prefer to just do the insertion client-side without a network refetch. Currently, when I trigger the mutation, the Profile doesn't reflect the change in the screen.
It looks like I should be using the update option but it doesn't seem to work for me with my named mutations. http://dev.apollodata.com/react/api-mutations.html#graphql-mutation-options-update
const getUserQuery = gql`
query getUserQuery($userId:ID!) {
User(id:$userId) {
id
username
headline
photo
followers {
id
username
thumbnail
}
}
}
`;
...
const followUserMutation = gql`
mutation followUser($followingUserId: ID!, $followersUserId: ID!) {
addToUserFollowing(followingUserId: $followingUserId, followersUserId: $followersUserId) {
followersUser {
id
username
thumbnail
}
}
}`;
...
#graphql(getUserQuery)
#graphql(followUserMutation, { name: 'follow' })
#graphql(unfollowUserMutation, { name: 'unfollow' })
export default class MyProfileScreen extends Component Profile
...
this.props.follow({
variables,
update: (store, { data: { followersUser } }) => {
//this update never seems to get called
console.log('this never triggers here');
const newData = store.readQuery({ getUserQuery });
newData.followers.push(followersUser);
store.writeQuery({ getUserQuery, newData });
},
});
EDIT: Just realised that you need to add the update to the graphql definition of the mutation.
EDIT 2: #MonkeyBonkey found out that you have to add the variables in the read query function
#graphql(getUserQuery)
#graphql(followUserMutation, {
name: 'follow',
options: {
update: (store, { data: { followersUser } }) => {
console.log('this never triggers here');
const newData = store.readQuery({query:getUserQuery, variables});
newData.followers.push(followersUser);
store.writeQuery({ getUserQuery, newData });
}
},
});
#graphql(unfollowUserMutation, {
name: 'unfollow'
})
export default class MyProfileScreen extends Component Profile
...
this.props.follow({
variables: { .... },
);
I suggest you update the store using the updateQueries functionality link.
See for example this post
You could use compose to add the mutation to the component. Inside the mutation you do not need to call client.mutate. You just call the mutation on the follow user click.
It might also be possible to let apollo handle the update for you. If you change the mutation response a little bit that you add the following users to the followed user and add the dataIdFromObject functionality. link

Rally API for User story creation in a particular Project?

i have tried the following code with no luck
var rally = require('rally'),
restApi = rally({
user: 'userName', //required if no api key, defaults to process.env.RALLY_USERNAME
pass: 'password', //required if no api key, defaults to process.env.RALLY_PASSWORD
apiKey: 'XXXXX', //preferred, required if no user/pass, defaults to process.env.RALLY_API_KEY
apiVersion: 'v2.0', //this is the default and may be omitted
server: 'https://rally1.rallydev.com', //this is the default and may be omitted
requestOptions: {
headers: {
'X-RallyIntegrationName': 'My cool node.js program', //while optional, it is good practice to
'X-RallyIntegrationVendor': 'My company', //provide this header information
'X-RallyIntegrationVersion': '1.0'
}
//any additional request options (proxy options, timeouts, etc.)
}
});
restApi.create({
type: 'hierarchicalrequirement', //the type to create
data: {
Name: 'RallYUS',
//the data with which to populate the new object
},
fetch: ['FormattedID'], //the fields to be returned on the created object
scope: {
//optional, only required if creating in non-default workspace
project: 'rally',
workspace: 'abcd'
},
requestOptions: {} //optional additional options to pass through to request
}, function(error, result) {
if(error) {
console.log(error);
} else {
console.log(result.Object);
}
});
I was able to create a US but in a different project . I have been trying to fix the project issue and event entered ObjectID and Object UUID as well but still keeps creating in default project attached to user profile . Any help to force Userstory using hierarchialrelation with creation of US would definitely help
All object relationships are specified using refs in the Web Services API. This should work:
scope: {
project: '/project/12345' //where 12345 is the object id
}

where is the best place to adjust how ember-data talks to sails

I am trying to implement an ember-cli (ember-data) to sails.js connection. I have installed ember-data-sails and have a simple model and am using the socketAdapter...
adapters/application.js
import SailsSocketAdapter from 'ember-data-sails/adapters/sails-socket';
export default SailsSocketAdapter.extend({
useCSRF: true,
coalesceFindRequests: true,
namespace: 'api/v1',
//defaultSerializer: '-rest',
});
Example Model:
RequestType.js
module.exports = {
attributes: {
name : { type: 'string' },
desc : { type: 'string' },
requestSubType : { type: 'hasMany' }
}
};
So far it is mostly working, I can get a list of RequestTypes ok. However if i try and nest a route to display/create the hasMany relationship, then i have an issue.
request-sub-types/new/routes.js
This file, requests the Parent model requestType first, and then creates a new record, inserting the Parent in the belongsTo attribute.
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
var requestType = this.modelFor('requestTypes/view');
return requestType.get('requestSubTypes').createRecord({
requestType: requestType
});
}
});
request-types/view/route.js
This file is the model I think that is being requested above. The issue here is the params object that get fed through is:
{ requestType_id: 6 }
but if this is passed to the store.query call then nothing gets returned. If however I change the params object to:
{ id: 6 }
then I do see a record come back from the sails api.
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
console.log('RT: View Route MODEL', params);
var query = {};
if(params.requestType_id) {
query.id= params.requestType_id;
}
return this.store.query('requestType', query);
}
});
So My question is what do i need to edit to make the Primary Key definitions match up. so that Ember-data and Sails can talk to each other correctly? Is it in a serializer somewhere, the Sails API or in each model call?
thanks... and sorry if the above doesn't make any sense! :)

Ember.js update model after save

I have a small Ember app and adding in authentication at the moment with a simple API in the background:
POST /login
//returns
{
"token": "Much53cr4t"
}
Ember model for login (route setup correctly and calls the endpoint as expected)
App.Login = DS.Model.extend({
username: DS.attr(),
password: DS.attr(),
token: DS.attr()
});
Controller
App.LoginController = Ember.ObjectController.extend({
// Implement your controller here.
actions: {
submit: function() {
var self = this;
var login = self.get('model');
login.set('username', self.get('username'));
login.set('password', self.get('password'));
login.save().then(function (result) {
//do something here?
});
}
}
});
I would like to get the returned token value to be added either to the created model before save, or new one. Whichever is easier. Can't seem to find any other advice other than 'return an id' but that I would consider not the best when it comes to an AUTH API endpoint like this.
So I ended up adding a custom REST adapter for this:
App.LoginAdapter = DS.RESTAdapter.extend({
host: 'http://127.0.0.1',
createRecord: function(store, type, record) {
var p = this._super(store, type, record);
return p.then(function(data){
record.set('token', data['token']);
});
},
});
Still wonder is there an easier, or more 'Ember-way' for doing this?