Broken promise getting a user - ember.js

I may be making a Promise faux pas but after authenticating a user I want to load the user's profile into the App.Session singleton that I've created:
App.Session.set(
'userProfile',
self.get('store').find('user',App.Session.get('userId'))
);
This results in the API call being made and a valid resultset being returned but for some reason in the debugger I get an empty result. Specifically, I do see the User.id but the rest of the columns are blank.
From the debugger, here's the JSON response:
{
"user": {
"id": "1",
"username": "jsmith",
"name": {
"first_name": "Joe",
"last_name": "Smith"
},
"emails": [
{
"id": "52153c0330063",
"name": "work-1",
"type": "other",
"address": "new#notreally.com",
"comments": "",
"status": "active",
"is_primary": false
},
{
"id": "52153d1b90ad0",
"name": "work-2",
"type": "other",
"address": "old#yesreally.com",
"comments": "",
"status": "active",
"is_primary": true
},
]
}
I'm a little new to Promises and so I thought maybe if I changed the code to:
self.get('store').find('user',App.Session.get('userId')).then( function(profile){
App.Session.set('userProfile', profile);
});
I felt pretty good about my new Promise acumen as I wrote this new code. Sadly my proud moment was greeted with failure. My second code snippet behaves precisely the same as the first one. Huh?
Can anyone help?
--------- ** UPDATE ** ---------
I've now including the model definition for User and a picture of the debugger window I made reference to.
User Model
App.RawTransform = DS.Transform.extend({
deserialize: function(serialized) {
return serialized;
},
serialize: function(deserialized) {
return deserialized;
}
});
App.NameTransform = DS.Transform.extend({
deserialize: function(serialized) {
return App.Name.create(serialized);
},
serialize: function(deserialized) {
return JSON.stringify(deserialized);
}
});
App.User = DS.Model.extend({
username: DS.attr("string"),
name: DS.attr("name"),
roles: DS.attr("raw"),
goals: DS.attr("raw"),
places: DS.attr("raw"),
emails: DS.attr("raw"),
networks: DS.attr("raw"),
relationships: DS.attr("raw"),
services: DS.attr("raw"),
uom: DS.attr("raw"),
});
Debug Window
Prior to login the model viewer looks like this:
Then after login it looks like this:
And then looking at the record details we see:

Ok, the answer seems to be down to two things. First of all, the second code snippet for handling the promise that I tried:
self.get('store').find('user',App.Session.get('userId')).then( function(profile){
App.Session.set('userProfile', profile);
});
is the correct way to go. The first method just leaves you with a "broken promise" in a "broken heart" sort of way not technically speaking but the point is it doesn't work.
The reason that my second promise implementation didn't work though was down to the Model indirectly and very specifically down to the deserializer I had put in place for Names.
I was scratching my head on this for second as the deserializer had worked back in the Ember-Data v0.1x world so I did what seemed appropriate ... I blamed Ember-Data. Come on, we've all done it. The fact is that Ember-Data had nothing to do with it and once I was willing to accept the blame I realised that it was simply a matter of not having moved my Name object over to the project I'm currently working on. Doh!

Related

Action Cables, nested JSON attributes and JSONAPI

I have a fairly specific problem that I was hoping one of you really intelligent folk might know a solution for (or even a workaround at this stage)
Specifically, I'm dealing with action cables, nested JSON, and the JSONAPI.
I have an asset model, which has some attributes like name, desc etc. but it also has an attribute called state which is a complex nested JSON object.
// app/models/asset.js
export default DS.Model.extend({
// Attributes
name: DS.attr('string'),
desc: DS.attr('string'),
state: DS.attr(),
lastSeen: DS.attr('date'),
});
When anything on the asset changes in the backend, it is pushed down the cable to Ember, where it does a pushPayload(data), the payload looks like this;
{
"data": {
"id": "5",
"type": "assets",
"attributes": {
"asset_id": "962ABC",
"name": "962 ABC",
"desc": "Test Vehicle",
"activation_status": "active",
"state": {
"avl": {
"longitude": 152.9475426,
"reported_at": "2017-06-22T21:59:52Z"
},
"dfm": {
"in_alarm": false,
"reported_at": "2017-06-21T05:46:57Z",
"sensitivity": "normal",
"voice_prompt": false,
"driver_detected": true,
},
"tpms": {
"system_desc": "123ABC",
"system_type": "123_abc"
}
},
"last_seen": "2017-06-22T21:59:54.000Z"
},
"relationships": {
"company": {
"data": {
"id": "1",
"type": "companies"
}
},
"events": {
"links": {
"related": "/events/?asset_id=5"
}
},
"messages": {
"links": {
"related": "/messages/?asset_id=5"
}
}
}
}
}
This all works fine and dandy, updates to the asset & state are displayed as they happen thanks to the cable, and state is read only so I don't have to worry about saving anything. HOWEVER, I have noticed that when any single attribute on state changes in the backend, the entire asset is pushed down from the backend (this should be fine), and then this fires the observer for state and also all observers for state descendants - whereas I need it to only fire the observer for the state attribute that changed.
I have tried a number of things and each seemed to either not work at all, or still continue to update state in a way that fired off all state observers.
What I have tried;
ember-model-data-fragments (while it should work, I think the way that the action cable pushes the data must subvert this?)
embedded records (requires an ID for state, not currently compatible with JSONAPI)
raw json transform (making the json into ember objects, didn't seem to help)
Can anyone suggest a strategy or solution for me to try? I've spent nearly 2 days on this problem.. I would even settle for just splitting it up between avl/tpms/dfm, as long as when an attribute in one of those sections is changed, it doesn't notify properties from the other 2 sections.
Thanks

Correct way to persist embedded relationships in ember-data in a Ember-cli application

I am facing a situation in which I need to persist an embedded relationship into database. I am describing a similar situation in this question. It is an ember-cli project.
I have two models:
//app/model/post.js
import DS from 'ember-data';
var Post = DS.Model.extend({
entry: DS.attr('string'),
comments: DS.hasMany('comment')
});
export default Post;
//app/models/comment.js
import DS from 'ember-data';
var Comment = DS.Model.extend({
text: DS.attr('string'),
post: DS.belongsTo('post')
});
export default Comment;
1 Serializer:
//app/serializers/post.js
import DS from 'ember-data';
export default DS.ActiveModelSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
comments: {
embedded: 'always'
}
}
});
1 Route:
//app/routes/index.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.find('post', 1);
},
setupController: function(controller, model) {
var newComment = this.store.createRecord('comment', {});
newComment.set('text', 'xxxx comment');
model.get('comments').pushObject(newComment);
model.save().then(function(){
console.log(model.toJSON());
comments = model.get('comments');
comments.forEach(function(comment){
console.log("Comment: " + comment.get('text'));
console.log("Comment id: " + comment.get('id'));
});
});
}
});
So, the GET call in model hook the server returns:
// GET /posts/1
{
"posts": {
"id": "1",
"entry": "This is first post",
"comments": [
{
"id": "1",
"post": "1",
"text": "This is the first comment on first post"
},
{
"id": "2",
"post": "1",
"text": "This is the second comment on first post"
}
]
}
}
When in the setupController hook, I add a new comment to the post and save it, its actually sending a PUT request with the following body:
// PUT /posts/1 -- Request
{
"posts": {
"id": "1",
"entry": "This is first post",
"comments": [
{
"id": "1",
"post": "1",
"text": "This is the first comment on first post"
},
{
"id": "2",
"post": "1",
"text": "This is the second comment on first post"
},
{
"post": "1",
"text": "xxxx comment"
}
]
}
}
The server returns the following output:
// PUT /posts/1 -- Response
{
"posts": {
"id": "1",
"entry": "This is first post",
"comments": [
{
"id": "1",
"post": "1",
"text": "This is the first comment on first post"
},
{
"id": "2",
"post": "1",
"text": "This is the second comment on first post"
},
{
"id": "3",
"post": "1",
"text": "xxxx comment"
}
]
}
}
But now in the console log I get the following output:
Comment: This is the first comment on first post
Comment id: 1
Comment: This is the second comment on first post
Comment id: 2
Comment: xxxx comment
Comment id: 3
Comment: xxxx comment
Comment id: null
Why is the new comment returned with id is added to the post's comments and is not replacing the comment?
Am I doing anything wrong or I need to add something else for this?
Ember Data would have no exact way of recognizing the difference between a record that user attempted to save and a record a different user attempted to save.
All it can safely know is that a new record with a new id came back (since there was no unique identifier on the record before, and you didn't specify to save that exact record).
In a non multi-user world, it could assume the new record should replace the existing record, but the Embedded Record stuff just isn't that smart yet.
1. Delete the record after you save (cause you know it'll get duped, hacky)
var comments = model.get('comments');
comments.pushObject(newComment);
model.save().then(function(){
comments.popObject(newComment);
newComment.deleteRecord(); // not really necessary
...
});
2. Save the record from the comment's point of view (cheapest and cleanest, might be a bit of additional server side logic for you)
newComment.save();

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.

How to get the return data of a save with Ember Data?

I'm using Ember.js with ember-data to make a GUI for a self made API.
I've been following this tutorial to handle authentication but I want to use ember-data instead of custom jQuery requests.
One thing I have to do is to call the API to create a new session, by sending email and password, and the API sends me back an API Key object.
Here is my LoginController handling the loginUser action (it's CoffeeScript) :
App.LoginController = Ember.ObjectController.extend
actions:
loginUser: ->
session = #store.createRecord 'session',
email: #get 'email'
password: #get 'password'
session.save()
Here is the result I get when creating a session:
{
"users": [
{
"id": "525fa0286c696c0b14040000",
"email": "john.doe#mydomain.com",
"first_name": "John",
"surname": "Doe"
}
],
"api_key": {
"id": "526e464c6c696c07d2000000",
"type": "session",
"key": "6b824d6a-a065-4b6f-bb28-5c19389760f8",
"expires_at": "2013-10-28T11:41:08+00:00",
"user_id": "525fa0286c696c0b14040000"
}
}
I have Session, ApiKey and User models. I can create the session, but the thing I don't understand is how to access the return value of the save() method.
I know that my ApiKey and User are loaded somewhere because I get an error after save() if their respective Ember model don't exist but I don't know how to access them.
I've tried to use save() callbacks like then() or didCreate event but there's a lack of documentation about arguments passed to these callbacks and how to use them.
Ember.js 1.1.2
Ember Data 1.0.0.beta.3
EDIT:
I've tried to create an actuel Session model on my API, resulting in this JSON output:
{
"api_keys": [
{
"id": "526f69526c696c07d2110000",
"type": "session",
"key": "4c26af37-2b21-49c2-aef5-5850a396da0b",
"expires_at": "2013-10-29T08:22:50+00:00",
"user_id": "525fa0286c696c0b14040000"
}
],
"users": [
{
"id": "525fa0286c696c0b14040000",
"email": "john.doe#coreye.fr",
"first_name": "John",
"surname": "Doe"
}
],
"session": {
"id": "526f6e666c696c18c0010000",
"api_key_id": "526f69526c696c07d2110000"
}
}
(note the root element is now session)
It doesn't work better because now my save action leads to the following error (not in the console but then points to error callback):
Object function () { [...] } has no method 'eachTransformedAttribute'
I get this error, the relation between Session and ApiKey being declared in Ember Data models or not...
Your second example JSON looks better: since you are saving a Session, I would expect a session node in the response and other models to be side loaded. You can access the saved session after it's saved by using a promise callback:
session.save().then (savedSession) =>
console.log savedSession.api_key.key
Since you have _id relationship keys in your JSON, I assume you are using the ActiveModel adapter and its default serializer:
App.ApplicationAdapter = DS.ActiveModelAdapter.extend()

CRUD operations using Ember-Model

Here,I am trying to implement CRUD operations using ember-model.
I am totally new to ember environment,actually i don't have much understanding of ember-model.
Here,i am trying to add new product and delete existing one.I am using inner node of fixture
i.e. cart_items.My this fixture contains two node i.e. logged_in and cart_items and this what my fixture structure :
Astcart.Application.adapter = Ember.FixtureAdapter.create();
Astcart.Application.FIXTURES = [
{
"logged_in": {
"logged": true,
"username": "sachin",
"account_id": "4214"
},
"cart_items": [
{
"id": "1",
"name": "Samsung Galaxy Tab 2",
"qty": "1",
"price": "100",
"subtotal": "100"
},
{
"id": "2",
"name": "Samsung Galaxy Tab 2",
"qty": "1",
"price": "100",
"subtotal": "100"
},
{
"id": "3",
"name": "Samsung Galaxy Tab 2",
"qty": "1",
"price": "100",
"subtotal": "100"
}
]
}
];
I want to this fixture struture only to get data in one service call from server.
Now,here is my code which i am using to add and delete product from cart_items
Astcart.IndexRoute = Ember.Route.extend({
model: function() {
return Astcart.Application.find();
}
});
Astcart.IndexController = Ember.ArrayController.extend({
save: function(){
this.get('model').map(function(application) {
var new_cart_item = application.get('cart_items').create({name: this.get('newProductDesc'),qty: this.get('newProductQty'),price: this.get('newProductPrice'),subtotal: this.get('newProductSubtotal')});
new_cart_item.save();
});
},
deleteproduct: function(product){
if (window.confirm("Are you sure you want to delete this record?")) {
this.get('model').map(function(application) {
application.get('cart_items').deleteRecord(product);
});
}
}
});
But when i am trying to save product i am getting an exception
Uncaught TypeError: Object [object global] has no method 'get'
And when i am trying to delete product i am getting an exception
Uncaught TypeError: Object [object Object] has no method 'deleteRecord'
Here,i also want to implement one functionality i.e. on every save i need to check if that product is already present or not.
If product is not present then only save new product other wise update existing product.
But i don't have any idea how to do this?
I have posted my complete code here.
Can anyone help me to make this jsfiddle work?
Update
I have updated my code here with debugs.
Here, i am not getting any exception but record is also not getting delete.
I am not getting what is happening here?
Can anyone help me to make this jsfiddle work?
'this' context changes within your save method. You need to use the 'this' of the controller and not the map functions. Try this:
save: function(){
var self = this;
self.get('model').map(function(application) {
var new_cart_item = application.get('cart_items').create({
name: self.get('newProductDesc'),
qty: self.get('newProductQty'),
price: self.get('newProductPrice'),
subtotal: self.get('newProductSubtotal')
});
new_cart_item.save();
});
}