Skip to content
Browse files

Merge pull request #83 from pjmorse/records-edits

Records edits
  • Loading branch information...
2 parents f54ed3c + f2eae03 commit fb5298ae924f027e762b99d2fa47c60456d61c5e @wagenet wagenet committed May 23, 2011
Showing with 54 additions and 51 deletions.
  1. +54 −51 source/records.textile
View
105 source/records.textile
@@ -12,34 +12,36 @@ endprologue.
h3. Models, Records and the Store
-In SproutCore the model layer is the lowest application layer and holds all your data as well as the business logic of your application. The controller layer calls into the model layer to modify data and retrieves data from the model layer mostly using bindings.
+In SproutCore the model layer is the lowest application layer and holds all your data as well as the business logic of your application. The controller layer calls into the model layer to modify data and retrieves data from the model layer. Generally, controllers use bindings to perform these functions.
The model layer is also responsible for talking to your server, fetching and committing data when necessary. The server communication aspect of the model layer is not covered in this guide, but in the guide about using data sources.
-Models are a blueprint for your data, they define the data schema of your application. This data schema is mostly very similar to the data schema of your backend. In SproutCore models are defined by subclassing +SC.Record+. When you actually want to create a data record from one of your blueprints, you use +SC.Store+ to create an instance of a +SC.Record+ class. The store manages the lifecycle and the data of your records in a central place. When you retrieve or update a property from a record, the record actually uses the store to access the underlying data hash.
+Models are a blueprint for your data, defining the data schema of your application. This data schema is generally similar to the data schema of your back-end application.
-All the classes of SproutCore's model layer are located in the *datastore* folder inside of the main sproutcore folder. Have a look at the source code there if you want to have more in-depth information. The code has plenty of inline documentation and can be a valuable resource to gain deeper insights in how the store works.
+In SproutCore, models are defined by subclassing +SC.Record+. When you actually want to create a data record from one of your blueprints, you use +SC.Store+ to create an instance of a +SC.Record+ class. Your application's store manages the lifecycle and the data of your records in a central place. When you retrieve or update a property from a record, the record actually uses the store to access the underlying data hash.
+
+All the classes of SproutCore's model layer are located in the *datastore* folder inside the main sproutcore folder. Have a look at the source code there if you want to have more in-depth information. The code has plenty of inline documentation and can be a valuable resource to gain deeper insights in how the store works.
h3. Anatomy of Records
A SproutCore record consists of four main components:
* Store key
-* Id
+* ID
* Status
* Data hash
-Each record has a unique store key which is assigned when the record is created. The store key is a unique record identifier in the whole store and is mainly used internally to relate ids, statuses and data hashes to each other in an unambiguous way. The store key is the only one of the four components which is actually a property of +SC.Record+. The other three components are stored centrally in the store and mapped to the individual records using the store key.
+Each record has a unique _store key_ which is assigned when the record is created. The store key is a unique record identifier in the whole store and is used internally to relate IDs, statuses and data hashes to each other in an unambiguous way. The store key is the only one of the four components which is actually a property of +SC.Record+. The other three components are stored centrally in the store and mapped to the individual records using the store key.
<div style='text-align: center;'>
!images/records/record_anatomy.png!
</div>
-All records of a certain type have a unique id as usual in relational database systems. In fact the ids of SproutCore records usually are the same as the primary keys of your data in the backend. Therefore, unlike the store key, the id is not automatically created but it is your responsibility to assign a unique id when creating or loading records.
+All records of a given type have a unique _ID_, as usual in relational database systems. In fact the IDs of SproutCore records usually are the same as the primary keys of your data in the backend. Therefore, unlike the store key, the ID is not automatically created. Instead, it is your responsibility to assign a unique ID when creating or loading records.
-The status of a record represents its current state with respect to the corresponding record on the server. The store uses the status property to determine which operations can be performed on the record, for instance, if a record can be edited safely and which records need to be commited back to the server.
+The _status_ of a record represents its current state with respect to the corresponding record on the server. The store uses the status property to determine which operations can be performed on the record, for instance, if a record can be edited safely and which records need to be committed back to the server.
-Last but not least, the actual data of a record is stored in a plain "JSON":http://www.json.org data hash. When you get or set a property on a record, the value of this property is read from or written to the data hash.
+Last but not least, the actual _data_ of a record is stored in a plain "JSON":http://www.json.org data hash. When you get or set a property on a record, the value of this property is read from or written to the data hash.
h4. Primary Record States
@@ -50,9 +52,9 @@ There are five primary record status codes in SproutCore:
* +SC.Record.DESTROYED+
* +SC.Record.ERROR+
-The names of these states are pretty self explanatory: EMPTY indicates a non existing record. READY indicates that the record can be safely edited. BUSY indicates that the record is currently locked for write operations, mostly because of an ongoing commmunication with the server. Finally DESTROYED is the state of destroyed records and ERROR indicates that something went wrong while processing this record.
+The names of these states are pretty self explanatory: +EMPTY+ indicates a non-existent record. +READY+ indicates that the record can be safely edited. +BUSY+ indicates that the record is currently locked for write operations, mostly because of an ongoing communication with the server. Finally +DESTROYED+ is the state of destroyed records and +ERROR+ indicates that something went wrong while processing this record.
-The three main states READY, BUSY and DESTROYED have several substates. You will learn more about these substates below when you actually start working with records. You can also refer to the complete overview of record states in the last section of this guide.
+The three main states +READY+, +BUSY+ and +DESTROYED+ have several sub-states. You will learn more about these sub-states below when you actually start working with records. You can also refer to the complete overview of record states in the last section of this guide.
h3. Defining Your Models
@@ -74,9 +76,9 @@ App.Contact = SC.Record.extend({
});
</javascript>
-WARNING: Property names defined on +SC.Record+ itself are reserved names, meaning they cannot be used for custom record attribtutes. Please refer to the "documentation of SC.Record":http://docs.sproutcore.com/symbols/SC.Record.html for a list of all reserved names.
+WARNING: Property names defined on +SC.Record+ itself are reserved names, meaning they cannot be used for custom record attributes. Please refer to the "documentation of SC.Record":http://docs.sproutcore.com/symbols/SC.Record.html for a list of all reserved names.
-We have used the +SC.Record.attr+ helper to add the +firstName+, +lastName+ and +age+ attributes with the type of each attribute as first argument. The optional second argument of +SC.Record.attr+ is an option hash. E.g. we can add some default values to our attribtues:
+We have used the +SC.Record.attr+ helper to add the +firstName+, +lastName+ and +age+ attributes with the type of each attribute as first argument. The optional second argument of +SC.Record.attr+ is an option hash. E.g. we can add some default values to our attributes:
<javascript filename="apps/app/models/contact.js">
App.Contact = SC.Record.extend({
@@ -86,7 +88,7 @@ App.Contact = SC.Record.extend({
});
</javascript>
-Whenever you specify a +defaultValue+ option on an attribute it will return this default value if it's +null+ or +undefined+.
+Whenever you specify a +defaultValue+ option on an attribute, it will return this default value if that attribute is +null+ or +undefined+ for a given instance.
NOTE: The +defaultValue+ will not be written to the underlying data hash and therefore not committed back to the server.
@@ -118,11 +120,11 @@ App.Contact = SC.Record.extend({
});
</javascript>
-For a reference of how to specify your custom date format check the documentation of "SC.DateTime#toFormatedString":http://docs.sproutcore.com/symbols/SC.DateTime.html#toFormattedString.
+For a reference of how to specify your custom date format check the documentation of "SC.DateTime#toFormattedString":http://docs.sproutcore.com/symbols/SC.DateTime.html#toFormattedString.
h4. Record Ids
-In SproutCore you don't define the primary key property of your models explicitly like you defined your custom attributes above. The records' primary keys are managed by the store, so every record inherently has an id property. However, you can specify the identifier of this id property. This is where it can become a bit confusing at first... but let's clear it up step by step.
+In SproutCore you don't define the primary key property of your models explicitly like you defined your custom attributes above. The records' primary keys are managed by the store, so every record inherently has an ID property. However, you can specify the identifier of this ID property. This is where it can become a bit confusing at first... but let's clear it up step by step.
First of all, by default SproutCore uses the identifier "guid" for the primary key. You can change this identifier by defining a +primaryKey+ property in your model:
@@ -132,22 +134,22 @@ App.Contact = SC.Record.extend({
});
</javascript>
-NOTE: If you want to use your custom id identifier in all your models, you can make your life a bit easier and your code more maintainable by defining a custom record base class, where you define the +primaryKey+ property. Then you can subclass this custom base class to create your models.
+NOTE: If you want to use your custom ID identifier in all your models, you can make your life a bit easier and your code more maintainable by defining a custom record base class, where you define the +primaryKey+ property. Then you can subclass this custom base class to create your models.
-However, this primary key identifier is only used to identify the id property in the underlying data hash, but not to get or set the id on a record. For example if you create a record and pass a hash with initial values, then SproutCore will now look for a property called "uid" in the hash when you don't explicitly specify an id. If you want to get or set the id of a record though, you always use +id+ independent of the +primaryKey+'s value:
+However, this primary key identifier is only used to identify the ID property in the underlying data hash, but not to get or set the ID on a record. For example if you create a record and pass a hash with initial values, then SproutCore will now look for a property called "uid" in the hash when you don't explicitly specify an ID. If you want to get or set the ID of a record though, you always use +id+ independent of the +primaryKey+'s value:
<javascript>
myRecord.get('id'); // note: NOT 'uid'
myRecord.set('id', 1);
</javascript>
-WARNING: You should never change the id of an existing record using +set()+ like above unless you know what you are doing.
+WARNING: You should never change the ID of an existing record using +set()+ like above unless you know what you are doing.
-It is a best practice to never include the id in the data hash, because then you end up with two ids: the id property in the data hash and the id managed by the store. If you receive a JSON data hash from the server (where the id is necessarily included) then you should extract and delete the id from this hash before using it to load the record into the store.
+It is a best practice to never include the ID in the data hash, because then you end up with two IDs: the ID property in the data hash and the ID managed by the store. If you receive a JSON data hash from the server (where the ID is necessarily included) then you should extract and delete the ID from this hash before using it to load the record into the store.
h4. Relations
-Often models don't exist completely independent of each other but are related to other models. For example one or more addresses could belong to the +Contact+ model we created above. So let's define the +Address+ model first:
+Often models don't exist completely independently of each other but are related to other models. For example one or more addresses could belong to the +Contact+ model we created above. So let's define the +Address+ model first:
<javascript filename="apps/app/models/address.js">
App.Address = SC.Record.extend({
@@ -178,11 +180,11 @@ App.Address = SC.Record.extend({
});
</javascript>
-Notice the +isMaster+ and +inverse+ options used with the +toOne+ helper. The +isMaster: YES+ option on the +address+ attribute makes sure, that the +Contact+ record actually gets marked as changed when you assign a different +Address+ record to it. You should always set the +isMaster+ option to +YES+ on one side of the relation and to +NO+ on the other to control which record is committed back to the server when you alter the relation.
+Notice the +isMaster+ and +inverse+ options used with the +toOne+ helper. The +isMaster: YES+ option on the +address+ attribute ensures that the +Contact+ record actually gets marked as changed when you assign a different +Address+ record to it. You should always set the +isMaster+ option to +YES+ on one side of the relation and to +NO+ on the other to control which record is committed back to the server when you alter the relation.
The +inverse+ option specifies the property name of the inverse relation on the associated model and should be set on the side of the relation where +isMaster+ is set to +YES+.
-In the underlying data hash a +toOne+ relation is simply represented as the id of the associated record.
+In the underlying data hash a +toOne+ relation is simply represented as the ID of the associated record.
NOTE: It is not mandatory to define both directions of the relation if you don't need it.
@@ -210,7 +212,7 @@ App.Address = SC.Record.extend({
The only thing that changed compared to the one-to-one example above is the +toMany+ keyword in the +Contact+ model. The +isMaster+ and +inverse+ options apply to +toMany+ relations in the same way as they do to +toOne+ relations.
-In the underlying data hash a +toMany+ relation is represented as an array of ids of the the associated records.
+In the underlying data hash a +toMany+ relation is represented as an array of IDs of the the associated records.
NOTE: It is not mandatory to define both directions of the relation if you don't need it.
@@ -238,7 +240,7 @@ App.Address = SC.Record.extend({
Again the only thing that changed compared to the one-to-many example from above is the use of the +toMany+ helper in the +Address+ model.
-Since a many-to-many relation effectively is constructed by using +toMany+ on both sides, it is represented in the underlying data hashes of both sides of the relation as an array of record ids.
+Since a many-to-many relation effectively is constructed by using +toMany+ on both sides, it is represented in the underlying data hashes of both sides of the relation as an array of record IDs.
NOTE: It is not mandatory to define both directions of the relation if you don't need it.
@@ -270,7 +272,7 @@ App = SC.Application.create({
});
</javascript>
-The example above shows creating the store with fixtures as data source. You can read more about fixtures and using other data sources in the respective guides.
+In this example, we create the store with fixtures as data source. You can read more about "fixtures":fixtures.html and other data sources in the respective guides.
h4. Creating Records
@@ -280,7 +282,7 @@ You can create records of a previously defined record type like this:
contact = App.store.createRecord(App.Contact, {});
</javascript>
-The first argument of the store's +createRecord+ method is the record type. The second argument is a hash with optional initial values. Furthermore you can specify the record id as third argument:
+The first argument of the store's +createRecord+ method is the record type. The second argument is a hash with optional initial values. Furthermore you can specify the record ID as third argument:
<javascript>
contact = App.store.createRecord(
@@ -290,7 +292,7 @@ contact = App.store.createRecord(
);
</javascript>
-Usually you will not specify an id like this, because either you get the record id from the server, or you want to use some kind of temporary id on new records until they get comitted to the server, which then can return the id of the persisted record. So let's use a temporary id:
+Usually you will not specify an ID like this, because either you get the record ID from the server, or you want to use some kind of temporary ID on new records until they get committed to the server, which then can return a persistent ID. So let's use a temporary ID:
<javascript>
contact = App.store.createRecord(
@@ -300,7 +302,7 @@ contact = App.store.createRecord(
);
</javascript>
-NOTE: Ids are not limited to numbers, but can also be strings.
+NOTE: IDs are not limited to numbers, but can also be strings.
When you create a record its status will be +READY_NEW+, indicating that the record is editable and does not exist on the server yet.
@@ -341,11 +343,11 @@ To delete a certain record, just call the +destroy+ method on it:
contact.destroy();
</javascript>
-Equally to updating a record, the record has to be in a +READY+ state to be able to be destroyed. If you destroy a newly created record (which was not yet committed to the server) the status will transition from +READY_NEW+ to +DESTROYED_CLEAN+, indicating that there is no need to tell the server about the destroy, since it never knew about this record in the first place. If you destroy a record loaded from the server, then the state will transition from +READY_CLEAN+ (or +READY_DIRTY+ if you changed it before) to +DESTROYED_DIRTY+, indicating that the server needs to be notified about this destroy action.
+Just as when updating a record, the record has to be in a +READY+ state to be able to be destroyed. If you destroy a newly created record (which was not yet committed to the server) the status will transition from +READY_NEW+ to +DESTROYED_CLEAN+, indicating that there is no need to tell the server about the destroy, since it never knew about this record in the first place. If you destroy a record loaded from the server, then the state will transition from +READY_CLEAN+ (or +READY_DIRTY+ if you changed it before) to +DESTROYED_DIRTY+, indicating that the server needs to be notified about this destroy action.
h4. Getting Information about Records
-You can get the id, the store key and the status of a record by calling the +get+ method on the respective properties:
+You can get the ID, the store key and the status of a record by calling the +get+ method on the respective properties:
<javascript>
id = contact.get('id');
@@ -373,23 +375,23 @@ NOTE: For a complete list of record state constants see the "documentation of th
h3. Finding Records in the Store
-Because the store manages all records in memory, you can query it for records of a certain type, records with a certain id or more complex search criteria.
+Because the store manages all records in memory, you can query it for records of a certain type, records with a certain ID or more complex search criteria.
-h4. Finding a Specific Record by Id
+h4. Finding a Specific Record by ID
-If you know the type and the id of the record you want to retrieve, you can just hand these two parameters to the store's +find+ method:
+If you know the type and the ID of the record you want to retrieve, you can just hand these two parameters to the store's +find+ method:
<javascript>
contact = App.store.find(App.Contact, 1);
</javascript>
-This statement returns the record of type +App.Contact+ with the id 1. If the record does not exist, then the return value will be +null+.
+This statement returns the record of type +App.Contact+ with the ID 1. If the record does not exist, then the return value will be +null+.
-WARNING: When +find+ is called with a record type and an id as arguments, it only looks for records of exactly this type. It will not return records which type is a subclass of the specified record type.
+WARNING: When +find+ is called with a record type and an ID as arguments, it only looks for records of exactly this type. It will not return records which type is a subclass of the specified record type.
h4. Finding All Records of a Certain Type
-To find all records of one record type, just pass it to the +find+ method:
+To find all records of one record type, just pass that type to the +find+ method:
<javascript>
contacts = App.store.find(App.Contact);
@@ -411,12 +413,13 @@ allRecords = App.store.find(SC.Record);
The above statement returns all records in your application, because we are asking for all records of type +SC.Record+, which is SproutCore's base model class.
-Internally +find+ converts the specified record types to a query, it's just a convenient method to save some characters of typing required to create the query yourself. Read on in the next section how to do this and to learn more about the return type of +find+.
-
+Internally +find+ converts the specified record types to a query. +find+ is just a convenient method to save some characters of typing required to create the query yourself. Read on in the next section how to do this and to learn more about the return type of +find+.
h4. Using Queries
-SproutCore features a SQL-like query language to facilitate more complex queries to the store. However, let us first translate the +find+ calls of the previous section to using queries, like +find+ does internally. To build a query which looks for all records of a certain type, you just call +SC.Query.local+ with this record type as argument and pass this query to +find+:
+SproutCore features a SQL-like query language to facilitate more complex queries to the store. To demonstrate, let us first translate the +find+ calls of the previous section to using queries, as +find+ does internally.
+
+To build a query which looks for all records of a certain type, you just call +SC.Query.local+ with this record type as argument and pass this query to +find+:
<javascript>
query = SC.Query.local(App.Contact);
@@ -433,7 +436,7 @@ query = SC.Query.local(SC.Record);
allRecords = App.store.find(query);
</javascript>
-Whenever you call +SC.Store+'s +find+ method with a query (or using one of the convenient ways from the previous section) it returns a +SC.RecordArray+. As the name already indicates +SC.RecordArray+ implements +SC.Array+ and therefore you can use it like a normal read-only array. For example:
+Whenever you call +SC.Store+'s +find+ method with a query (or using one of the convenient ways from the previous section) it returns a +SC.RecordArray+. As the name indicates, +SC.RecordArray+ implements +SC.Array+ and therefore you can use it like a normal read-only array. For example:
<javascript>
contacts.firstObject(); // returns first result
@@ -445,7 +448,7 @@ Please refer to the "documentation of SC.Array":http://docs.sproutcore.com/symbo
NOTE: If the query was not yet fetched from the server, the store automatically forwards it to the data source to load the data from the server.
-NOTE: +SC.RecordArray+ objects automatically get updated by the store when you add or remove records to or from the store which match the corresponding query.
+NOTE: Objects in an +SC.RecordArray+ are automatically updated by the store when you add or remove records to or from the store which match the corresponding query.
h5. Conditions
@@ -471,9 +474,9 @@ query = SC.Query.local(App.Contacts, {
});
</javascript>
-However, you will want to not only hard-code the query conditions, but to make use of variables containing the desired values. For this you can use query parameters.
+However, you will not want to hard-code the query conditions, but to make use of variables containing the desired values. For this you can use query parameters.
-SproutCore handles two different types of query parameters: sequential and named parameters. Lets rephrase the above query using sequential parameters:
+SproutCore handles two different types of query parameters: sequential and named parameters. Let's rephrase the above query using sequential parameters:
<javascript>
query = SC.Query.local(App.Contacts, {
@@ -556,13 +559,13 @@ h5. Local vs. Remote Queries
You will have noticed the keyword +local+ in the +SC.Query.local+ call we used until now to create the queries. Actually the keyword +local+ is somewhat confusing, because local queries do not act exclusively on the in-memory store but also call the data source to fetch records from the server. The main characteristic of local queries is that the store automatically updates their results whenever the contents of the local in-memory store change.
-Remote queries (build with +SC.Query.remote+), on the other hand, return a +SC.RecordArray+ which is not updated automatically. However, "remote" doesn't mean necessarily that the results have to be fetched from a remote server. They could also be loaded from a local browser storage. It's admittedly a bad choice of names.
+Remote queries (build with +SC.Query.remote+), on the other hand, return a +SC.RecordArray+ which is not updated automatically. "Remote" doesn't mean necessarily that the results have to be fetched from a remote server. They could also be loaded from a local browser storage. It's admittedly a bad choice of names.
WARNING: You should use local queries in almost all cases unless you know what you're doing.
h4. Extending SproutCore's Query Language
-If SproutCore's built-in query operators are not sufficient for your use case, you can easily extend the query language. For example by default there are no bit-wise operators, so lets implement a +BITAND+ operator which evaluates to +true+ if the bit-wise and of the two arguments is unequal to zero:
+If SproutCore's built-in query operators are not sufficient for your use case, you can easily extend the query language. For example, by default there are no bit-wise operators, so let's implement a +BITAND+ operator which evaluates to +true+ if the bit-wise and of the two arguments is unequal to zero:
<javascript>
SC.Query.registerQueryExtension('BITAND', {
@@ -599,17 +602,17 @@ h4. READY Substates
h4. BUSY Substates
-+SC.Record.BUSY_LOADING+: When you first get a record from the store, it will usually be in the BUSY_LOADING state. This means that the record did not exist in the store and the server has not yet returned any data. All properties will be empty at this point. When data is loaded for this record, it will transition to READY_CLEAN.
++SC.Record.BUSY_LOADING+: When you first get a record from the store, it will usually be in the +BUSY_LOADING+ state. This means that the record did not exist in the store and the server has not yet returned any data. All properties will be empty at this point. When data is loaded for this record, it will transition to +READY_CLEAN+.
-+SC.Record.BUSY_CREATING+: A record in this state was newly created in the store. We are now waiting on the data source to confirm that it has been created in the server as well. Once this completes, the record will become READY_CLEAN. If the create fails, the record will return to READY_NEW.
++SC.Record.BUSY_CREATING+: A record in this state was newly created in the store. We are now waiting on the data source to confirm that it has been created in the server as well. Once this completes, the record will become +READY_CLEAN+. If the create fails, the record will return to +READY_NEW+.
-+SC.Record.BUSY_COMMITTING+: A record in this state was modified in the store and it is waiting on the data source to confirm the change is saved on the server as well. Once this completes, the record will become READY_CLEAN.
++SC.Record.BUSY_COMMITTING+: A record in this state was modified in the store and it is waiting on the data source to confirm the change is saved on the server as well. Once this completes, the record will become +READY_CLEAN+.
-+SC.Record.BUSY_REFRESH_DIRTY+: A record in this state has local changes but you asked the data source to reload the data from the server anyway. When the server updates, it will replace any local changes with a fresh copy from the server. If the refresh fails, the record will return to its READY_DIRTY state.
++SC.Record.BUSY_REFRESH_DIRTY+: A record in this state has local changes but you asked the data source to reload the data from the server anyway. When the server updates, it will replace any local changes with a fresh copy from the server. If the refresh fails, the record will return to its +READY_DIRTY+ state.
-+SC.Record.BUSY_REFRESH_CLEAN+: A record in this state has no local changes but you asked it to reload the data from the server anyway. When the server finished, success or failure, this record will return to the READY_CLEAN state (unless there is an error).
++SC.Record.BUSY_REFRESH_CLEAN+: A record in this state has no local changes but you asked it to reload the data from the server anyway. When the server finished, success or failure, this record will return to the +READY_CLEAN+ state (unless there is an error).
-+SC.Record.BUSY_DESTROYING+: A record in this state was destroyed in the store and now is being destroyed on the server as well. Once the destroy has completed, the record will become DESTROYED_CLEAN. If the destroy fails (without an error), it will become DESTROYED_DIRTY again.
++SC.Record.BUSY_DESTROYING+: A record in this state was destroyed in the store and now is being destroyed on the server as well. Once the destroy has completed, the record will become +DESTROYED_CLEAN+. If the destroy fails (without an error), it will become +DESTROYED_DIRTY+ again.
!images/records/busy_substates.png!

0 comments on commit fb5298a

Please sign in to comment.
Something went wrong with that request. Please try again.