Creating and updating nested objects in Ember - javascript

I got nested JSON data from the server like this:
{
name: "Alice",
profile: {
something: "abc"
}
}
and I have the following model:
App.User = Ember.Object.extend({
name: null,
profile: Ember.Object.extend({
something: null
})
})
If I simply do App.User.create(attrs) or user.setProperties(attrs), the profile object gets overwritten by plain JS object, so currently I'm doing this:
var profileAttr = attrs.profile;
delete attrs.profile
user.setProperties(attrs); // or user = App.User.create(attrs);
user.get('profile').setProperties(profileAttrs);
It works, but I've got it in a few places and in the real code I've got more than one nested object, so I was wondering if it's ok to override User#create and User#setProperties methods to do it automatically. Maybe there's some better way?

Based on your comment, you want the automatic merging behaviour you get with models (the sort of thing you get with .extend()). In that case, you could try registering a custom transformer, something like:
App.ObjectTransform = DS.Transform.extend({
deserialize: function(json){
return Ember.Object.create(json);
}
});
App.User = DS.Model.extend({
profile: DS.attr('object')
});
See: https://github.com/emberjs/data/blob/master/TRANSITION.md#json-transforms

If you are doing your server requests without an adapter you can use the model class method load() with either an array of json objects or a single object. This will refresh any known records already cached and stash away the JSON for future primary key based lookups. You can also call load() on a model instance with a JSON hash as well but it will only update that single model instance.
Its unclear why you are not using an adapter, you can extend one of the Ember Model adapters and override the the record loading there, eg. extend from the RESTAdapter and do any required transform on the JSON if required by overriding _loadRecordFromData
You can also override your models load function to transform data received if required as well. The Ember Model source is fairly easy to read so its not hard to extend to your requirements.

Related

Ember.js (2.5.0) how to set nested object value

Sounds like a simple enough thing to do yet is causing me all sorts of grief.
I have a simple server model which has a few nested objects,
export default DS.Model.extend({
type: DS.attr('string'),
attributes: DS.attr(),
tasks: DS.attr()
});
I can create a new record in the route using
export default Ember.Route.extend({
model() {
return this.store.createRecord('server');
},
actions: {
create(server) {
server.save().then(() => this.transitionTo('servers'));
}
}
});
and in the related .hbs I'm setting a few properties of attributes and tasks using value=model.attributes.name from a form for example.
This all works fine. I however want to add a few more properties from the route during create such as default values.
Using server.set('attributes.size', 'large'); doesn't work as Ember doesn't know about size yet as it's a new record.
I can use setProperties but this seems to wipe out every other value
server.setProperties({
attributes: {
size: "large"
},
tasks: {
create: true
}
});
size is now correctly set, however name is now null because I didn't specify it in the setProperties...
What's the proper way to go about this? Surely I don't need to map out all the properties in setProperties? That seems wasteful and very error prone.
Something I've thought is should attributes just be its own model and have a relationship with Server? Even though this is always a 1-to-1 and 1-to-1 relationship?
I would recommend using ember-data-model-fragments addon as a solution in this case.
https://github.com/lytics/ember-data-model-fragments
Other option using a separate model for attributes and setting up a 1-to-1 relation. Both would be belongsTo, however it is depend on your database and API also, so you have to align your backend system to match with this new structure.

Ember route or controller expose data model as json

