I thought I had this figured out using reduce(), but the twist is that I need to roll up multiple properties on each record, so every time I am returning an object, and the problem I'm having is that previousValue is an Ember Object, and I'm returning a plain object, so it works fine on the first loop, but the second time through, a is no longer an Ember object, so I get an error saying a.get is not a function. Sample code:
/*
filter the model to get only one food category, which is determined by the user selecting a choice that sets the property: theCategory
*/
var foodByCategory = get(this, 'model').filter(function(rec) {
return get(rec, 'category') === theCategory;
});
/*
Now, roll up all the food records to get a total
of all cost, salePrice, and weight
*/
summary = foodByCategory.reduce(function(a,b){
return {
cost: a.get('cost') + b.get('cost'),
salePrice: a.get('salePrice') + b.get('salePrice'),
weight: a.get('weight') + b.get('weight')
};
});
Am I going about this all wrong? Is there a better way to roll up multiple records from the model into one record, or do I just need to either flatten out the model records into plain objects first, or alternatively, return an Ember object in the reduce()?
Edit: doing return Ember.Object.create({...}) does work, but I still would like some opinion on whether this is the best way to achieve the goal, or if Ember provides functions that will do this, and if so, if they're any better than reduce.
Assuming this.get('model') returns an Ember.Enumerable, you can use filterBy instead of filter:
var foodByCategory = get(this, 'model').filterBy('category', theCategory);
As for your reduce, I don't know of any Ember built-ins that would improve it. The best I can think of is using multiple, separate mapBy and reduce calls:
summary = {
cost: foodByCategory.mapBy('cost').reduce(...),
salePrice: foodByCategory.mapBy('salePrice').reduce(...),
...
};
But that's probably less performant. I wouldn't worry too much about using Ember built-ins to do standard data manipulation... most Ember projects I know of still use a utility library (like Lodash) alongside Ember itself, which usually ends being more effective when writing this sort of data transformation.
Related
This question already has answers here:
Ember.js 2.3 implement #each.property observer on a HasMany relationship?
(2 answers)
Closed 6 years ago.
I have to access a property that is two level's deep on my controller, but the [] only can access one level deep via the emberjs guide.
model(params) {
params.paramMapping = {
page: "page",
perPage: "per_page",
total_pages: "pages"
};
return this.findPaged('contractf', params);
},
setupController(controller, model) {
model.forEach(function(item) {
item.set('sale_price', item.get('dealers_sched_id.sale_price'));
});
controller.set('content', model);
},
Above, I have my model which is basically fetching all records in a pagination format in the contractf model. Then I set up my controller and loop through all those models and bind a sale_price property that goes into it's relationship to get the sale_price in the correct model relation.
Now in my template, I have this:
new_suggested_price: Ember.computed('selectedItems', 'selectedItems.[].sale_price', function() {
var ret = 0;
this.get('selectedItems').filterBy('car_used', 'N').forEach(function(contract){
var salePrice = contract.get('sale_price');
if (salePrice) {
ret += (salePrice*100);
}
});
return ret/100; // We *100/100, so we avoid a floating point calc error.
}),
Basically just gives me a number that is easily format-able. As you can see it depends on the selectedItems (which is basically the model, but filters by a property). So I have to go into each model item and find that sale_price I property I set and if it changes, this computed property will update. Reading Ember's guide, I couldn't do selectedItems.[].dealers_sched_id.sale_price because it only goes one level deep.
I thought setting a property on setupController would fix that issue, but it doesn't seem to because I'm still getting NaN as the sale_price value.
Now if I set a setTimeout function for 500 ms, it populates fine.. How do I get it defined on page load?
Thank you for any help.
Why does this problem exist
Ember API indeed allows you to use only one level of #each/[] in your computed property dependencies.
This limitation is likely artificial as using two or more #each levels is a huge performance impact on internal observer maintenance.
Thus, you have to avoid more than one #each/[] in your CP dependency chains.
If you can't have a cake with N candles, have N cakes with one candle each
But sometimes your business dictates you to have to or more levels.
Fear not! This can be achieved with a chain of computed properties, where each property has only one #each level.
Say, you have Foo, Bar and Baz models and want to depend on
foos.#each.bars.#each.bazes.#each.name
Here's a chain of computed properties you need to create:
barsArrays: computed('foos.#each.bars') -- map foos by bars. You'll have an array of arrays of bars.
bars: computed('barsArrays.[]') -- flatten it to receive an array of bars.
bazesArrays: computed('bars.#each.bazes') -- map bars by bazes.
bazes: computed('bazesArrays.[]') -- flatten bazesArrays.
bazesNames: computed('bazes.#each.name') -- map bazes by name.
How to make the chain shorter
Note that you can make this chain shorter (but not necessarily more performant) by relying on the fact that bar.bazes is a relationship array that is never replaced with a different array (only its content changes, but the array object stays the same).
bazesArrays: computed('foos.#each.bars')-- map foos by bars, flatten, then map by bazes. You'll have an array of arrays of bazes.
bazes: computed('bazesArrays.[]') -- flatten bazesArrays.
bazesNames: computed('bazes.#each.name') -- map bazes by name.
Here's a working demo: http://emberjs.jsbin.com/velayu/4/edit?html,js,output
model.forEach(function(item) {
item.set('sale_price', item.get('dealers_sched_id.sale_price'));
});
in this line here, you are essentially trying to create an alias, which is the right idea, becuase it is a "model-level" concern.. which you are tying to do at the controller level.
You could create a computed.alias('dealers_sched_id.sale_price') on your model definition, and avoid all the extra layers of computing properties.
Edit: in your case we are dealing with an asynchronous relationship
This means you need to be aware that the value will not always be available to you when the relationship promise is still resolving itself. the reason you are getting a NaN is that the belongsTo is still technically "loading"... so any synchronous function you try to perform is likely to fail on you.
When you look at both answers provided to you, involving computed properties, understand that both approaches will work... they will compute and recompute when the promises resolve.
somewhere in your code, you might be trying to access the eventual value, before it is actually ready for you... maybe an alert() console.log() or one of Ember's synchronous life-cycle hooks (hint: the setupController)?
Another approach, could be to use your route's model() hook or afterModel() to ask for the dealers_sched_id object before resolving the route... it may be less ideal, but it will ensure you have all the data you need before trying to use the values.
maybe it's just a brain bug on my side, but im really confused for many days now.
I have a search formula with many configurable changing parameters like this:
ID, name, lastname, date1,
There is no hierarchical order of these parameters, the user can configure them in and out of the form.
The ember way for queryparameter is : { ID: ..., lastname: ..., date1: ... }, but what can i do, if i don't know what parameters can face up? There are for different modules in our application from 10 to 40 Parameters configurable....
I need help to find the "best-practice" to solve this problem.
I would be delighted, if someone could give me an impact how to solve this!
Best regards, Jan
If I understood you correctly, you want to find a reusable solution to not make a huge list of ifs for different query params for #store.find.
To make it reusable, you can stay with single find as follows:
this.store.find('myModel', queryHash)
And build the queryHash before the call. You can for example have a set of checkboxes and a computed property that is based on all the checkboxes values. This computed property will build your hash, e.g.:
queryHash: Ember.computed "lastName", "date1", function() {
query = {}
if(this.get("lastName")) { query.lastName = this.get("lastName"); }
if(this.get("date1")) { query.date1 = this.get("date1"); }
query
});
The disadvantage is that you need to know all the possible options that are available (but does not need to be checked) in current route for the user (e.g. via some kind of inputs or form).
On the other hand, if you cannot say what are names (or how many of them there are), you can at least hold all user-provided data in some kind of an array and enumarate through it, adding all the proper hash keys with values to the query object.
I'm looking for an idiomatic way to write an "active record" like method to filter out inactive models
Using the latest ember-data I usually pull in all employee records like so
var employees = this.store.all('employee');
Keep in mind that I'm doing this "filter" 100% client side because I have everything in memory. I use all the employees for a few parts of the app and need the "all" like behavior in these situations. But as I'm also allowing "active/ inactive" status I'd like to filter them down client side for a few features.
I wanted a nice way to query this using a simple filter and I thought it would be active-record like to extend the model and add this but I wanted some guidance first (ie- should I be doing this when the store is not injected into the model directly, and if yes how should I go about injecting this?)
If I shouldn't do this, what is the best way to get all employees and filter down to get only the active ones? (ie- can I just invoke store.all and apply the filter or do I need to work with this data differently) ?
(here is a sample of the filter I'm doing manually now)
return content.filter(function(apt) {
return apt.get('employee').get('active') === true;
});
Ember Data's store has a filter method that has the same functionality as the all filter, aka live record array.
store.filter('employee', function(employee){
return employee.get('active');
});
Let's say I do the following request:
App.Phones.find({'country' : 'DE'});
My backend replies with some telephone numbers. Now I do:
App.Phones.find({'country' : 'ES'});
Now I get other telephone numbers. But:
App.Phones.all();
Has accumulated the "old" numbers and the new ones. Is it possible to clean the store between calls to find? How?
I have tried with App.Phones.clean();, without success (has no method 'clean')
EDIT
This is quite strange but: calling record.destroy(); (as suggested by intuitivepixel) on an object does not remove it from the store, it just marks it as destroyed=true. That means, the drop-down is still showing that option. Actually, walking the records (all()) shows that the records are still there after being destroyed. Maybe Ember will remove them from the store eventually, but that does not help me at all: since my select is bound to all(), I need them to be removed right now.
Even worse: since the object is there, but destroyed, the select shows it, but it does not allow to select it!
I can think of a very ugly hack where I create an Ember.A with filtered records (removing the destroyed records), like this:
Destroy all records (the old ones)
Request new records from the backend
When the records are received (.then), walk the records in the store (.all()), that is, the destroyed and the new ones.
Add the records in the array which are not destroyed
Bind the select to this filtered array.
This looks extremely ugly, and I am really surprised that Ember is not able to just fully and reliably clean the store for a certain record type.
I guess you could do the following to clean the in the store saved Phones records:
App.Phones.find({}); //this will invalidate your cache
But obviously this will make a new request retrieving all the phone numbers.
Depending on what you want to achieve, you could use find() in the application route, then either all() or a filter() in other routes to retrieve just DE, ES etc. In other words there is no such method available to do something like: App.Phones.clean().
Update
Another way (manually) I can think of to remove the records of one type from the cache could be to delete them one by one beetwen your find() operations, for example create a simple utility function which contains the below code, and call it beetwen your calls to find():
App.Phones.all().forEach(function(record) {
record.destroy();
});
Hope it helps.
So, this is the (unsatisfying, ugly, hacky, non-intuitive) code that I have come up with. It is doing the job, but I have a very bad feeling about this:
getNewPhones : function (countryCode, subtype, city) {
// Mark old records as destroyed
App.Availablephone.all().forEach(function(phone, index) {
phone.destroy();
console.log('Destroyed phone %o', phone);
});
// Not possible to set the availablePhones to .all(), because old records are still there
//App.Availablephone.find({country : countryCode, subtype : subtype, city : city});
//this.set('availablePhones', App.Availablephone.all());
// So, hack is in order:
// 1. request new data
// 2. filter all records to just get the newly received ones (!!!)
var _this = this;
App.Availablephone.find({country : countryCode, subtype : subtype, city : city}).then(function(recordArray) {
var availablePhones = Ember.A();
App.Availablephone.all().forEach(function(phone, index) {
if(!phone.isDestroyed) {
console.log('Adding phone=%o', phone);
availablePhones.push(phone);
}
});
_this.set('availablePhones', availablePhones);
});
},
Comments / critiques / improvements suggestions are very much welcome!
I have a RecordArray that has come back from a App.ModelName.find().
I'd like to do some things with it, like:
paginating through the set of records within
adding records from another findQuery into the array
I may be confused, but it seems like it's difficult (or at least undocumented) on how to work with the records that come back from find()/findAll()/findQuery() other than looping over the set and displaying them as normal.
This is further complicated by the array that gets returned from all(), which seems to be closer to an identity map, maybe.
None of this may be possible, but if it isn't I can open issues and start to work on that myself.
The RecordArrays returned by Ember Data aren't really meant for modification. In particular, Model.find() (sans-argument) and Model.all() return live arrays that keep updating as new matching records are available.
If you want to manipulate an array of models, you're best off using Model.find({})(the argument makes it use findQuery()) and observing the isLoaded property. Something like this:
query: null,
init: function() {
// should really do this in the route
this.set('query', Model.find({}));
},
content: function() {
var query = this.get('query');
return query && query.get('isLoaded') ? query.toArray() : [];
}.property('query.isLoaded')
Now content returns a plain old array and you can have your way with it (though you still need to wait for the records to load before you can start modifying the array).
If the issue is that you want a query to keep updating, then consider using Model.filter(), which returns a live array like find(), but accepts a matching function. Note that confusingly, but quite intentionally, none of find(), all(), and filter() have an isLoaded property.
As for pagination, you could try a simple mixin approach, or a more elaborate rails-based solution.