CRUD operations using Ember-Model - ember.js

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();
});
}

Related

Ember-data: validation errors on relationships (hasMany)

I'm trying to create record with associated records in one request. In case if some of nested records have validation errors, I'd like to access appropriate errors on that record. I'm using json-api adaptor, so what should be the format of errors from the backend? I'm trying something like this, with no luck though:
{"errors":[
{
"detail": "can't be blank",
"source": {
"pointer":"data/relationships/steps/0/data/attributes/est_threshold"
}
}
]}
According to this line, it should be implemented somehow:
https://github.com/emberjs/data/blob/master/addon/adapters/errors.js#L7
Any ideas?
You'll need to sideload nested records in the data. The example structure given in the ember guides is:
{
"post": {
"id": 1,
"title": "Node is not omakase",
"comments": [1, 2, 3]
},
"comments": [{
"id": 1,
"body": "But is it _lightweight_ omakase?"
},
{
"id": 2,
"body": "I for one welcome our new omakase overlords"
},
{
"id": 3,
"body": "Put me on the fast track to a delicious dinner"
}]
}
https://guides.emberjs.com/v1.10.0/models/the-rest-adapter/
So it doesn't seem to be implemented yet. I found kinda hackish way to do that in the model mixin:
`import Ember from 'ember'`
RelatedErrors = Ember.Mixin.create
save: ->
#_super().catch (resp) =>
resp.errors.forEach (err) =>
if [_, rel, idx, attr] = err.source.pointer.match /^data\/relationships\/(\w+)\/(\d+)\/data\/attributes\/(\w+)$/
#get(rel).objectAt(idx).get('errors').add(attr, err.detail)
`export default RelatedErrors`
However, add on DS.Errors is deprecated, so this is still not a perfect solution. Also invalid state of related models need to be cleared before each commit, which is not happening this far.

Ember - Only update fields returned in response JSON

we would like to add lazy loading functionality to our ember project, but it looks like ember will always override fields not returned by the response JSON with NULL. First I get a list of users:
GET https://example.com/users
{
"users": [
{
"id": 1,
"name": 'user1',
"email": 'email#user1.com',
"images": [],
"posts": []
},
{
"id": 2,
"name": 'user2',
"email": 'email#user2.com',
"images": [],
"posts": []
},
{
"id": 3,
"name": 'user3',
"email": 'email#user3.com',
"images": [],
"posts": []
},
]
}
This provides a minimal set of user information, with two empty hasMany relations "images" and "posts".
Now, if somebody want to see the users posts or images he would click a button which triggers the lazy loading:
GET https://example.com/userImages/1
{
"user": {
"id": 1,
"images": [1,2,3,4]
},
"images": [
{
"id": 1,
"name": "image1",
"path" "path/to/img1/"
},
{
"id": 2,
"name": "image2",
"path" "path/to/img2/"
},
{
"id": 3,
"name": "image3",
"path" "path/to/img3/"
},
{
"id": 4,
"name": "image4",
"path" "path/to/img4/"
}
]
}
To reduce traffic to a minimum, only the newly loaded information is included. After the adapter has deserialzed and pushed the new data to the store, all fields from User1 which are not included in the payload (name, email) are set to NULL in the ember store (tested with store.pushPayload('model', payload)).
Is there a possibility to update only incoming data? Or is there a common best practice to handle such a case?
Thanks in advance for your help
EDIT:
One possibility would be to extend the ember-data "_load()" function with the block
for (var key in record._data) {
var property = record._data[key];
if( typeof(data[key]) == 'object' && data[key] == null ) {
data[key] = property;
}
}
But this is the worst possible solution I can imagine.
I think what you want is the store's update method. It's like push (or pushPayload), except that it only updates the data that you give it.
Your property returns a promise and that promise returns whatever came back from the server.
foobar: function() {
return this.store.find('foobar');
}
When the promise resolves, you have two versions of the data, the one already rendered in the client (dataOld) and the one that just returned from the backend (dataNew). To update the client without removing what hasn't change, you have to merge the old and the new. Something along the lines of:
foobar: function() {
var dataOld = this.get('foobar');
return this.store.find('foobar').then(function(dataNew) {
return Ember.$.merge(dataOld, dataNew);
});
}

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.

Broken promise getting a user

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!