PDA

View Full Version : [FIXED][3.x] Ext.data.DataWriter realize/update



dj
4 May 2009, 5:44 PM
I get these errors
TypeError: this.ef is undefined message=this.ef is undefined
when creating records in a store that was never loaded before.

realize and update use extractValues to extract the updated/realized data from the response. That only works for JsonReaders and only when JsonReader.readRecords was called once (that method creates the "ef" extractors) The generation of the "ef" extractors should probably be extracted from readRecords so that update and realize can call that method if needed. Also either update/realize should move to JsonReader or should be made compatible with XmlReader also.


BTW: shouldn't realize and update also use a "root" for getting the updated record? Right now one has to send something like

{"data": [{"id": 1, "created_at": "2009-05-05T00:44:30Z", "title": "Title"},{"id": 2, "created_at": "2009-05-05T00:44:30Z", "title": "Title"}], "tid": 57, "type": "rpc", "status": true}
I.e. the updated record fields are directly in the "data" array. I see two problems with this:
1) You cannot pass along other record-specific data that is not part of the record definition. E.g. some debug data like # of SQL-queries or so.
2) What happens when a server side validation fails for one update but not the other. What should the server send back in that case?

{"data": [{"id": 1, "created_at": "2009-05-05T00:44:30Z", "title": "Title"},{"status":false, "errors":{"title":"Title must be unique"}}], "tid": 57, "type": "rpc", "status": true}?

mystix
4 May 2009, 6:22 PM
I get these errors
TypeError: this.ef is undefined message=this.ef is undefined
when creating records in a store that was never loaded before.


i think you need to mention the IE version (and preferably OS too)?
i googled "TypeError" and came up with hits for both IE7 / 8 on Vista.

dj
4 May 2009, 6:37 PM
Actually it's Firefox on Mac OS X. Nevertheless the browser is not an issue here. This exception (or a similar one) will be thrown in all browsers.

I have forgotten to explicitly mentioning it: This exception gets thrown in data/JsonReader.js in method extractValues (line 226 in rev 3841). Apparently this.ef is undefined because readRecords was never called.

christocracy
11 May 2009, 1:05 PM
I get these errors TypeError: this.ef is undefined message=this.ef is undefined when creating records in a store that was never loaded before.

I can't duplicate this error. I removed my store.load and created a record into an empty store without problem on Ubuntu FF3.0.10.


realize and update use extractValues to extract the updated/realized data from the response. That only works for JsonReaders

Yes, indeed. I'm aware that XmlReader needs to have implemented extractValues.



1) You cannot pass along other record-specific data that is not part of the record definition. E.g. some debug data like # of SQL-queries or so.

2) What happens when a server side validation fails for one update but not the other. What should the server send back in that case?


Originally, the Writer package was designed around Ext.Direct then expanded to work with all proxies. With Ext.Direct, it was a snap to wrap each record-action into a distinct transaction, since Ext.Direct queues them all into one Ajax request. On the server, one could raise an exception for each record and return error-info from failed validations and whatnot. It would be quite easy to implement a Direct-specific save method in DirectStore.

dj
12 May 2009, 2:42 PM
Here is a test case. Just drop that in the examples/writer directory and try to create a new user. I mocked the AJAX-call / the server side away so this test case also works locally.
(Since there are some console.* calls you need to have Firebug or comment out those calls)



This is the hotfix I have in place right now:


Ext.override(Ext.data.Store, {
onCreateRecords: function(success, rs, data){
if (success === true) {
try {
var bogusData = {};
bogusData[this.reader.meta.root] = [];
this.reader.readRecords(bogusData);
this.reader.realize(rs, data);
}
catch (e) {
this.handleException(e);
if (Ext.isArray(rs)) {
// Recurse to run back into the try {}. DataReader#realize splices-off the rs until empty.
this.onCreateRecords(success, rs, data);
}
}
}
}
});

christocracy
12 May 2009, 3:22 PM
What's the point of trying to realize a created record with no data?

The code I'm running correctly raises an exception telling me I have invalid data. You can't realize a record without at least providing the database pk (ie: reader.meta.idProperty).



try {
var bogusData = {};
bogusData[this.reader.meta.root] = [];
this.reader.readRecords(bogusData);
this.reader.realize(rs, data);
}



DataReader#realize


