Pages

Saturday, June 16, 2012

Polymorphic JSON: Changing the model type based on the response

I am using the Party Model, and needed to create an Individual or Organization model depending on the JSON response. Had to override Ext.data.reader.Reader to do it. Ideally, Sencha would fire a "creating model" event or hook that would let us pick the model at Read time. The system looks like this:

PARTY
id
name
getText() - returns name

ORGANIZATION : PARTY
doingBusinessAs
getText() - returns doingBusinessAs

INDIVIDUAL : PARTY
middleName
firstName
birthday
getText() override - returns full name

Handles overridden fields and associations properly. You should use the name property on belongsTo associations if you want to override it.


Here is the override code:

Ext.data.reader.Reader.override({
    extractData:function(root){


        var me = this,
            records = [],
            Model   = me.model,
            length  = root.length,
            convertedValues, node, record, i;


        if (!root.length && Ext.isObject(root)) {
            root = [root];
            length = 1;
        }


        for (i = 0; i < length; i++) {
            node = root[i];

        /* OVERRIDE */
        if(me.types){
        var typeCode = node[me.typeProperty || 'type'];
        var type = me.types[typeCode];
        me.model = Ext.ClassManager.get(type); //the model reference is used in other functions
        Model = me.model;
        me.buildExtractors(true); //need to rebuild the field extractors
        }
        /* END OVERRIDE */


        record = new Model(undefined, me.getId(node), node, convertedValues = {});

            record.phantom = false;

            me.convertRecordData(convertedValues, node, record);

            records.push(record);


            if (me.implicitIncludes) {
                me.readAssociated(record, node);
            }
        }


        return records;
    }
});

//Here is how you would use it:

Ext.define('My.model.Party', {
    extend:'Ext.data.Model',


    requires:[
    'My.model.Individual',
    'My.model.Organization'
    ],


    fields:[
    'id',
    'type',
    'name',
    {
    name:'text',
    mapping:'name'
    }
    ],


    proxy:{
    type:'ajax',
    url:'parties',
    reader: {
        root:'parties',
        types:{
        'i':'My.model.Individual',
        'o':'My.model.Organization'
        }
    }
    }
});

// the json:

{
success: true,
parties:[
{ id:4, type:'i', name:'chief', firstName:'el', middleName:'_', birthday:'May 1' },
{ id:99, type:'o', name:'Sencha, Inc.', doingBusinessAs:'Sencha' }
]
}


Your Parties store would then contain an Individual object and an Organization object.

Peace.

2 comments:

  1. Great post Neil! I'm a big party model fan. Just a few questions
    1. Was there a reason you chose not to ship down the 'subclass' type in the response as a property and simply configure the reader to interrogate said property? e.g. ... reader { root:'partyies' typeProperty: 'subtype'}...

    2. Does this scheme handle associations (HasMany, HasOne, etc) at the base class level and subclass level?

    ReplyDelete
    Replies
    1. 1. Nope, there was no good reason :) Good idea, I will use that instead.

      2. It handled subclassing fields and associations properly in my tests. I tested hasMany and belongsTo. Works in parent and child models. The only issue was I had to set the name property of belognsTo associations in order for them to be override-able.

      Delete