I've been struggling for the past few days with primary keys and the last version of Ember Data.
I first read how to do it on the Breaking Changes file on GitHub, but it's apparently outdated. I tried several other ways (with the help of Peter Wagenet on IRC), but none of them seem to work.
I would like to make slug my primary key on my model, and also since I'm working with MongoDB, I would like to use _id instead of id.
Has anyone figured out how to do this? My underlying problem is that model records get loaded twice when I do several App.MyModel.find() on the model.
As of Ember Data 1.0 beta you define primaryKey on the respective serializer.
For the entire app
App.ApplicationSerializer = DS.RESTSerializer.extend({
primaryKey: '_id'
});
For a single type
App.FooSerializer = DS.RESTSerializer.extend({
primaryKey: '_id'
});
You will still refer to it as id on the model, but Ember Data will serialize/deserialize it to _id during transfer.
Example: http://emberjs.jsbin.com/OxIDiVU/635/edit
Read More about it here: http://emberjs.com/api/data/classes/DS.RESTSerializer.html#property_primaryKey
I would like to make slug my primary key on my model, and also since I'm working with MongoDB, I would like to use _id instead of id.
Use the adapter's map API to specify the attribute that should be used as primary key:
App.Adapter.map('App.Person', {
primaryKey: '_id'
});
See serializer.js api docs for detail. If you need to further customize how the records id is serialized, use the addId hook described here.
Since ember-data is still under active development, documentation on this feature is somewhat limited and may change before 1.0 release. In the meantime refer to the ember-data test suite to see examples of this in action. For example:
mapped primary keys are respected when serializing a record to JSON demonstrates how an _id attribute will be included in json when a record is saved
mapped primary keys are respected when materializing a record from JSON shows how JSON with _id attribute will be transformed into a record with the correct id
In case the solution suggested by Nikita doesn't work (didn't for me using revision 11 of ember-data), here is how I changed the primary key when working with the RESTAdapter:
App.Adapter = DS.RESTAdapter.extend({
serializer: "App.MySerializer"
});
// notice we extend the RESTSerializer, not Serializer!
App.MySerializer = DS.RESTSerializer.extend({
primaryKey: function(type) {
return '_id'; // you get it...
}
});
I'm aware that this probably won't help the OP anmore but I still post it as it may be helpful for some of you.
Try to extend your adapter like this:
App.RESTSerializer = DS.RESTSerializer.extend({
init: function() {
this._super();
this.map('App.MyModel', {
primaryKey: "_id"
});
}
});
I use MongoDB and Ember-Data 1.0.0-beta.6 in my application and the _id posed a problem in Ember 1.4.0 for me too. Here's what I've done to solve the problem, assuming the returned JSON array is nested in the root property "people":
App.ApplicationSerializer = DS.RESTSerializer.extend({
normalizeHash: {
people: function(hash) {
hash.id = hash._id;
delete hash._id;
return hash;
}
}
});
This is, of course, an application-wide serializer but you can limit it to a specific path with something like App.MyPathSerializer = DS.RESTSerializer.extend({ ... });
Related
I have an endpoint /activities which behaves in a typical fashion for a RESTful API using JSON. I can of course do things like the following:
model: function() {
return this.store.find('activity');
}
to return a list of activities. Or I can:
model: function(params) {
return this.store.find('activity', params.id);
}
get a specific Activity record. This works fine. But the API also allows certain filtering to be done using URL parameters. So for instance,
GET /activities/type/exercise
Unlike a full list it would only bring back activities which are associated with "exercise". What is the best way for one to pull back data using ember-data? At a bare minimum I want the data to come back, be associated to the correct model, and be iterable via a `DS.RecordArray or similar object.
Ideally I would also like to avoid making network requests every time I hit this route although possibly this is something that I can't get "out of the box". Still interested if there are any best practices.
Ember can already query for data, by default using query params i.e.
store.find(activities, {type: 'exercise'}); // /activities?type=exercise
you could override findQuery on your activities adapter so that if conforms to your api:
// /adapters/activity.js
import DS form 'ember-data';
export default DS.RESTAdapter.extend({
findQuery: function(store, type, query) {
for(var key in query) break; // get first key in the example 'type'
var path = ['', key, query[key]].join('/');
return this.ajax(this.buildURL(type.typeKey) + path, 'GET');
},
});
I haven't tested this but it should work.
My users JSON returns the result like so:
{
users: [{...}]
some_other_data: {}
some_other_data2 [{...}]
}
If i remove the other keys from the JSON result - then it'll work fine, but i really need those extra other "keys" to be included in the JSON result.
I tried to apply a solution from the answer here - return single record with ember-data find() and multiple params
But changing my code
App.Store = DS.Store.extend
revision: 12
adapter: 'DS.RESTAdapter'
to something similar to the solution above just messes up the paths that my ember app requests.
There is no support for extra properties in your JSON response in ember-data.
The only 'extra' properties that are supported are meta and since, for example:
{
meta: {}
since: {}
users: [{...}]
}
This extra properties can later be extracted by hooking into the extractMeta function of your serializer, something like this:
App.CustomRESTSerializer = DS.RESTSerializer.extend({
extractMeta: function(loader, type, json) {
var meta, since;
meta = json[this.configOption(type, 'meta')];
since = json[this.configOption(type, 'since')];
if (!meta || !since) { return; }
Ember.set('App.metaDataForLastRequest', meta);
Ember.set('App.sinceForLastRequest', since);
this._super(loader, type, json);
}
});
App.Store = DS.Store.extend({
adapter: DS.RESTAdapter.create({
serializer: App.CustomRESTSerializer
})
});
IMO, if you can, you should change the JSON returned from your backend and retrieve the extra data by defining an extra model and bind it to your User model with a relationship like hasMany or belongsTo to make things work.
Hope it helps.
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.
I'm experimenting with Ember.js, Node.js and MongoDB. I've based my noodling on the excellent video on the Ember site and Creating a REST API using Node.js, Express, and MongoDB. I've hit a roadblock on the Ember.js side trying to get my create record functionality working.
Currently, when a user creates a record in my sample application, two will show up in the listing. This is happening because when I save my record to the server, a new ID is created for that record by MongoDB. When the response is returned (containing the object with the new ID), the record is duplicated until I refresh the page. One has the new Mongo-supplied ID and the other has a NULL.
Here is where I create the new object:
App.NewwineRoute = Ember.Route.extend({
model: function() {
return App.Wine.createRecord();
}
});
Here is where I store the record to MongoDB:
App.NewwineController = Ember.ObjectController.extend({
doneEditing: function() {
this.get('store').commit();
this.transitionToRoute('wines');
}
});
I'm curious what the best way to handle this is when using ember-data? I've tried all kinds of tricks and worn my connection out searching for examples.
The closest I've been is a nasty hack of setting an id of -1 to the new object/record and then attempting to remove it after the commit. Sadly, the object/record wouldn't really be removed, just show up blank in my list. Plus, I couldn't create any objects/records with an id of -1 from that point on (because one already exists). Seems like a dead-end.
Thanks in advance.
>'.'<
SOLUTION:
I was able to glean the solution to the problem from the following AMAZING examples:
Ember.js CRUD REST
Node.js REST Server for Ember
For others that have had the ID problem, the App.Adapter in the above example handles the mapping from "_id" to "id".
App.Adapter = DS.RESTAdapter.extend({
serializer: DS.RESTSerializer.extend({
primaryKey: function (type){
return '_id';
}
})
});
Inside of the example's Node.js service, the DB calls map "id" to "_id":
collection.findOne({'_id':new BSON.ObjectID(id)}, function(err, item) {
Thanks again to ddewaele for sending over the example, it was a great tutorial for linking these technologies together.
I'm currently using the FixtureAdapter in my Ember app, but when I switch to the RESTAdapter, my URLs no longer work.
The app is a scorekeeping type thing, and I want users to be able to log all the scores without having to be connected to the Web. After the game is finished they can optionally save all the data to the server.
Now, when Ember wants to route to say, matches/:match_id, the ID isn't there because I didn't commit anything to the server/store, so my models don't yet have an ID and I get URLs like: /match/null/games/null
Is this expected behaviour? And if so, is there a workaround? I thought about using model.clientId and then overriding the model hook for each route to try and fetch the Model from the store using the id when present and falling back to clientId. Any other ideas?
UPDATE March 10, 2013:
The following seems to fit my needs and allows to (for now) forget about moving back and forth between local storage and the REST adapter:
App.Store = DS.Store.extend({
revision: 11,
adapter: DS.RESTAdapter.extend({
namespace: 'api/v1',
bulkCommit: true,
generateIdForRecord: function(store, record) {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
}
})
});
UUID function taken from: Create GUID / UUID in JavaScript?
If a record hasn't been committed, then it shouldn't have an id yet. Furthermore, a url that serializes the application state of viewing that record doesn't make any sense because the record won't exist in another browser until it is committed. You couldn't just paste the url elsewhere and have it load where you left off.
I think what you really want to do is serialize the application state differently (ie. generate a less specific url) when the record is uncommitted. You can achieve this by overriding the serialize method in your route.
For example,
App.PostRoute = Ember.Route.extend({
serialize: function(model, params) {
if (model && model.get('isNew')) {
// corresponds to path '/post/:post_id'
return { post_id: 'new'}
}
return this._super(model, params);
}
});
Now if you call transitionToRoute('post', post) from your controller, where post is a newly created but uncommitted record, the application state will be serialized to the path /post/new. If you pass it a committed record with an id, it will be serialized as usual.