• If you are citizen of an European Union member nation, you may not use this service unless you are at least 16 years old.


DataStore-About DataSources

Page history last edited by martin@... 10 years, 6 months ago Saved with comment

Data sources connect your in-memory store to one or more server backends.  Unlike the rest of the DataStore framework, the data source is really not much more than an API definition.  Most of its contents are up to you to implement according to the type of backend you want to work with.


Creating Your Own Data Source


To get started working with your own data source, you just need to define one in your application.  Usually it goes into a file at the root level of your app or framework or in a directory called “data_sources”.  It’s up to you.  In this file, you just need to subclass SC.DataSource and implement the API’s described in this chapter.


MyApp.DataSource = SC.DataSource.extend({

 // code goes here...



You then need to switch your application store to use your own data source.  By default, new SproutCore applications are wired up to use a fixtures data source.  When you are ready to start talking to your own server instead, just replace the call to fixtures to the name of your data source instead.  You will find this line in core.js in your app:


  // change...

  store:  SC.Store.create().from(SC.Record.fixtures),


  // to...

  store: SC.Store.create().from(‘MyApp.DataSource’)


Note that the line above names your data source class as a string (If you were using an older version of 1.0, you may have to update your app to make this change, as sproutcore will no longer accept the actual class itself).  This is because your data source class may not be defined yet.  The first time your store actually tries to access the data source, it will look up the class name and instantiate your data source for you.


It’s really that simple to get started.  You should now be using your own data source.  Now it’s time to implement the key methods.


Data Source Methods


There are really only three primitive methods called by the store on your data source:


  • fetch() - called the first time you try to find() a query on a store or anytime you refresh the record array after that.
  • retrieveRecords() - called anytime you try to access an individual record that has not been loaded yet
  • commitRecords() - called anytime the store has changes pending that were committed back using commitRecords().


The commitRecords() method is called by the store for a variety of reasons.  Some records might need to be created, some might need to be updated, and other might be destroyed.  Depending on how your server is implemented, you may deal with all of these actions fundamentally in the same way.  If that is the case, you can implement commitRecords() and be done with it.


However, if your server creates, updates, and deletes substantially different, you can rely on the default implementation of commitRecords() instead, which will sort out the records passed by the store and instead call three other methods:  


  • createRecords() - called with a list of records that are new and need to be created on the server
  • updateRecords() - called with a list of records that should exist on the server and need to be updated
  • destroyRecords() - called with a list of records that should be deleted on the server


Four of the methods above - retrieveRecords(), createRecords(), updateRecords() and destroyRecords() - all work on multiple records.  If you have a bulk API with your server where you can send many operations at once, this might be the best place for you to override.  However, if your server API is geared towards working with individual records instead, you can use the default implementations of these methods which will call the following for each individual record, one at a time:


  • retrieveRecord() - called to retrieve a single record
  • createRecord() - called to create a single record
  • updateRecord() - called to update a single record
  • destroyRecord() - called to destroy a single record


So there you have it.  Three primitive methods that are actually called by the store on your data source.  The data source provides default implementations of several of these that devolve into seven additional methods.  Depending on how you server works, you can choose to override any or all of these methods; just make sure you deal with enough to them to make sure the three primitive methods are handled.



All of these methods, by the way, have the same return value: YES if you handled all of the records you were passed.  NO if you handled none of them.  SC.MIXED_STATE if you handled some of them.


About Store Keys


Whenever the store invokes one of the entry point methods defined above, you may expect it to pass you records or records ids or something like that.  However, remember that from an architectural standpoint, the data sources sits beneath the store.  The purpose of the data source is to work with data hashes not records.  


Therefore, instead of being passed record ids, what the store will usually send you are storeKeys.  Store keys, you might recall, are transient integers assigned to each data hash when it is first loaded into the store.  It is used to track data hashes as they move up and down nested stores; even if no associated record is ever created from it.


When you are passed a storeKey, you can use it to retrieve the status, data hash, recordType and record ID using several low-level methods defined on the store:


  • store.readDataHash(storeKey) - returns the current data hash associated with a store key, if there is any
  • store.readStatus(storeKey) - returns the current record status associated with the store key.  May be SC.Record.EMPTY.
  • SC.Store.recordTypeFor(storeKey) - returns the record type for the associated store key
  • SC.Store.idFor(storeKey) - returns the record ID for the associated store key.


Although you will use these low-level APIs to read data from the store, you should never use low-level APIs to modify data in the store.  Instead, you should use one of the store callbacks described below.  The store callbacks will ensure that the record states are properly updated as you work. 


Store Callbacks


The DataStore framework is basically a large finite state machine that keeps careful record of the current state of each record current in memory.  Whenever the store calls one of the primary entry point methods on your data source, it will put any records of queries related to the call into a BUSY state.  While in this state, the record or query results cannot be modified by the rest of the application.  This way, your data source can be sure that record data will not change underneath it until it is finished working with it.


Because records are put into a BUSY state - effectively "locking" them from the rest of the app - it is vitally important that your data source eventually invoke a callback on the store for each record or query you are passed to put the store back into an unlocked state.  The only exception to this rule is if your data source returns NO from the callback method; indicating that you do not know how to handle the record or query in question.  In this case, the store will automatically unlock any related records and continue on.


The diagram below shows an example of how the callbacks you execute can impact the flow of your application.



Note that data sources can invoke these callbacks anytime that makes sense for the data source.  Traditionally, you would invoke the callback when you receive a response from the server.  For example, if you commit a change to a record, then when the server returns with an OK you might call store.dataSourceDidComplete() to unlock the record.  


In some cases, however, you might be able to assume the server's response and invoke the callback on the store immediately.  This way you can unlock the record right away so that user can continue working on their data. 



Record-Related Callbacks


Whenever retrieveRecords(), commitRecords() or any of the related methods are called on your data source, the store puts any records to be handled by your method into a BUSY state.  To release the records, you must invoke one of the record-related data source callbacks defined on the store.  There are three of these associated callbacks:


  • store.dataSourceDidComplete(storeKey, dataHash, id) - the most common callback.  This tells the store you are finished with the storeKey in question.  You can optionally pass a dataHash and/or id as well.  Doing so will replace the current dataHash or id.  For example, you might use this callback when you have retrieved a record to load its contents into the store.
  • store.dataSourceDidError(storeKey, error) - call this when you could not complete the request because an error occurred.  You can optionally pass an error object with more info; which is not currently used but will be in a future version of SproutCore.
  • store.dataSourceDidCancel(storeKey) - call this when you could not complete the request because you cancelled the operation for some reason.  This is rarely used but for example if the app shows a cancel button you may use this to reset the data.


Loading Records


In addition to these primitive callbacks, you may also call the convenience method store.loadRecords() instead. loadRecords() allows you to pass a recordType, an array of data hashes, and optionally an array of ids.  It will look up the storeKey associated with each recordType/id pair and then set the data hash for you; calling dataSourceDidComplete() as needed along the way. 


loadRecords() is often the most convenient way to get large blocks of data into the store, especially in response to a fetch() or retrieveRecords() call.


Query-Related Callbacks


Like records, queries that are passed through the fetch() method also have an associated status property (accessed through the "status" property on the record array returned from store.find()).  To properly reset this status, you must invoke an appropriate query-related callback on the store.  The callbacks for queries are similar to those for records:


  • store.dataSourceDidFetchQuery(query) - called whenever the data source has completed fetching any related data for the query.  This puts the query results (record array) status into a READY state.
  • store.dataSourceDidErrorQuery(query, error) - called whenever the data source encountered an error fetching related data for the query.  This puts the query results status into an ERROR state.
  • store.dataSourceDidCancelQuery(query) - called whenever you had to cancel loading the results for some reason.  This is currently equivalent to calling dataSourceDidFetchQuery().


In addition to these basic callbacks, there is one other special method called store.loadQueryResults(query, storeKeys) that you will use when handling remote queries. This method is similar to store.dataSourceDidFetchQuery() except that you also provide an actual array of storeKeys (or a promise to provide the store keys) that will comprise the resulting set.


Moving On


On to learn about Fetching Data » 

Back to DataStore Programming Guide Home » 

Comments (0)

You don't have permission to comment on this page.