realize: function(rs, data){
if (Ext.isArray(rs)) {
for (var i = rs.length - 1; i >= 0; i--) {
// recurse
if (Ext.isArray(data)) {
this.realize(rs.splice(i,1).shift(), data.splice(i,1).shift());
}
else {
// weird...rs is an array but data isn't?? recurse but just send in the whole invalid data object.
// the else clause below will detect !this.isData and throw exception.
this.realize(rs.splice(i,1).shift(), data);
}
}
}
else {
// If rs is NOT an array but data IS, see if data contains just 1 record. If so extract it and carry on.
if (Ext.isArray(data) && data.length == 1) {
data = data.shift();
}
if (!this.isData(data)) {
// TODO: create custom Exception class to return record in thrown exception. Allow exception-handler the choice
// to commit or not rather than blindly rs.commit() here.
rs.commit();
throw new Ext.data.DataReader.Error('realize', 'DataReader.js', rs);
}
var values = this.extractValues(data, rs.fields.items, rs.fields.items.length);
rs.phantom = false; // <-- That's what it's all about
rs._phid = rs.id; // <-- save oldId so we can remap in Store#onCreateRecords
rs.id = data[this.meta.idProperty];
rs.data = values;
rs.commit();
}
},


DataReader#isData


/**
* Returns true if the supplied data-hash <b>looks</b> and quacks like data. Checks to see if it has a key
* corresponding to idProperty defined in your DataReader config containing non-empty pk.
* @param {Object} data
* @return {Boolean}
*/
isData : function(data) {
return (data && typeof(data) == 'object' && !Ext.isEmpty(data[this.meta.idProperty])) ? true : false
}

dj
12 May 2009, 3:29 PM
In the hotfix I read zero records before realizing the data that the server provided. Only to ensure that this.reader.ef is valid. (It gets created in readRecords if it is not already there)

Can you reproduce the exception TypeError: this.ef is undefined with the attached test case?



Ext.override(Ext.data.Store, {
onCreateRecords: function(success, rs, data){
if (success === true) {
try {
var bogusData = {};
bogusData[this.reader.meta.root] = [];
this.reader.readRecords(bogusData);
this.reader.realize(rs, data);
}
catch (e) {
this.handleException(e);
if (Ext.isArray(rs)) {
// Recurse to run back into the try {}. DataReader#realize splices-off the rs until empty.
this.onCreateRecords(success, rs, data);
}
}
}
}
});

christocracy
12 May 2009, 3:34 PM
2) What happens when a server side validation fails for one update but not the other. What should the server send back in that case?

Use Ext.Direct. I just overrode Store#save in Ext.data.DirectStore#save which will wrap each record-action in a distinct Ext.Direct transaction. simply throw an exception for each record that goes bad.

http://extjs.com/forum/showthread.php?t=67520

Gotta' hit the hay now. 3 more days working in a bank then I'm back to Canada.

christocracy
13 May 2009, 12:41 PM
Ok, dj. I've confirmed it was a bug. Good one.

In my sandbox app, I had been loading 2 stores. I had only disabled 1 of the store.load(). The other store.load() was of course executing JsonReader#readRecords, causing this.ef to be created before I tried inserting into the other empty store.

Thanks. Committed to SVN.

The fix was simple. I simply placed the building of extraction-functions into a method.

JsonReader#readRecords


.
.
.
//Generate extraction functions for the totalProperty, the root, the id, and for each field
if (!this.ef) {
this.ef = this.buildExtractors();
}
.
.
.


Then I added a check for this.ef in JsonReader#readResponse as well:
JsonReader#readResponse


readResponse : function(action, response) {
var json = response.responseText;
var o = Ext.decode(json);
if(!o) {
throw new Ext.data.JsonReader.Error('response', 'JsonReader.js');
}
if (Ext.isEmpty(o[this.meta.successProperty])) {
throw new Ext.data.JsonReader.Error('success', 'JsonReader.js', this.meta.successProperty);
}
// TODO, separate empty and undefined exceptions.
else if ((action == Ext.data.Api.CREATE || action == Ext.data.Api.UPDATE) && Ext.isEmpty(o[this.meta.root])) {
throw new Ext.data.JsonReader.Error('root', 'JsonReader.js', this.meta.root);
}
// makde sure extaction functions are defined.
if (!this.ef) {
this.ef = this.buildExtractors();
}
return o;
}