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!
Related
I'm new to ember and exploring its capabilities by building a small module. I came across a scenario where I need to update the ember model content synchronously. The ember-data model contains an array of objects as contents.
I was hoping to perform a few tasks as follows
Perform an array content reorder - for the sake of simplicity we
can assume swapping the first and last item.
Append a record
without a network call
Delete a record without a network call.
Doing these should automatically sync the data bindings/computed props
My data model after a peekAll call contains 10 records(shown below) on which I need to perform the above operations.
My model is as shown below
export default Model.extend({
testId: attr('number'),
name: attr('string')
});
What is the right approach to update the content record? Could someone please suggest how to proceed?
This looks to me like the results of running something like let arr = await store.findAll('test-model'), is that correct? This is probably a PromiseArray and you can access the data as a Javascript Array by calling arr.slice() on it. This will let you do normal array operations, though performing a content re-order doesn't really make much sense in this scenario. I assume you were using it as an example.
For adding and removing records without a network call you can do that by going back to the store and this is what is covered in the docs, you don't need to act on this Object you're looking at.
Adding a new record:
let testModel = store.createRecord('test-model', {
name: 'Lorem ipsum'
});
testModel.save(); //until you do this no network data will be sent
Removing a record:
let testModel = store.peekRecord('testModel', 1); //to get a record with ID of 1
testModel.deleteRecord();
testModel.save(); //until you run save no network is sent
Once you've taken action like this on the store the data structure you posted above may be updated to contain the new data depending on how you accessed it originally. You can also re-fetch data from the store which will now know about your adding a deleting of models (even though you haven't saved it back to the server yet)
If you haven't saved yet and you re-do a peekRecord you'll need to filter out any deleted records from the results.
let undeletedModels = this.store.peekAll('test-model').filter(m => !m.isDeleted);
Let's say I have a list of records that are supposed to have state selected in some current context. I can see two approaches here:
Create an array of objects: records.map(record => { record: record, selected: false }), get and set like `recordObj.set('selected', true). Render this array.
Set property explicitly on record objects: record.set('selected', true). Render RecordArray.
The second one looks much easier than the first as you don't have to manage additional array and objects (this becomes a real hassle).
The problem is that if you set anything on a record object, even a field that is not present in the model, it will still persist through the application (I guess until the model is reloaded and new record object is created?).
How to prevent that and ensure that temporary properties will be clean on every route change? Or how to improve the first approach? Is there anything I'm missing?
In route file
actions: {
willTransition() {
let records = this.get('controller.records');
records.setEach('selected', false);
}
}
This will make sure every time you leave route you are safe.
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.
I'm reviewing some code and I came across a line that does the following:
Person.find_by(name: "Tom").id
The above code gets the FIRST record with a name of "Tom", then builds a model, then gets the id of that model. Since we only want the id, the process of retreiving all data in the model and initializing the model is unneeded. What's the best way to optimize this using active record queries?
I'd like to avoid a raw sql solution. So far I have been able to come up with:
Person.where(name: "Tom").pluck(:id).first
This is faster in some situations since pluck doesn't build the actual model object and doesn't load all the data. However, pluck is going to build an array of records with name "Tom", whereas the original statement only ever returns a single object or nil - so this technique could potentially be worse depending on the where statement. I'd like to avoid the array creation and potential for having a very long list of ids returned from the server. I could add a limit(1) in the chain,
Person.where(name: "Tom").limit(1).pluck(:id).first
but is seems like I'm making this more complicated than it should be.
With Rails 6 you can use the new pick method:
Person.where(name: 'Tom').pick(:id)
This is a little verbose, but you can use select_value from the ActiveRecord connection like this:
Person.connection.select_value(Person.select(:id).where(name: 'Tom').limit(1))
This might work depending on what you're looking for.
Person.where(name: "Tom").minimum(:id)
Person.where(name: "Tom").maximum(:id)
These will sort by id value while the Person.where(name: "Tom").first.id will sort off of your default sort. Which could be id, created_at, or primary_key.
eitherway test and see if it works for you
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.