Ember.js : How to modify data in RESTSerializer - ember.js

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'),

Related

Access controller from nested object

I need to access an ember controllers context from within a regular object. Currently I am saving a reference to the controller context in the init() method that seems a little crappy.
let self = this //saving the context here
export default Ember.controller.extend({
init() {
this._super(...arguments);
self = this;
},
settings: {
crud: {
read: {
enabled: true,
default() {
return self.get('blah.blah'); //Need to access the controller context
}
}
}
}
});
So I need access to the controller self.get('blah.blah'). Is there a better way to do this?
Use a computed property closure
export default Ember.controller.extend({
settings: Ember.computed(function() {
const controller = this;
return {
crud: {
read: {
enabled: true,
defaults() {
return controller.get('blah.blah');
}
}
}
};
})
});
Accessing object
this.get('settings').crud.read.defaults(); // "blah.blah"

ember data array UI not updating on pushObject

I have a list of product-tag that I fetch for my model.
Route:
model: function() {
return {
product_tags: this.store.find('product-tag', {merchant: merchId})
}
}
I have a component that adds tags to the model, however when after I create the record and push it into the model (as suggested on other posts) my UI still isn't updating.
addTag: function(name) {
tag = this.store.createRecord('product-tag', {
name: name
});
this.model.product_tags.toArray().addObject(tag);
tag.save();
}
//model merchant.js
export default DS.Model.extend({
user_id: DS.attr('number'),
product_tags: DS.hasMany('product-tag', {async: true})
});
//model product-tag.js
export default DS.Model.extend({
merchant: DS.belongsTo('merchant'),
name: DS.attr('string'),
});
What am I doing wrong? Thanks in advance.
You should make it array in the route, so u can use it always afterwards like u want. Your calling toArray() which makes a new Array instance, then your model is not hooked to the array u just made.
model: function() {
return {
product_tags: this.store.query('product-tag', {merchant: merchId}).then(function(pt) {
return pt.toArray();
});
}
}
var x = this.get('model.product_tags') === model's p_t // true
var y = this.get('model.product_tags').toArray() === model's p_t // false
Later on just do
addTag: function(name) {
this.get('store').createRecord('product-tag', {
name: name
}).save().then(function(saved){ 
this.get('model.product_tags').pushObject(saved);
}.bind(this);
}

Detecting change in state for Ember object

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

Ember.js Fixture findQuery

I'm trying to change my URLs in Ember to use a property on a model (title), instead of the ID. I can get everything to work if I click on a link-to, but when I do a fresh reload of the models route, everything seems to fall apart. Within queryFixtures I can get return the desired item, and logs to the console by logging 'experiment' to the console in the model hook.
App.ExperimentRoute = Ember.Route.extend({
serialize: function(experiment, params) {
return {
experiment_title: experiment.get('urlFriendlyName')
}
},
model : function(params) {
experiment = this.get('store').find('experiment', { title: params.experiment_title });
return experiment;
}
});
My store:
App.Store = DS.Store.extend({
adapter: DS.FixtureAdapter.extend({
queryFixtures: function(fixtures, query, type) {
function urlFriendlyTitle(title) {
var ret = title.replace(/ /g,"-"); // replace spaces
// do other replacements that make sense in your case, e.g.:
ret = ret.replace(/&/g,"and");
//... and so on and so forth
// encode the remaining characters
ret = ret.toLowerCase();
ret = encodeURIComponent(ret);
return ret;
}
return fixtures.filter(function(item) {
if(urlFriendlyTitle(item.title) == query.title) {
// console.log(item);
return item;
}
});
}
})
});
UPDATE:
App.ExperimentRoute = Ember.Route.extend({
model : function(params) {
experiment = this.get('store').find('experiment', { });
console.log(Ember.inspect(experiment))
return experiment;
}
});
Running the above outputs '<DS.PromiseArray:ember325>' in the console
App.Router.map(function () {
// Add your routes here
this.resource('category', { path: '/:category_id'}, function() {
this.resource('experiment', { path: '/:experiment_title' }, function() {
this.resource('device', { path: '/:device' });
});
});
});
App.ExperimentRoute = Ember.Route.extend({
setupController: function(controller, model) {
console.log(Ember.inspect(model))
console.log(Ember.inspect(controller))
}
})
This route outputs <App.ExperimentController:ember388> <DS.AdapterPopulatedRecordArray:ember326>

Mapping data in ember-data

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