Check if an item is already in the ember-data store - ember.js

I am making a lot of async calls and using loadMany to preload the ember data store like this:
if(data.feed.activities.length > 0){
App.store.loadMany(App.Activity, data.feed.activities);
}
Some of my bindings are screwing up if I am readding the same item more than once which is a possibility.
Is there a way of not reloading the item if it is already in the store? I don't want to have to iterate over each item and check if that is possible.

This is from the load() documentation in store.js
"Load a new data hash into the store for a given id and type
combination. If data for that record had been loaded previously, the
new information overwrites the old. If the record you are loading data
for has outstanding changes that have not yet been saved, an exception
will be thrown."
As you can see, the new information overwrites the old, so it should be ok to reload the same data. Maybe you have another issue. Have you configured your id correctly?

Related

How (and when) do I use iCloud's encodeSystemFields method on CKRecord?

encodeSystemFields is supposed to be used when I keep records locally, in a database.
Once I export that data, must I do anything special when de-serializing it?
What scenarios should I act upon information in that data?
As a variation (and if not covered in the previous question), what does this information help me guard against? (data corruption I assume)
encodeSystemFields is useful to avoid having to fetch a CKRecord from CloudKit again to update it (barring record conflicts).
The idea is:
When you are storing the data for a record retrieved from CloudKit (for example, retrieved via CKFetchRecordZoneChangesOperation to sync record changes to a local store):
1.) Archive the CKRecord to NSData:
let record = ...
// archive CKRecord to NSData
let archivedData = NSMutableData()
let archiver = NSKeyedArchiver(forWritingWithMutableData: archivedData)
archiver.requiresSecureCoding = true
record.encodeSystemFieldsWithCoder(with: archiver)
archiver.finishEncoding()
2.) Store the archivedData locally (for example, in your database) associated with your local record.
When you want to save changes made to your local record back to CloudKit:
1.) Unarchive the CKRecord from the NSData you stored:
let archivedData = ... // TODO: retrieved from your local store
// unarchive CKRecord from NSData
let unarchiver = NSKeyedUnarchiver(forReadingWithData: archivedData)
unarchiver.requiresSecureCoding = true
let record = CKRecord(coder: unarchiver)
2.) Use that unarchived record as the base for your changes. (i.e. set the changed values on it)
record["City"] = "newCity"
3.) Save the record(s) to CloudKit, via CKModifyRecordsOperation.
Why?
From Apple:
Storing Records Locally
If you store records in a local database, use the encodeSystemFields(with:) method to encode and store the record’s metadata. The metadata contains the record ID and change tag which is needed later to sync records in a local database with those stored by CloudKit.
When you save changes to a CKRecord in CloudKit, you need to save the changes to the server's record.
You can't just create a new CKRecord with the same recordID, set the values on it, and save it. If you do, you'll receive a "Server Record Changed" error - which, in this case, is because the existing server record contains metadata that your local record (created from scratch) is missing.
So you have two options to solve this:
Request the CKRecord from CloudKit (using the recordID), make changes to that CKRecord, then save it back to CloudKit.
Use encodeSystemFields, and store the metadata locally, unarchiving it to create a "base" CKRecord that has all the appropriate metadata for saving changes to said CKRecord back to CloudKit.
#2 saves you network round-trips*.
*Assuming another device hasn't modified the record in the meantime - which is also what this data helps you guard against. If another device modifies the record between the time you last retrieved it and the time you try to save it, CloudKit will (by default) reject your record save attempt with "Server Record Changed". This is your clue to perform conflict resolution in the way that is appropriate for your app and data model. (Often, by fetching the new server record from CloudKit and re-applying appropriate value changes to that CKRecord before attempting the save again.)
NOTE: Any time you save/retrieve an updated CKRecord to/from CloudKit, you must remember to update your locally-stored archived CKRecord.
As of iOS 15 / Swift 5.5 this extension might be helpful:
public extension CKRecord {
var systemFieldsData: Data {
let archiver = NSKeyedArchiver(requiringSecureCoding: true)
encodeSystemFields(with: archiver)
archiver.finishEncoding()
return archiver.encodedData
}
convenience init?(systemFieldsData: Data) {
guard let una = try? NSKeyedUnarchiver(forReadingFrom: systemFieldsData) else {
return nil
}
self.init(coder: una)
}
}

peekRecord() is not working but peekAll() is working

