I have a basic ember-data model object:
App.Group = DS.Model.extend({
//attributes
});
I have json which is structured like this:
root.levelone.leveltwo.property
I don't want to map this project as is but would like to map property in the json to property in the model like this:
App.Group = DS.Model.extend({
property: DS.attr('string')
});
Is it possible to define a mapping that is different from the incoming json? I don't have much control on what is coming from the server.
If this is not possible with ember-data, what is the best way to model this deep nesting?
FYI, the latest version of Ember (v.10) requires custom transforms to be defined on the DS.JSONTransforms object. And the 'to' and 'from' properties have been renamed to 'serialize' and 'deserialize'.
I'm not sure quite what you're asking but you can define custom DS.attr transforms.
Something like this maybe? Haven't tested it.
DS.attr.transforms.deepNest = {
from: function(serialized) {
return this.root2.property
},
to: function(deserialized) {
return { root2: property }
}
}
property: DS.attr('deepNest', {key: 'root1'})
IT changed FROM THIS:
DS.attr.transforms.object = {
from: function(serialized) {
return Em.none(serialized) ? {} : serialized;
},
to: function(deserialized) {
return Em.none(deserialized) ? {} : deserialized;
}
}
TO THIS:
DS.RESTAdapter.registerTransform('object', {
fromJSON: function(serialized) {
return Em.none(serialized) ? {} : serialized;
},
toJSON: function(deserialized) {
return Em.none(deserialized) ? {} : deserialized;
}
})
Ember data v 1.0 beta 2 requires this approach:
CustomTransform = DS.Transform.extend({
deserialize: function(serialized) {
...
},
serialize: function(deserialized) {
...
}
});
Ember.Application.initializer({
name: "customTransforms",
initialize: function(container, application) {
application.register('transform:custom', CustomTransform);
}
});
Related
I want to modify the attributes in model.
models/example.js:
export default DS.Model.extend({
value: DS.attr('number'),
modified_value: function() {
/*Some Calculations*/
return modified_value
}.property('value'),
})
above code works fine, but it creates another extra attribute in model. So to avoid this,I want to move it to the RESTSerializer. How to do this ?
Create a new file (example.js) in directory transforms.
export default DS.Transform.extend({
deserialize: function(serialized) {
return serialized;
// if (serialized) {
// return JSON.stringify(serialized);
// } else {
// return '';
// }
},
serialize: function(deserialized) {
return deserialized;
// if (deserialized) {
// return JSON.stringify(deserialized);
// } else {
// return [];
// }
}
});
In your model file example change the attr to DS.attr('example')
export default DS.Model.extend({
value: DS.attr('example'),
I'm using Ember API with a JSON API backend. The API accepts filters like this:
/users?filter[simple][name]=John
Right now, whenever I want to make a query, I'm doing this:
this.store.query('users', {
filter: {
simple: {
name: 'John'
}
}
});
It would be nice to avoid this verbosity by adding a helper function that works like this:
this.store.simpleQuery('users', { name: 'John' });
That function would pass its arguments directly to query(), just wrapping the query in { filter: { simple: ... } }.
Is this possible? How do I do this?
Well what is stopping you from creating your own method in the adapter to do exactly that?
// ... your adapter code
simpleQuery: function(modelName, query) {
return this.store.query('users', {
filter: {
simple: {
name: 'John'
}
}
});
}
// ...
You need to extend the default store. Add the following to app/services/store.js
import DS from 'ember-data';
export default DS.Store.extend({
simpleQuery(modelName, query) {
return this.query(modelName, {
filter: { simple: query }
});
},
simpleQueryRecord(modelName, query) {
return this.queryRecord(modelName, {
filter: { simple: query }
});
}
});
And you'll be able to do this:
this.store.simpleQuery('users', { name: 'John' });
this.store.simpleQueryRecord('users', { email: 'john#example.com' });
I am using Ember-Data and one of my properties is a dictionary data structure. I'd like any update to this dictionary to be converted into an action which sets the parent Model into a "dirty" state.
So here's the config:
Model
export default DS.Model.extend({
// standard types
foo: DS.attr('string'),
bar: DS.attr('number'),
baz: DS.attr('boolean'),
// dictionary (aka, flexible set of name value pairs)
dictionary: DS.attr('object')
});
Transform
export default DS.Transform.extend({
deserialize: function(serialized) {
return Ember.Object.create(serialized);
},
serialize: function(deserialized) {
return deserialized;
}
});
This works and let's assume for a moment that the "dictionary" property is defined as:
{
one: { prop1: foo, prop2: bar, prop3: baz },
two: 2,
three: "howdy",
many: [{},{},{}]
}
This means that an Ember Object has four properties. These properties can be a string, a number, an array, or an object. What I'd like is to have some way of identifying any changes to this underlying basket of attributes so I can propagate that to the Model and have it adjust its state to "dirty".
TL;DR - Working JS Bin example
In order to accomplish this you have to do the following:
1. Deserialize the raw object and all its nested deep properties to Ember Objects so they could be Observable
2. Add observers to your model for all existing keys dynamically on every change of the raw object reference, because it can change its content and scheme.
3. Remove these dynamic observers on every raw object reference change and assign the new ones
4. All dynamic properties changes will set timestamp property so that controllers could listen to it
This is a "Deep" transform I wrote in order to accomplish (1):
// app/transforms/deep.js
export
default DS.Transform.extend({
deserializeRecursively: function(toTraverse) {
var hash;
if (Ember.isArray(toTraverse)) {
return Ember.A(toTraverse.map(function(item) {
return this.deserializeRecursively(item);
}, this));
} else if (!Ember.$.isPlainObject(toTraverse)) {
return toTraverse;
} else {
hash = this.generatePlainObject(Ember.keys(toTraverse), Ember.keys(toTraverse).map(function(key) {
return this.deserializeRecursively(Ember.get(toTraverse, key));
}, this));
return Ember.Object.create(hash);
}
},
deserialize: function(serialized) {
return this.deserializeRecursively(serialized);
},
serialize: function(deserialized) {
return deserialized;
},
generatePlainObject: function(keys, values) {
var ret = {};
keys.forEach(function(key, i) {
ret[key] = values[i];
});
return ret;
}
});
This is a mixin for Models with deep raw objects which accomplish (2) & (3) & (4)
// app/mixins/dynamic-observable.js
export
default Ember.Mixin.create({
propertiesToAnalyze: [],
registerRecursively: function(toTraverse, path, propsToObserve) {
if (Ember.isArray(toTraverse)) {
propsToObserve.addObject(path + '.#each');
if (toTraverse.length > 0) {
this.registerRecursively(toTraverse[0], path + '.#each', propsToObserve);
}
} else if (!(toTraverse instanceof Ember.Object)) {
propsToObserve.addObject(path);
} else {
Ember.keys(toTraverse).forEach(function(propertyName) {
this.registerRecursively(Ember.get(toTraverse, propertyName), path + '.' + propertyName, propsToObserve);
}, this);
}
},
addDynamicObserver: function(propertyNameToAnalyze) {
var propertyToAnalyze = this.get(propertyNameToAnalyze),
propsToObserve = Ember.A([]),
currentDynamicProps = this.get('currentDynamicProps'),
propsToRemove = currentDynamicProps.filter(function(prop) {
return new RegExp('^' + prop + '.').test(prop);
});
propsToRemove.forEach(function(prop) {
Ember.removeObserver(prop, this, dynamicPropertiesObserver)
}, this);
currentDynamicProps.removeObjects(propsToRemove);
this.registerRecursively(propertyToAnalyze, propertyNameToAnalyze, propsToObserve);
propsToObserve.forEach(function(prop) {
Ember.addObserver(this, prop, this, 'dynamicPropertiesObserver');
}, this);
currentDynamicProps.addObjects(propsToObserve);
},
dynamicPropertiesObserver: function(sender, key, value, rev) {
this.set('dynamicPropertyTimestamp', new Date().getTime())
},
addDynamicObservers: function() {
this.get('propertiesToAnalyze').forEach(this.addDynamicObserver, this);
},
init: function() {
this._super();
this.get('propertiesToAnalyze').forEach(function(prop) {
Ember.addObserver(this, prop, this, Ember.run.bind(this, this.addDynamicObserver, prop));
}, this);
},
dynamicPropertyTimestamp: null,
currentDynamicProps: Ember.A([])
});
This is how you use the mixin on a model:
// app/models/some-object.js
import DynamicObservable from 'app/mixins/dynamic-observable';
export
default DS.Model.extend(DynamicObservable, {
dictionary: DS.attr('deep'),
propertiesToAnalyze: ['dictionary']
});
Finally, this is an array controller which its model is an array of some-object models
export
default Ember.ArrayController.extend({
message: '',
observeDictionaries: function() {
this.set('message', 'A dictionary has been changed. change time: ' + new Date().getTime());
}.observes('#each.dynamicPropertyTimestamp')
});
Let's say that I want to have URLs like /users/JoshSmith for maximum readability/shareability.
I set up my Router:
this.resource('user', path: '/users/:username')
And my route:
var UserRoute = Ember.Route.extend({
model: function(params) {
debugger
return this.store.find('user', { username: params.username });
}
});
But this findQuery function actually returns an array, since it's calling /users?username= instead of calling /users/:username like I would normally do.
I'm a little lost as to how I should be handling this; I'm assuming there's a convention out there, I just can't find it.
As suggested here: http://discuss.emberjs.com/t/find-by-different-property/2479
Just override serialize on your route.
var UserRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('user', { username: params.username });
},
serialize: function(model) {
return { username: model.get('username') };
}
});
This replaces the default which looks like this:
serialize: function(model) {
// this will make the URL `/posts/12`
return { post_id: model.id };
}
Source: http://emberjs.com/api/classes/Ember.Route.html#method_serialize
I had this same problem as well, findQuery always returns a record array. The way I got around this was to simply change my model hook in the router to
model: function(params) {
return this.store.find('user', { username: params.username }).then(function(users) {
return users.get('firstObject');
});
}
So when querying the model which returns a carrierwave hash, I'm getting his.get('pic')
"[object Object]" in Ember. So How do I get into the view?
{"record":{"id":1234,"first_name":"John","gravepic":{"url":"http://c185643.r43.cf1.rackcdn.com/uploads/record/gravepic/13267/IMG_0013.JPG","ios":{"url":"http://c185643.r43.cf1.rackcdn.com/uploads/record/gravepic/13267/ios_IMG_0013.JPG"},"thumb":{"url":"http://c185643.r43.cf1.rackcdn.com/uploads/record/gravepic/13267/thumb_IMG_0013.JPG"},"small":{"url":"http://c185643.r43.cf1.rackcdn.com/uploads/record/gravepic/13267/small_IMG_0013.JPG"},"medium":{"url":"http://c185643.r43.cf1.rackcdn.com/uploads/record/gravepic/13267/medium_IMG_0013.JPG"},"large":{"url":"http://c185643.r43.cf1.rackcdn.com/uploads/record/gravepic/13267/large_IMG_0013.JPG"}},"eulogy":"FATHER","deceased":true,"gender":"male","photos":[{"accuracy":null,"approval":null,"asset_caption":"","asset_name":null,"assetpic":{"url":"http://c185643.r43.cf1.rackcdn.com/uploads/photo/assetpic/270/iccfa_350x200_3.jpg","thumb":{"url":"http://c185643.r43.cf1.rackcdn.com/uploads/photo/assetpic/270/thumb_iccfa_350x200_3.jpg"},"small":{"url":"http://c185643.r43.cf1.rackcdn.com/uploads/photo/assetpic/270/small_iccfa_350x200_3.jpg"},"medium":{"url":"http://c185643.r43.cf1.rackcdn.com/uploads/photo/assetpic/270/medium_iccfa_350x200_3.jpg"},"large":{"url":"http://c185643.r43.cf1.rackcdn.com/uploads/photo/assetpic/270/large_iccfa_350x200_3.jpg"}},"copyright":null,"coredata_id":null,"created_at":"2013-02-09T10:36:13Z","id":270,"ignore":null,"is_approved":true,"latitude":null,"longitude":null,"record_id":"13267","updated_at":"2013-02-09T10:36:13Z","user_id":1},{"accuracy":null,"approval":null,"asset_caption":null,"asset_name":null,"assetpic":{"url":null,"thumb":{"url":null},"small":{"url":null},"medium":{"url":null},"large":{"url":null}},"copyright":null,"coredata_id":null,"created_at":null,"id":null,"ignore":null,"is_approved":true,"latitude":null,"longitude":null,"record_id":13267,"updated_at":null,"user_id":null}],"copies":[{"content":null,"created_at":null,"id":null,"is_approved":true,"name":null,"record_id":13267,"summary":null,"updated_at":null,"user_id":null}],}}
The next questions will be something like record.photos.first.medium.
Or record.gravepic.small
Just not sure how to get to something beyond "object object"
I'm not sure if this is still the exact API, but if you want multiple values out of the hash I suggest adding a new DS.attr type.
DS.RESTAdapter.registerTransform('json', {
deserialize: function(serialized) {
return Em.isNone(serialized) ? {} : serialized;
},
serialize: function(deserialized) {
return Em.isNone(deserialized) ? {} : deserialized;
}
})
App.Something = DS.Model.extend({
gravepic: DS.attr("json")
})