Records in SproutCore follow a life cycle similar to records in any other database, but with an important difference. Since the SproutCore store hosts the data only temporarily (remember it's a quick access cache representing remote data), we don't actually perform final data modifications within the application. Instead, we invoke requests and modifications (Create, Read, Update, Destroy) on a remote data store and update our local record state to match them.
We've already seen that records have a status value, such as SC.Record.READY_CLEAN
, which indicates the current state of the record. To help us understand every possible state in the life cycle of a SproutCore record, I've created figures of the SC.Record
states. You should find these figures useful when you begin working with records. We will refer back to them repeatedly in this section as we learn how to load, unload, create, read, update, and ultimately destroy records.
The following figure shows the static states of a record and the methods on the store that we use for transition between them. The methods are on the store because the store manages the state of each record and so, ultimately determines how the record state should change.
Note that transitions are not immediate and so, there are several intermediate busy states that exist while we wait for a confirmation from the remote data source. As you can see in the figure, the record will typically be in one of the six static states: READY_NEW
, READY_CLEAN
, READY_DIRTY
, DESTROYED_DIRTY
, DESTROYED_CLEAN
, or EMPTY
. We will look at each of these states in more detail shortly.
The SproutCore data store includes support for loading and unloading records. Loading a record is different from creating a record because the record has already been created somewhere on a remote store and we are just loading it temporarily for use within our application. Likewise, unloading a record is not the same as destroying one because the real remote record is not actually being destroyed.
To load a record, we call loadRecord
on the store. This method takes the type of the record and the data hash of the record as arguments. You can also pass the ID as the third argument but in case you don't, it will be looked up from within the data hash according to the record's primaryKey
value.
For example:
var storeKey; storeKey = MyApp.store.loadRecord(MyApp.Calendar, { guid: 'calendar3', title: "Birthdays" });
As you can see from the state figure, a record that is loaded will initially be in the READY_CLEAN
state, which means it is synchronized with the remote data store as far as we know.
The return value for loadRecord
is the unique storeKey
value for the record. Store keys represent how the store manages its records internally. Because the same ID may be shared between different types of records and because newly created records usually have no ID, the store generates a unique store key to properly identify each record. We will find that we use the store key from time to time when working with the store, and you can retrieve it from the record if you need.
For example:
storeKey = myRecord.get('storeKey'),
Finally, as we will see with each of these methods, there is a multiple record version of loadRecord
, which is loadRecords
. To use loadRecords
or any of the other pluralized methods, we simply pass the same arguments, but within arrays.
You will notice in the figure that loadRecord
appears each time with pushRetrieve
in parenthesis. This is because the loadRecord
method is really a wrapper over pushRetrieve
and dataSourceDidComplete
(a method we will look at later) and depending on the state of the record, calls the appropriate method.
In a lot of SproutCore applications, the number of records can exceed thousands, tens of thousands, and even millions of records. In such a scenario, it is absolutely necessary to also unload records in order to free the memory. To unload a record that has been loaded, we call unloadRecord
on the store and pass either the record type and ID or the store key to it.
For example:
MyApp.store.unloadRecord(MyApp.Calendar, 'calendar3'),
This is equivalent to the following code:
MyApp.store.unloadRecord(null, null, aCalendar.get('storeKey'));
This sub-section is titled "To be read records" rather than "Reading records" because we aren't doing the actual reading from the remote database directly. Due to this slight semantic difference, the record we receive from the store will either be in the BUSY_LOADING
or one of the two BUSY_REFRESH
states while it is actually being read from the remote source and sent to us. The following figure shows these additional busy states.
The typical way we request records is with the find
method on the store. We can retrieve all records of a type by passing the record type to find
. If we pass a specific ID as the second argument, it will return just the matching record of that type. We can also retrieve all records that match certain conditions by passing a query object to find
.
For example:
// An SC.RecordArray of event records. allEvents = MyApp.store.find(MyApp.Event); // A single event record. event = MyApp.store.find(MyApp.Event, 'event1'), // An SC.RecordArray of event records starting after now. futureEvents = MyApp.store.find(SC.Query.create({ recordType: MyApp.Event, conditions: "startAt > " + (new Date()).getTime() });
The previous example introduces an important class in the SproutCore Model layer that we haven't looked at yet, SC.RecordArray
. When we retrieve a group of records, the returned object is always a record array, which is like a normal array, but has a status value that we can use to know when the actual array was filled. Record arrays are also able to update their contents live from the store when they are based on local query objects. We'll look more at working with query objects later on in this chapter.
Another thing to know is that the find
method is actually just a wrapper over retrieveRecord
, materializeRecord
, and some internal code for handling query objects. When you call find
with a record type and ID, it will call retrieveRecord
if the record isn't loaded, in order to retrieve it from the remote source or will call materializeRecord
if it is loaded, to return an instance of the record class. While you will likely never use retrieveRecord
over find
, the plural version retrieveRecords
allows you to request a specific set of record IDs at once, which may be of use.
Lastly, the other store method shown in the figure is the refreshRecord
method. We can call this on already loaded records in order to re-fetch their data from the remote source. Depending on the current state of the loaded record, this will put the record into the BUSY_REFRESH_CLEAN
or BUSY_REFRESH_DIRTY
state.
Creating a new record in the client is simple enough. We simply call createRecord
on the store and pass the type of record, the data hash for the record, and optionally an ID for the record. Typically, you will not have an ID for the record yet because assigning unique IDs is usually the job of the remote data store.
For example:
newCalendar = MyApp.store.createRecord(MyApp.Calendar, { title: "Family" }); console.log(newCalendar.toString()); // > MyApp.Calendar({title: Family}) READY_NEW
As you can see from the static record states figure, a call to createRecord
is the only entry point to the READY_NEW
state. This state indicates that the record can be used but has not yet been created in the remote data store.
Use createRecords
to create multiple records at once.
Once a record is in the READY_CLEAN
state, modifications to its attributes will automatically move it to the READY_DIRTY
state. The dirty state indicates that the record is no longer synchronized with the remote data store and that the remote data store will need to be updated.
In the static record state figure, the connection between READY_CLEAN
and READY_DIRTY
shows the recordDidChange
method of the store. While you could call this, you won't typically need to, since it will be called for us by using key-value coding (that is, set
) to change the record attributes.
There is also a recordsDidChange
method for indicating multiple record changes.
Destroying a record on the client is again very simple. The store provides the destroyRecord
and destroyRecords
methods that work almost identically to the unloadRecord
and
unloadRecords
methods. This means that you pass the same arguments to destroyRecord
, either the record type and ID or the store key of the record. The difference is that with destroyRecord
, the data hash of the record is not immediately cleared and its state becomes DESTROYED_DIRTY
, indicating that it needs to be synchronized with the server.
For example:
MyApp.store.destroyRecord(MyApp.Calendar, toBeDestroyedCalendar.get('id')); console.log(toBeDestroyedCalendar.toString()); // > MyApp.Calendar({guid: work-calendar, title: Work}) DESTROYED_DIRTY
By the way, we can get the same result by simply calling destroy
on the record instance as shown in the following code line:
toBeDestroyedCalendar.destroy();
We will look at how to complete the transition between the DESTROYED_DIRTY
and DESTROYED_CLEAN
states shortly.
18.118.198.81