My backend always responds with all available data and it took a considerably amount of time. So I'm reloading store periodically and I plan to use peekAll() and peekRecord().
My code is:
model: function() {
return Ember.RSVP.hash({
'clusters': this.store.peekAll('cluster'),
'single': this.store.peekRecord('cluster', 'cluster::My')
});
When code is executed, at first I can see that both of these items do not contain content. After few seconds data are loaded to store and I can see content 'clusters' on template as expected. But 'single' is still completely without content ({{model.single}} does not return nothing in template). But when I have a button with action:
alert(this.store.peekRecord('cluster', 'cluster::My'));
I can see that the record was found. Records are also available via Ember Inspector. What am I doing wrong that only peekAll() works in model for me.
The semantics of both methods are:
store.peekAll returns a live array that is updated as the store is updated.
store.peekRecord returns the corresponding object in the current cache, or null, and it does not update.
So the behaviour you're observing is the expected one. If you want to use the peek methods, my advise is to make sure that the initial request has finished loading before fetching any data from the store.

ember.js ember-data initiale state of new created model record

Ember.data 2.2.0
The state of just created object is dirty (get('hasDirtyAttributes' return true), cause the new ID is set every time.
I need to know when the record is created, not saved and the "user" not modified it. So, I can't use the dirty state cause the store change it.
If I modified the internal state just after create the record, I will broke somthing inner the record?
My real need, is when I create the record I need a initiale state and I want detect when a user change it. So, I saw in the record source code, it's use the "setProperties" methode to set the ID and optionaly the data passed to the createRecord method.
So, I want override the createRecord store metod to set the dirty state to false after created it. And the principal, how I can do that?
I saw the doc of DS.RootState Class and it just talk about that states : (deleted, saved, uncommitted, inFlight, empty, loaded, created, updated, loading) and the method translateTo but nothing to change the dirty state.
In the doc say :
Flags are Boolean values that can be used to introspect a record's
current state in a more user-friendly way than examining its state
path
So... I set currentState.parentState.isDirty to false and that it
Edit: After set the flag directly, the record doesn't change state, stay in no dorty. So, how what I can do?
The only solution I found is test if the object is new. If is it, re-create a new one.
If not, I call the rollbackAttributes().
And use the that method on model for detect a change :
isChanged : function() {
return this._internalModel.hasChangedAttributes();
}

Ember Data - ArrayController's content does not reload, after adding new items by store.pushPayload method

I have a list of items handling by Ember.ArrayController. I'm doing some PATCH action on records, which updates existing items and adding a new ones, if it is needed from the context. All changes I'm sending back from the server and I'm pushing it to the store by using Store.pushPayload() method.
I do something like that:
All changes in existing records are automatically updated - so observers of particular items are run.
Unfortunately when I have a new items in payload - they do not appear on the list - observes of ArrayController.content are not called.
I also tried to manually notify ArrayController about the changes by doing:
_this.store.pushPayload(response);
var tasksController = _this.get('controllers.tasks');
tasksController.contentDidChangedManually();
And in controller:
contentDidChangedManually: function() {
this.set('contentChangedManually', new Date().getTime());
},
filteredContent: function() { // my content filters... }.property('arrangedContent', 'contentChangedManually')
But it does not work, because contentDidChangedManually() is run before pushing a payload is done. Unfortunately, Store.pushPayload() does not return a promise, so I can't run it when new records are ready.
Does anyone have a solution for this? Thanks in advance.
It sounds as if you are using find to load your model data, you likely want to use DS.Store.filter instead as that returns a live updating record array:
http://emberjs.com/api/data/classes/DS.Store.html#method_filter

Ember.js Data how to clear datastore

I am experiementing with Ember.js and have setup a small app where users can login and logout. When the user logs out I want to clear all of the currently cached records in the Data Store.
Is there a way to do this or would I have to force the browser to reload the page?
I know this question is from 2013. But since Ember Data 1.0.0-beta.17 (May 10, 2015) there's a straightforward way of clearing the datastore:
store.unloadAll()
(More here: http://emberigniter.com/clear-ember-data-store/)
It looks like, as of today, there is still no generic way of fully forcing a store cleanup. The simplest workaround seems to loop through all your types (person, ...) and do:
store.unloadAll('person');
As seen here
Clearing the data store is not yet supported in Ember-Data. There is an open issue concerning this on the Github tracker.
A cleaner & generic approach. Just extend or reopen store & add a clear method like this.
DS.Store.extend({
clear: function() {
for(var key in this.typeMaps)
{
this.unloadAll(this.typeMaps[key].type);
}
}
});
There is:
App.reset();
But that does more than clear out the data store and we've occasionally seen errors where store.pushPayload tries to push data onto an object marked destroyed from calling App.reset();.
Been we've been using:
store.init();
Which just creates a new empty store and works great but unfortunately is a private method.
This can now be done with store.destroy(). It unloads all records, but it also available for immediate use in reloading new records. I have confirmed this as of 1.0.0-beta.15. It doesn't appear to be in the documentation, but it's been working for me.
The alternative would be iterating the store's typeMaps and running store.unloadAll(typeMap.typeName), but I'm not sure it's entirely necessary.
Deleting record by record in model. deleteOrgs of this jsBin:
deleteOrgs: function(){
var len;
while(len = this.get('model.length')) {
// must delete the last object first because
// this.get('model.length') is a live array
this.get('model').objectAt(len-1).deleteRecord();
}
this.get('store').commit();
}
( As of August 2013, there is currently a problem with lingering deleted data. )