Partial update in Ember-Data? - ember.js

Imagine a bug tracker.
Each ticket contains a lot of data. Now the status of a few tickets from a long list of tickets is updated.
App.Ticket = DS.Model.extend({
id: DS.attr('number'),
status: DS.attr('string'),
...
});
Currently when invoking save in ember-data this would send the complete models to the server.
What can I do to only send a partial update, like [{"id": 1, "status": "closed"}, {...}]?
PS: I understand that this is not possible right now, so I'm wondering if there is a workaround / modification that would enable this? (a general-purpose solution is not required).

My current workaround is to specify a readOnly option that excludes the attributes that I don't want to be sent in the POST.
Details can be found here: https://github.com/emberjs/data/pull/303#issuecomment-13993905

I was looking into this as well and if you want this to apply to all saves, then today you would fix it with a serializer like serializers/ticket.js:
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
attrs: {
status: {serialize: false}
}
});
This would strip out status.

Related

Ember {embedded: 'always' } on model Vs Serializer

I was reading through Ember docs and some examples on working with Embedded Object like JSON in Ember.
I came across the EmbeddedRecordsMixin feature and saw that we can write code like below to tell it is embedded record.
import DS from 'ember-data';
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
author: { embedded: 'always' },
}
});
Qouting the below from Ember page
Note that this use of { embedded: 'always' } is unrelated to the { embedded: 'always' } that is defined as an option on DS.attr as part of defining a model while working with the ActiveModelSerializer. Nevertheless, using { embedded: 'always' } as an option to DS.attr is not a valid way to setup embedded records.
And i have also seen model written like this.
App.Child = DS.Model.extend({
name: DS.attr('string'),
toys: DS.hasMany('toy', {embedded: 'always'}),
});
Where child object has toys object embedded.
Going by the first example, can i write the child serailizer as below?
App.ChildSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
toys: {embedded: 'always'}
}
});
Can someone help me understand the difference between these two {embedded: 'always'} and what to use when?
Thanks
Short answer: yes you can and you should.
Well, as far as I know, ember (especialy ember-data) is build to work perfectly with a Rails backEnd.
Rails have a module called ActiveModelSerializer to serialize resources and their related attributes and relationships. Into this module, you can use an option embedded: 'always' to serialize the whole targeted relationship and not only the ids when your client ask for a ressource.
If you use it Rails side (server), you can handle it Ember side (client) by putting this option into your model if you want your ember-data store to handle it easily. It's just an 'echo' to this ActiveModelSerializer module functionality.
On the other side, if for example you create/update an object with many relationships, there is 2 ways to deal with it. The first is to first save object's relationships and then, on success, save the object itself. The second is to send it at once to your server with the option {embedded: 'always'} into your model's serializer, into the relationship you want to send at the same time (embedded) at the object itself.
Ember probably encourage to use this into the serializer, because putting this into model seems only related to a specific Rails option, and it's not straightforward at all. Moreover, putting this into the serializer fulfill this role, with or without ActiveModelSerializer.
Hope it's clear for you, if not, let me know so I can edit this.

How to use Ember Data with Nested Resources