I am trying to work with Ember.js
Can I expose my data model as JSON through a route or controller?
I have an object like this saved in the store:
this.store.createRecord('Person', {
id: 1,
name: this.get('name'),
email: this.get('email')
});
I want to expose this data from a route or controller as JSON object. I don't want to use any view.
Is it possible to do this?
Thanks for help!
EDIT
My route is:
App.ResultRoute = Ember.Route.extend({
model: function() {
return this.store.find('person', 1);
}
});
There is '1' because I want only this record.
In this way It works and I see in the view the {{name}} and the {{email} of the Person object.
I want to see only the JSON, I tried to do how you suggest me :
App.ResultRoute = Ember.Route.extend({
afterModel: function (model) {
model.get('content').forEach(function (item) {
console.log(item.get('content'));
});
}
});
But I receive this error:
Uncaught Error: Assertion Failed: Error: More context objects were passed than there are dynamic segments for the route: error
What is my error?
The way I would do this would be, I would have an api in my model which would return a plain json object to whoever asked it. So the Person model would have a getPersonDetails method which will hide all the internal details, including the attributes and associations and whatever else, and return the state of the person object it is invoked upon.
So, for example, if you wanted to display a table of persons or something, you would do a createRecord, and just ask the newly created person object for it's details.
Start from the beginning of this guide. http://emberjs.com/guides/routing/specifying-a-routes-model/ It will show you how to specify a model for a route.
Then, read this entire guide on controllers: http://emberjs.com/guides/controllers/
In general, you would access that data from the route's model hook with:
this.store.find('person') // All records
If you wanted to access that first object as JSON, you could do:
var person_JSON = this.store.find('person').then(function (persons) {
//The persons records are now available so you can do whatever you want with them
console.log(persons.objectAt(0).get('content'));
});
You could also iterate over all records and strip out the content to produce raw json without the Ember wrapping... Just depends on what you need to really do.
Really the best place to put this would be the route's afterModel hook, though. You wouldn't be working with a promise, as Ember would have dealt with that for you:
afterModel: function (model) {
model.get('content').forEach(function (item) {
console.log(item.get('content'));
});
}
Hope that helps.
Edit: Since you have one record try this:
afterModel: function (model) {
console.log(model.get('content'));
}

Backbone model.destroy with MongoDB persistence

I'm new to Backbone and to MongoDB. I'm running into problems destroying models (that are backed by MongoDB). I believe it has something to do with the fact that a MongoDB "document" uses the abnormal _id attribute and is not wired up with Backbone by default. I've tried setting idAttribute: '_id' which does not seem to solve the issue.
A sample Mongo document looks like:
{
_id: Object
$oid: "527303e82f3504ba5bf4b21f"
__proto__: Object
feeling: "ok"
location: "california"
name: "Kevin"
}
models.coffee
class Models.Campaign extends Backbone.Model
urlRoot: "http://localhost:4567/api/campaigns"
# setting idAttribute: '_id' causes issues rendering the Backbone collection
controller.coffee
...
model.destroy() #=> only removes the item from the dom but does not make a DELETE request
Am I able to pass in an id to the destroy() function?
Does anyone have advise in how to properly wire up Backbone with MongoDB without having to monkeypatch a ton of built-in Backbone functions?
I think you'll have an easier time if you add a parse to your model to sort out the confusing data you're getting from the server. You don't need or care about _id, you do care about $oid, and Backbone would prefer to work with an id property; you can sort out all three with a simple parse in your model like this:
parse: function(response) {
var parsed = _(response).pick(
'feeling',
'location',
'name'
);
parsed.id = response.$oid;
return parsed;
}
Demo: http://jsfiddle.net/ambiguous/pn773/
You'd probably be fine if your parse modified response rather than making a semi-copy using _.pick and modifying that copy, however, parse doesn't technically own response so it would be rude to change it and there could be surprising side effects. Good habits, etc.
Then when Backbone tries to convert the server's data to a model, it will end up working with:
{
id: "527303e82f3504ba5bf4b21f",
feeling: "ok",
location: "california",
name: "Kevin"
}
and everyone should be happy:
Backbone won't see the irrelevant _id.
Backbone won't see $oid and you won't need to set idAttribute to anything special.
Backbone will see an id like it expects and all the standard machinery will use the BSON ObjectId as the model's unique identifier.
The simple method should get you your DELETE /api/campaigns/527303e82f3504ba5bf4b21f request when you model.destroy().

How do I save just a subset of a Backbone Model's attributes to the server without triggering a PATCH instead of a POST

The API I am hitting explodes if I send any "extra" properties back to the server. Now, I'm sure I've broken some MVC rules or something by having properties on my client side Backbone model that don't exist server side, but, what I need to do is initiate a CREATE request but only pass through some of the attributes from my model I am initiating the CREATE request from.
I can easily do this in backbone:
Model.save({key: val}, {patch: true});
And then modify the Backbone sync methodMap defaults patch routes to POST instead of PATCH and that nets me exactly what I'm looking for except that later on I want to actually be able to PATCH (instead of POST) when appropriate. I just need it to POST as if it were PATCHing for create actions only (not for update actions!).
So, in short, I need to take something like this:
new Backbone.Model({'foo': 'bar', 'baz': 'beh'});
And tell it to sync itself to the server, but ONLY send 'foo' across and not send 'baz', but it has to send it as a POST (it can't be a PATCH).
Any ideas?
Below is a save override that can be placed in a base Backbone model or any individual model as appropriate. It may not cover every use case but it seems to fit this one.
JS Fiddle: http://jsfiddle.net/hEN88/1/
This override allows you to set a 'serverAttrs' array on any model to filter which properties are sent to the server.
save: function (attrs, options) {
attrs = attrs || this.toJSON();
options = options || {};
// If model defines serverAttrs, replace attrs with trimmed version
if (this.serverAttrs) attrs = _.pick(attrs, this.serverAttrs);
// Move attrs to options
options.attrs = attrs;
// Call super with attrs moved to options
Backbone.Model.prototype.save.call(this, attrs, options);
}
This question sounds like it may be a dupe of Exclude model properties when syncing (Backbone.js) and Backbone.js/express.js parameters for model.save()
The answer in both of those cases came down to overriding the save() function.
You can override toJSON method
// set viewOnlyAttrs for attributes no need to save
viewOnlyAttrs: ['url'],
toJSON: function(options) {
var obj = this;
// if is calling from model's save method
if(_.has(options || {}, 'emulateHTTP') && obj.viewOnlyAttrs)
return _.omit(obj.attributes, obj.viewOnlyAttrs);
return Backbone.Model.prototype.toJSON.call(this, options);
}

Persisting & loading metadata in a backbone.js collection

I have a situation using backbone.js where I have a collection of models, and some additional information about the models. For example, imagine that I'm returning a list of amounts: they have a quantity associated with each model. Assume now that the unit for each of the amounts is always the same: say quarts. Then the json object I get back from my service might be something like:
{
dataPoints: [
{quantity: 5 },
{quantity: 10 },
...
],
unit : quarts
}
Now backbone collections have no real mechanism for natively associating this meta-data with the collection, but it was suggested to me in this question: Setting attributes on a collection - backbone js that I can extend the collection with a .meta(property, [value]) style function - which is a great solution. However, naturally it follows that we'd like to be able to cleanly retrieve this data from a json response like the one we have above.
Backbone.js gives us the parse(response) function, which allows us to specify where to extract the collection's list of models from in combination with the url attribute. There is no way that I'm aware of, however, to make a more intelligent function without overloading fetch() which would remove the partial functionality that is already available.
My question is this: is there a better option than overloading fetch() (and trying it to call it's superclass implementation) to achieve what I want to achieve?
Thanks
Personally, I would wrap the Collection inside another Model, and then override parse, like so:
var DataPointsCollection = Backbone.Collection.extend({ /* etc etc */ });
var CollectionContainer = Backbone.Model.extend({
defaults: {
dataPoints: new DataPointsCollection(),
unit: "quarts"
},
parse: function(obj) {
// update the inner collection
this.get("dataPoints").refresh(obj.dataPoints);
// this mightn't be necessary
delete obj.dataPoints;
return obj;
}
});
The Collection.refresh() call updates the model with new values. Passing in a custom meta value to the Collection as previously suggested might stop you from being able to bind to those meta values.
This meta data does not belong on the collection. It belongs in the name or some other descriptor of the code. Your code should declaratively know that the collection it has is only full of quartz elements. It already does since the url points to quartz elements.
var quartzCollection = new FooCollection();
quartzCollection.url = quartzurl;
quartzCollection.fetch();
If you really need to get this data why don't you just call
_.uniq(quartzCollecion.pluck("unit"))[0];

Categories