Todo sobre la manzana y más

Model associations

The data package in Sencha Touch and ExtJS is awesome. Models let you easily and robustly configure your data structures and easily use them in all sorts of components. One of the model features that have a lot of potential but are used and understood relatively poorly are model associations. One of the most interesting uses of associations is that they allow you to use parent data when using the model in components. In practice this can be quite hard to accomplish however when all stores load their own data. In this blogpost I will show how to use parent (belongsTo) relations to automatically fetch and use parent data.

Model associations

First some basics about model associations. Associations define relationships between models. In Sencha Touch and ExtJS there are 3 types of associations:

hasOne – The model instance has one and only one sibling (of that type)

hasMany – The model instance has multiple children

belongsTo – The model instance has one and only one parent (of that type)

In Sencha Touch, models that implement the above illustration would be implemented as follows:

What we want

Now that we setup our associations, let’s put them to use.

For an imaginary car tyre dealer we are creating a mobile app. The client wants to include a list of all the tyres currently attached to it’s clients cars. The list should include the Tyre brand and position on the car, the car brand of the car it’s attached to and the client name.

We create 3 stores, a ‘Owners’ store containing the clients, a ‘Cars’ store that contains all cars and a ‘Tyres’ store that contains all tyres. The three stores operate on their respective models ‘Owner’, ‘Car’ and ‘Tyre’.

Now this is where it gets tricky. The ‘Tyres’ list operates on a store that contains ‘Tyre’ records. The ‘car brand’ and ‘customer name’ are part of other models however. Because we setup the ‘belongsTo’ relations on the Tyre and Car model we would think we can use those associations to gather the car brand name and client name. Unfortunately, that is not the case.

The Problem

The reason for the list not being able to display associated data has to do with the way stores load their data from a backend server.

Simply put, the stores load data from the backend throught their configured proxy (which have not been drawn) and instantiate records (model instances) from that data. The load process is in most cases asynchronous. This means that the Tyre and Car records will be instantiated without knowing about their related (instantiated) counterparts. So when we fetch a Tyre record from the store, it has no reference to a parent Car record we would like to use in the list component. The only way to accomplish that is to load the entire data structure of owners, including car and tyre children in 1 go using a hyrarchical data structure. In practice this way of loading data is cumbersome and mostly unsupported by typical standardized backend API’s .

The solution

Of course, in Sencha Touch and ExtJS there is always one or more ways to do it. After brainstorming a bit (Aaron Smith, thanks for your time) we figured out a solution. Our models need to be provided with a way to initiate the relations with other models when they are needed in components. Besides the definitions of the associations themselves on the model we need to instruct the model where the related objects can be found.

Most components get their data from models through the ‘getData’ method on the Model. That method already consumes an argument telling it to incorporate associated data in the results. The only problem is that it can’t find the related records. Therefore we chose to inject some custom code in the getData method that searches the associations. The easiest way for us was to let all our models inherit from a BaseModel class that includes those additions. By providing a couple of additions to the association we can completely transparently let the models take care of everything.

BaseModel

First we create our BaseModel that includes a couple of methods:

linkAssociations – Iterates over all parent (belongsTo) and straight (hasOne) associations. If the association includes our special foreignStore config we use the StoreManager to find the related record. The link to the related record is stored in a way in which Sencha Touch automatically picks it up.

linkChildAssociations – Used to fetch the direct children (hasMany) of the record and again stored in a way the default model handlers pick it up. When using this recursive, things can quickly spin out of control, be careful with this one!

getFlattenedData – Turns a hierarchical structure, fetched with getData, into a flat hash. The keys contain the relations in dot format. This way, the relations can be used for instance in a form panel.

Models

We need a couple of minor changes to our model associations specified above:

primaryKey – the field in the parent that identifies it.

foreignKey – the key that identifies the parent in the child. In a belongsTo or hasOne relation, this is part of the model itself, in a hasMany relation this is a field of the child objects that refer to my Id.

List

Now that we structured our data let’s see how difficult it is to load related data in a List component. This assumes that the various stores have been configured using their respective model and set to autoLoad.

That’s it! The List operates on the Tyres store but is able to use the defined associations to fetch and display parent data. Of course, the developer has to make sure that the parent relations are always there, otherwise the List’s XTemplate will croak. This has to do with the fact that at the moment of writing Sencha Touch triggers an error instead of a warning on these cases. Like everything there is also a way around this:

Conclusion

When understanding how associations work in Sencha Touch and ExtJS it’s pretty easy to make them work for you. The result is an enormous amount of freedom in the display and usage of associated data without having to resort to all kinds of nasty hacks. The above solution is one example of such a use with the associations neatly abstracted away in the data layer. Using the BaseModel’s getFlattenedData also allows one to use associated data in forms. This I will leave to the reader as a practical exercise. Good luck and let me know how it works for you.