My application backend has several resources. A model is exposed for each resource.
The entry point to all other resources is through the User model. What I mean is, given a User we can find BlogPost. Given a BlogPost we can find Comments etc.
In Ember terminology, we could say:
User hasMany BlogPost
BlogPost hasMany Comment
Comment belongsTo BlogPost
By backend exposes REST APIs of the form:
GET /api/v1/users/1
GET /api/v1/users/1/blog_posts/1
GET /api/v1/users/1/blog_posts/1/comments/1
I'm trying to figure out how to use Ember Data to fetch Comment belonging to a certain BlogPost belonging to a certain User.
From what I see, if I define a typical Ember model for Comment:
App.Comment = DS.Model.extend({
...
blogPost: DS.belongsTo('App.BlogPost')
});
and in the CommentRoute I have the following:
var CommentRoute = MessageRoute.extend({
model: function(params) {
this.store.find('comment')
},
The request is sent to:
/api/v1/comments
I don't even know where to begin in order for Ember Data to use urls of the form:
GET /api/v1/users/1/blog_posts/1/comments/1
I've seen several similar questions asked (see links below), but haven't seen a definitive answer to any of them. Most of them are almost a year old when ember-data, perhaps, did not have such functionality (or so it is claimed in some of these threads).
I ask again to confirm whether ember-data has such functionality or not.
Similar Questions:
Ember Data nested Models
Canonical way to load nested resources
Deep nested routes
The best way to handle it is with links. If you don't want to do it like that, it is far from supported, and difficult to hack in (the pipeline just doesn't easily pass the information around). Personally I'd recommend rolling your own adapter in that case (Ember without Ember Data).
App.Foo = DS.Model.extend({
name: DS.attr('string'),
bars : DS.hasMany('bar', {async:true})
});
App.Bar = DS.Model.extend({
foo: DS.belongsTo('foo'),
});
json:
{
id: 1,
name: "bill",
links: {
bars: '/foo/1/bars'
}
}
Example: http://emberjs.jsbin.com/OxIDiVU/971/edit

Ember children defined by a hasMany relationship suddenly change to embedded model

I have the following model defined:
App.Post = DS.Model.extend({
title: DS.attr('string'),
comments: DS.hasMany('comment')
});
App.Comment = DS.Model.extend({
message: DS.attr('string')
});
If I create a Post entry with a Comment, the JSON stored in my browsers local storage references the Comments as an array of IDs which works great:
...
"o3duh":{
"id":"o3duh",
"title":"How to write Ember",
"comments":[
"jf0a2"
]
}
...
However, the moment I add another Post, the JSON suddenly changes such that Comments are embedded:
...
"o3duh":{
"id":"o3duh",
"title":"How to write Ember",
"comments":[
{
"message":"First!"
}
]
},
"6kudl":{
"id":"6kudl",
"title":"Learning Ember is painful",
"comments":[
]
}
...
Why is this happening? Can I prevent it? This is causing me problems because once it changes into this embedded format, the data cannot be read by the LSAdapter when reloading the page.
Here is a JSBin so you can see it happen for yourself and see the full JSON etc. To reproduce the problem, just create a post and add a comment then you can refresh the page without problem. Then add another post and try to refresh the page.
I'm not sure if the problem is with ember-data or the localstorage adapter.
I solved the problem by modifying the LocalStorageAdapter so that it only attempts to persist JSON in the expected format.
You can see the pull request I submitted to the original author here: https://github.com/rpflorence/ember-localstorage-adapter/pull/26
Hopefully it will either get folded into the LSAdapter project, or better still, someone will come up with a better solution ;)
I was able to fix the JSON issue by defining the inverse relationship on Comment:
App.Comment = DS.Model.extend({
message: DS.attr('string'),
post: DS.belongsTo('post')
});
There are new issues now, but hopefully this will help.

Non-persistent attributes in EmberJS

