Manipulating a RecordArray - ember.js

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.

Related

Iterate over an ember model query

this.store.findAll('game').then(function(results){
// RUN SOME OPERATION ON THEM
})
I would like to know how I can play with the results variable. I understand I can do
results.get('firstObject') // returns the first object.
I'd like to know everything else I can do with it. Is there any api documentation for the results collection?
Thanks!
From ember guides,
The below methods, will return the Promise, it will be resolved to Record or RecordArray.
store.findAll() returns a DS.PromiseArray that fulfills to a DS.RecordArray.
store.findRecord returns a promise that will be resolved with the record.
store.query() returns a DS.PromiseArray in the same way as findAll.
The below two are synchronus method, it will retrieve what is available in the store and returns record itself. it will not request the server to fetch data.
store.peekAll directly returns a DS.RecordArray.
store.peekRecord direclty returns record
It's important to note that DS.RecordArray is not a JavaScript
array, it's an object that implements Ember.Enumerable. This is
important because, for example, if you want to retrieve records by
index, the [] notation will not work--you'll have to use
objectAt(index) instead.
From Ember.Enumerable, most of the time I happened to use the following,
forEach to iterate
map to transform to new Array
filterBy findBy for filtering based on single property check
toArray converting to normal native array
findAll will return a Promise which will resolve to a RecordArray.
The RecordArray is an ArrayProxy.
http://emberjs.com/api/classes/Ember.ArrayProxy.html
This is everything you need.
If you google "ember findall" you will find docs for "Ember.js - Models: Finding Records - Guides" as well.
https://guides.emberjs.com/v2.5.0/models/finding-records/

Ember.js: Summarize model records into one record

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.

Cant retrieve local records from store using Ember Data

I am trying to retrieve records which have already been loaded into the store by using this line in a controller:
var allProducts = this.store.all('product');
However, this is returning a strange object (see screenshot). When I call length on it, the result is "undefined." I have used the Chrome Ember inspector to confirm that records have indeed been loaded into Product before the above line of code is run. I thought since store.all returns a recordarray I could iterate over it immediately unlike a promise. Where am I going wrong please?
The strange object that is returned is a record array. This is important so that Ember can set up observers for arrays that are loaded. I believe this is what is causing your confusion. See more specifics in the docs:
It's important to note that DS.RecordArray is not a JavaScript array.
It is an object that implements Ember.Enumerable. This is important
because, for example, if you want to retrieve records by index, the []
notation will not work--you'll have to use objectAt(index) instead.
You will have to look at the documention for DS.RecordArray but you should be able to iterate over it using the forEach method. See the ember array documentation for more details.
The issue was that I was trying to iterate over the recordarray using a traditional for loop. Seems that a) recordarray cannot return length and 2) one must use a forEach loop to iterate over it, which is what I had initially did but dropped because forEach does not support break or continue.
Ahh promises! :)
You should be able to do this:
var allProducts = this.store.all('product').then(function(products) {
return products;
});

Clean store in between find operations

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!

Methods for filtering within a Doctrine results Collection?

I'm very new to Doctrine, so this might seem a rather obvious question to those more experienced.
I'm writing a data import tool that has to check every row being imported contains valid data. For example, the Row has a reference to a product code, I need to check that there is a pre-existing Product object with that code. If not, flag that row as invalid.
Now I can easily do something like this for each row.
$productCode = $this->csv->getProductNumber();
$product = $doctrine->getRepository('MyBundle:Product')->findOneBy(array('code' => $productCode ));
But that seems hideously inefficient. So I thought about returning the entire Product Collection and then iterating within that.
$query = $this->getEntityManager()->createQuery('SELECT p FROM MyBundle\Entity\Product p');
$products = $query->getResult();
All well and good, but then I've got to write messy loops to search for.
Two questions:
1). I was wondering if I'm missing some utility methods such as you have in Magento Collections, where you can search within the Collection results without incurring additional database hits. For example, in Magento this will iterate the collection and filter on the code property.
$collection->getItemByColumnValue("code","FZTY444");
2). At the moment I'm using the query below which returns an "rectangular array". More efficient, but could be better.
$query = $this->getEntityManager()->createQuery('SELECT p.code FROM MyBundle\Entity\Product p');
$products = $query->getResult();
Is there a way of returning a single dimensional array without have to reiterate the resultset and transform into a flat array, so I can use in_array() on the results?
If I understand your question correctly you want to filter an array of entities returned by getResult(). I had a similar question and I think I've figured out two ways to do it.
Method 1: Arrays
Use the array_filter method on your $products variable. Yes, this amounts to a "loop" in the background, but I think this is a generally acceptable way of filtering arrays rather than writing it yourself. You need to provide a callback (anonymous function in 5.3 preferred). Here is an example
$codes = array_filter($products, function($i) {
return $i->getCode() == '1234';
});
Basically in your function, return true if you want the result returned into $codes and false otherwise (not sure if the false is necssary, or if a void return value is sufficient).
Method 2: Doctrine's ArrayCollection
In your custom repository or where ever you are returning the getResult() method, you can instead return an ArrayCollection. This is found in the Doctrine namespace Doctrine\Common\Collections\. More documenation on the interface behind this method can be found here. So in this case you would have
$query = $this->getEntityManager()->createQuery('SELECT p FROM MyBundle\Entity\Product p');
$products = new ArrayCollection($query->getResult());
You can then use the filter() method on the array collection. Use it in a very similar way to the array_filter. Except it doesn't need a first argument because you call it like this: $products->filter(function($i) { ... });
The ArrayCollection class is an iterator, so you can use it in foreach loops to your hearts content, and it shouldn't really be different from an array of your products. Unless your code explicitly uses $products[$x], then it should be plug 'n' play*.
*Note: I haven't actually tested this code or concept, but based on everything I've read it seems legit. I'll update my answer if it turns out I'm wrong.
You can use another hydration mode. $query->getResult() usually returns a result in object hydration. Take a look at $query->getScalarResult(), which should be more suitable for your needs.
More info on the Doctrine 2 website.