Does anyone know of a way to specify for an Ember model an attribute which is not persisted?
Basically, we're loading some metadata related to each model and sending that data to Ember via the RESTAdapter within the model. This metadata can be changed in our app, but is done via using an AJAX call. Once the call succeeds, I want to be able to update this value within the model without Ember sticking its nose in this business by changing the model to the uncommitted and doing whatever it does with transactions behind the scenes.
I also have the problem that this metadata, which is not data from the model's database record, is passed by the RESTAdapter back to the server, which doesn't expect these values. I am using a RoR backend, so the server errors out trying to mass-assign protected attributes which aren't meant to be attributes at all. I know I can scrub the data received on the server, but I would prefer the client to be able to distinguish between persistent data and auxiliary data.
So, to the original question: is there any alternative to Ember-Data's DS.attr('...') which will specify a non-persistent attribute?
The other answers to this question work with Ember data versions up to 0.13, and no longer work.
For Ember data 1.0 beta 3 you can do:
App.ApplicationSerializer = DS.RESTSerializer.extend({
serializeAttribute: function(record, json, key, attribute) {
if (attribute.options.transient) {
return;
}
return this._super(record, json, key, attribute);
}
});
Now you can use transient attributes:
App.User = DS.Model.extend({
name: DS.attr('string', {transient: true})
});
These attributes won't be sent to the server when saving records.
When this PR get's merged it will be possible to flag properties as readOnly. But till then there are some workarounds to this, e.g. overriding your addAttributes method in the Adapter and deal with your special properties, here an example how this could look like:
Define your Model by adding the new option readOnly:
App.MyModel = DS.Model.extend({
myMetaProperty: DS.attr('metaProperty', {readOnly: true})
});
and then on the Adapter:
App.Serializer = DS.RESTSerializer.extend({
addAttributes: function(data, record) {
record.eachAttribute(function(name, attribute) {
if (!attribute.options.readOnly) {
this._addAttribute(data, record, name, attribute.type);
}
}, this);
}
});
what this does is to loop over the attributes of your model and when it find's an attribute with the readOnly flag set it skips the property.
I hope this mechanism works for your use case.
Following this answer, to prevent a field from being serialized, override the default serializer for your model:
In app/serializers/person.js:
export default DS.JSONSerializer.extend({
attrs: {
admin: { serialize: false }
}
});
See here for the source PR. This solution works in Ember Data 2, and should work in older versions as well.
Update
This answer is most likely out of date with the current releases of Ember Data. I wouldn't use anything in my answer.
I'm answering this question for reference, and because your comment indicated that the record remains isDirty, but here is my solution for read-only, non-persistent, non-dirty attributes.
Overriding the addAtributes method in your Serializer prevents readOnly attributes from being sent to the server, which is probably exactly what you want, but you need to extend (or reopen) your adapter to override the dirtyRecordsForAttributeChange to prevent the record from becoming dirty:
App.CustomAdapter = DS.RESTAdapter.extend({
dirtyRecordsForAttributeChange: function(dirtySet, record, attrName, newValue, oldValue) {
meta = record.constructor.metaForProperty(attrName);
if (meta && meta.options.readOnly) { return; }
this._super.apply(this, arguments);
};
});
Then you can use readOnly attributes like so:
App.User = DS.Model.extend({
name: DS.attr('string', {readOnly: true})
});
user = App.User.find(1); # => {id: 1, name: 'John Doe'}
user.set('name', 'Jane Doe'); #
user.get('isDirty') # => false
This setup is working for me.

How to change Record on client directly, without transaction

I have a very common situation, if there is a term for it, well I am not aware of it then.
A record is having fields: id, enabled, text, etc...
and having POST /record/enable to enable or disable record, as it invoke is bigger process on server.
So, once callback from normal POST is received, I want to update record.enabled to true locally, which should not be part of any transaction. and should be updated directly.
How to achieve this?? Or what is better alternative for such requirement?
I think something along these lines should do:
App.PObject = DS.Model.extend({
id: DS.attr('number'),
name: DS.attr('string'),
nestedObject: function() {
if (!this.nestedObj) {
this.nestedObj = Ember.Object.create({
active: false
});
}
return this.nestedObj
}.property()
});
As i have seen so far, Model is dirty only when its attributes, that are defined as DS.attr, change.
EDIT: Realized later that, This solution doesn't work.
Proper term of my problem is: transient field
Transient field of an Object is ignored for serialization/deserialization (like Java ORMs ignores transient fields) http://en.wikipedia.org/wiki/Transient_(computer_programming
I have figured out a solution. Its really simple.
App.PObject = DS.Model.extend({
id: DS.attr('number'),
name: DS.attr('string'),
//will not be part of Ember-data persistence, but will be populated on GET
enabled: null
});
Object (Record) properties which are not attributes are ignored.