Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge remote branch 'florian/records'

  • Loading branch information...
commit 4616b79168bde9ce4240d2618e2fd3afdfcf0a6e 2 parents 89d5cd9 + 8e8ac50
@wycats authored
View
BIN  assets/images/records/busy_substates.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  assets/images/records/destroyed_substates.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
2,000 assets/images/records/diagrams.graffle
2,000 additions, 0 deletions not shown
View
BIN  assets/images/records/ready_substates.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  assets/images/records/record_anatomy.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
2  guides.yml
@@ -38,7 +38,7 @@ index:
Models:
- title: SproutCore Records
url: records
- text: "The SproutCore data framework is a full-featured ORM-like framework for working with data in the SproutCore Learn about the SproutCore data framework and how you can use it to interact with your data sources."
+ text: "The SproutCore data framework is a full-featured ORM-like framework for working with data in SproutCore. Learn about the SproutCore data framework and how you can use it to interact with your data sources."
construction: true
- title: Hooking Up to a Backend
url: data_source
View
651 source/records.textile
@@ -0,0 +1,651 @@
+h2. Using Records and the Data Store
+
+This guide covers the basics of SproutCore's model layer. By referring to this guide, you will be able to:
+
+* Understand the anatomy of records
+* Define your application specific models and relations between them
+* Create and manage records
+* Understand how the store manages your data
+* Query the store for data
+
+endprologue.
+
+
+
+h3. Models, Records and the Store
+
+In a SproutCore application the model layer is the lowest 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.
+
+The model layer is also responsible for talking to your server, fetching and commiting 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.
+
+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.
+
+
+
+
+h3. Anatomy of Records
+
+A SproutCore record consists of four main components:
+# Store key
+# 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 a unambigious 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 storea and mapped to the individual records using the store key.
+
+!images/records/record_anatomy.png!
+
+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 record unique id on creation.
+
+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 if a record can be edited safely and which records need to be commited back to the server.
+
+Last but not least, the actual data of a record is stored in a plain JSON 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
+
+There are five basic record status codes in SproutCore:
+* +SC.Record.EMPTY+
+* +SC.Record.READY+
+* +SC.Record.BUSY+
+* +SC.Record.DESTROYED+
+* +SC.Record.ERROR+
+
+The names of these states are pretty self explanatory: EMPTY indicates that the record is not yet created. 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 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 states in the last section of this guide.
+
+
+
+
+
+h3. Defining your models
+
+Defining a model in SproutCore is as easy as subclassing +SC.Record+:
+
+<javascript>
+MyApp.Contact = SC.Record.extend({
+
+});
+</javascript>
+
+You just have created your custom +MyApp.Custom+ model class. However, this empty model is only of limited use, so let's add some record attributes.
+
+<javascript>
+MyApp.Contact = SC.Record.extend({
+ firstName: SC.Record.attr(String),
+ lastName: SC.Record.attr(String),
+ age: SC.Record.attr(Number)
+});
+</javascript>
+
+WARNING: Please refer to the "documentation of SC.Record":http://docs.sproutcore.com/symbols/SC.Record.html to see a list of all its property and method names which you cannot use for your own attributes!
+
+We have used the +SC.Record.attr+ helper to add the +firstName+, +lastName+ and +age+ attributes with the type of the attribtue as first argument. The optional second argument of the +SC.Record.attr+ helper is an option hash. E.g. we can add some default values to our attribtues:
+
+<javascript>
+MyApp.Contact = SC.Record.extend({
+ firstName: SC.Record.attr(String, { defaultValue: 'Unspecified' }),
+ lastName: SC.Record.attr(String, { defaultValue: 'Unspecified' }),
+ age: SC.Record.attr(Number, { defaultValue: 0 })
+});
+</javascript>
+
+Whenever you specify a +defaultValue+ option on an attribute it will return this default value if it's +null+ or +undefined+.
+
+NOTE: The +defaultValue+ will not be written to the underlying data hash and therefore not committed back to the server.
+
+If the name of the model's attribute property differs from the name used in the data hash, you can specify a custom key for each attribute which will be used to access the data hash:
+
+<javascript>
+MyApp.Contact = SC.Record.extend({
+ firstName: SC.Record.attr(String, { key: 'first_name' }),
+ lastName: SC.Record.attr(String, { key: 'last_name' }),
+ age: SC.Record.attr(Number)
+});
+</javascript>
+
+
+h4. Attrbibute Types
+
+All basic JavaScript data types can be used as attribute types:
+* String
+* Number
+* Boolean
+* Array
+* Object
+
+Additionally SproutCore comes with a predefined attribute helper for date/time values.
+
+<javascript>
+MyApp.Contact = SC.Record.extend({
+ // ...
+ dateOfBirth: SC.Record.attr(SC.DateTime, { format: 'YY-mm-dd' })
+});
+</javascript>
+
+For a reference of how to specify your custom date format check the documentation of the "SC.DateTime#toFormatedString":http://docs.sproutcore.com/symbols/SC.DateTime.html#SC.DateTime%23toFormattedString method.
+
+
+
+h4. Record Ids
+
+In SproutCore you don't define the primary key 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:
+
+<javascript>
+MyApp.Contact = SC.Record.extend({
+ primaryKey: 'uid',
+ // ...
+});
+</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.
+
+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 check for a property called "uid" in the hash when you don't explicitly specify an id on creation. If you want to get or set the id of a record though, you still would do
+
+<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()+. And if you do so, you should know what you are doing and probably not need this guide...
+
+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 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>
+MyApp.Address = SC.Record.extend({
+ street: SC.Record.attr(String),
+ number: SC.Record.attr(Number),
+});
+</javascript>
+
+
+h5. One-to-One Relations
+
+If we only need one address to be associated with each contact, then we can use the +toOne+ relation helper:
+
+<javascript>
+MyApp.Contact = SC.Record.extend({
+ // ...
+ address: SC.Record.toOne(
+ 'MyApp.Address',
+ { isMaster: YES, inverse: 'contact' }
+ )
+});
+
+MyApp.Address = SC.Record.extend({
+ // ...
+ contact: SC.Record.toOne(
+ 'MyApp.Contact',
+ { isMaster: NO }
+ )
+});
+</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 the relation changes.
+
+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.
+
+h5. One-to-Many Relations
+
+If we want to associate multiple addresses with a certain contact, then we have to use the +toMany+ relation helper:
+
+<javascript>
+MyApp.Contact = SC.Record.extend({
+ // ...
+ address: SC.Record.toMany(
+ 'MyApp.Address',
+ { isMaster: YES, inverse: 'contact' }
+ )
+});
+
+MyApp.Address = SC.Record.extend({
+ // ...
+ contact: SC.Record.toOne(
+ 'MyApp.Contact',
+ { isMaster: NO }
+ )
+});
+</javascript>
+
+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.
+
+h5. Many-to-Many Relations
+
+If we not only want to relate multiple addresses to one contact, but also relate one address to multiple contacts, we have to use +toMany+ on both sides of the relation. SproutCore's +toMany+ helper manages many-to-many relations without a join table, which you would use in a relational database:
+
+<javascript>
+MyApp.Contact = SC.Record.extend({
+ // ...
+ address: SC.Record.toMany(
+ 'MyApp.Address',
+ { isMaster: YES, inverse: 'contact' }
+ )
+});
+
+MyApp.Address = SC.Record.extend({
+ // ...
+ contact: SC.Record.toMany(
+ 'MyApp.Contact',
+ { isMaster: NO }
+ )
+});
+</javascript>
+
+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 hash as an array of record ids.
+
+
+h4. Other Properties on Model Classes
+
+Any property defined on a model class not using +SC.Record.attr+ is a transient property. This means that its value is not passed through to the data hash of the record and therefore is neither committed back to the server nor loaded from incoming JSON data.
+
+<javascript>
+MyApp.Contact = SC.Record.extend({
+ // transient property
+ isContact: YES,
+
+ // transient computed property
+ fullName: function() {
+ return this.get('firstName') + ' ' + this.get('lastName');
+ }.property('firstName', 'lastName').cacheable(),
+ // ...
+});
+</javascript>
+
+NOTE: If you use +set()+ on an undefined property, SproutCore by default will pass the value through to the underlying data hash. You can turn this behavior off by setting +ignoreUnknownProperties: YES+ in your model classes.
+
+
+
+
+h3. Using Your Models
+
+Now that we have defined our +Contact+ and +Address+ models it's time to actually create some records. All records are managed by the store, so we have to make sure first that we have an instance of +SC.Store+ available. Usually the store is instantiated somewhere in your application's "main.js" file:
+
+<javascript>
+// ...
+store: SC.Store.create().from(SC.Record.fixtures()),
+// ...
+</javascript>
+
+In the example above we are using fixtures as the data source. You can read more about fixtures and using other data sources in the respective guides.
+
+
+h4. Creating Records
+
+You can create records of a previously defined record type like this:
+
+<javascript>
+myContact = MyApp.store.createRecord(MyApp.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:
+
+<javascript>
+myContact = MyApp.store.createRecord(
+ MyApp.Contact,
+ { firstName: 'Florian', lastName: 'Kugler' },
+ 99
+);
+</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:
+
+<javascript>
+myContact = MyApp.store.createRecord(
+ MyApp.Contact,
+ { firstName: 'Florian', lastName: 'Kugler' },
+ - Math.random(Math.floor(Math.random() * 99999999))
+);
+</javascript>
+
+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.
+
+h5. Creating associated records
+
+When creating associated records, you first have to create the records and afterwards establish the connection between the records.
+
+<javascript>
+myContact = MyApp.store.createRecord(MyApp.Contact, {/*...*/}, 1);
+myAddress = MyApp.store.createRecord(MyApp.Address, {/*...*/}, 1);
+
+// for a toOne relation
+myContact.set('address', myAddress);
+
+// example for a toMany relation
+myContact.get('address').pushObject(myAddress);
+</javascript>
+
+In this case we're adding the +Address+ record to the +Contact+ record, because +Contact+ is defined as master in this relation (+isMaster: YES+) and has the inverse property set. It is important to add the non-master record to the master record in order to set up the connection between these records properly.
+
+
+h4. Updating Records
+
+Updating a record's attributes is as easy as calling +set()+:
+
+<javascript>
+myContact.set('firstName', 'Jack');
+</javascript>
+
+In order to be able to update record attributes the record has to be in a +READY+ state. If you are updating an attribute of a newly created record, the status will still be +READY_NEW+. If you update an attribute of a record that was previously loaded from the server or committed to the server, then the status will transition from +READY_CLEAN+ to +READY_DIRTY+. This indicates that the record needs to be committed back to the server.
+
+
+h4. Destroying Records
+
+To delete a certain record, just call +destroy()+ on it:
+
+<javascript>
+myContact.destroy();
+</javascript>
+
+Equally to updating a record, the record has to be in a +READY+ state to be able to destroy it. 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+ to +DESTROYED_DIRTY+, indicating that the server needs to 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 +get()+ on the respective properties:
+
+<javascript>
+id = myContact.get('id');
+storeKey = myContact.get('storeKey');
+status = myContact.get('status');
+</javascript>
+
+To test if the record is currently in a certain state, use JavaScript's binary operators:
+
+<javascript>
+status = myContact.get('status');
+
+// checks if the record is in any READY state
+if (status & SC.Record.READY) {
+ // ...
+}
+
+// checks if the record is in the READY_NEW state
+if (status & SC.Record.READY_NEW) {
+ // ...
+}
+</javascript>
+
+NOTE: For a complete list of record state constants see the documentation of SproutCore's "SC.Record class":http://docs.sproutcore.com/symbols/SC.Record.html.
+
+
+
+
+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.
+
+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:
+
+<javascript>
+myContact = MyApp.store.find(MyApp.Contact, 1);
+</javascript>
+
+This statement returns the record of type +MyApp.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. You cannot retrieve records this way 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:
+
+<javascript>
+myContacts = MyApp.store.find(MyApp.Contact);
+</javascript>
+
+If you want to find all records of several record types, pass an array of record types to the +find+ method:
+
+<javascript>
+myContactsAndAddresses = MyApp.store.find(
+ [MyApp.Contact, My.Address]
+);
+</javascript>
+
+You can also find all records of a certain type and all its subclasses:
+
+<javascript>
+allRecords = MyApp.store.find(SC.Record);
+</javascript>
+
+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+.
+
+
+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+:
+
+<javascript>
+query = SC.Query.local(MyApp.Contact);
+myContacts = MyApp.store.find(query);
+</javascript>
+
+As you can see, the method from the previous section of directly passing the record type to the +find+ method just saves you the call of +SC.Query.local+. Querying for multiple record types or all records follows the same pattern:
+
+<javascript>
+query = SC.Query.local([MyApp.Contact, MyApp.Addresses]);
+myContactsAndAddresses = MyApp.store.find(query);
+
+query = SC.Query.local(SC.Record);
+allRecords = MyApp.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:
+
+<javascript>
+myContacts.firstObject(); // returns first result
+myContacts.objectAt(3); // returns fourth result
+myContacts.lastObject(); // returns last result
+</javascript>
+
+Please refer to the "documentation of SC.Array":http://docs.sproutcore.com/symbols/SC.Array.html to learn about the array access methods.
+
+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+s automatically get updated by the store when you add or remove records to/from the store which match the corresponding query.
+
+
+h5. Conditions
+
+You can limit the results of a query to match certain conditions:
+
+<javascript>
+query = SC.Query.local(MyApp.Contacts, {
+ conditions: 'firstName = "Florian"'
+});
+results = MyApp.store.find(query);
+</javascript>
+
+The above query returns all records of type +MyApp.Contacts+ and subclasses of this type where the +firstName+ attribute matches the value "Florian".
+
+You can combine several conditions using the logical operators +AND+, +OR+ and +NOT+ as well as the parenthesis +(+ and +)+ for grouping:
+
+<javascript>
+query = SC.Query.local(MyApp.Contacts, {
+ conditions: 'firstName = "Florian" AND lastName = "Kugler"'
+});
+
+query = SC.Query.local(MyApp.Contacts, {
+ conditions: '(firstName = "Florian" AND lastName = "Kugler") OR age > 30'
+});
+</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. SproutCore handles two different types of query parameters: sequential and names parameters. Lets rephrase the above query using sequential parameters:
+
+<javascript>
+query = SC.Query.local(MyApp.Contacts, {
+ conditions: '(firstName = %@ AND lastName = %@) OR age > %@',
+ parameters: ['Florian', 'Kugler', 30]
+});
+</javascript>
+
+The elements of the +parameters+ array will be inserted sequentially at the positions of the +%@+ placeholders. Now lets do the same with named parameters:
+
+<javascript>
+query = SC.Query.local(MyApp.Contacts, {
+ conditions: '(firstName = {first} AND lastName = {last}) ' +
+ 'OR age > {age}',
+ parameters: {
+ first: 'Florian',
+ last: 'Kugler',
+ age: 30
+ }
+});
+</javascript>
+
+Which of these methods you use is mainly a matter of personal preference and the complexity of your query.
+
+The parameters inside the query conditions can be of the following types:
+* attribute names of the record type queried for
+* +null+ and +undefined+
+* +true+ and +false+
+* integer and floating point numbers
+* strings (double or single quoted)
+
+Furthermore you can use the following comparison operators:
+* +=+, +!=+, +<+, +<=+, +>=+
+* +BEGINS_WITH+ (checks if a string starts with another one)
+* +ENDS_WITH+ (checks if a string ends with another one)
+* +CONTAINS+ (checks if a string contains another one, or if an object is in an array)
+* +MATCHES+ (checks if a string is matched by a regexp, you will have to use a parameter to insert the regexp)
+* +ANY+ (checks if the thing on its left is contained in the array on its right, you will have to use a parameter to insert the array)
+* +TYPE_IS+ (unary operator expecting a string containing the name of a Model class on its right side, only records of this type will match)
+
+
+h5. Sorting
+
+To obtain ordered query results you can simply add the +orderBy+ option to your query:
+
+<javascript>
+query = SC.Query.local(MyApp.Contacts, {
+ conditions: 'age > 30',
+ orderBy: 'ASC lastName, firstName'
+});
+</javascript>
+
+In this case the results are sorted in an ascending order first by last name and second by first name. If you omit the +ASC+ keyword, the results are by default sorted in an ascending order. To sort them in descending order, begin the +orderBy+ string with the keyword +DESC+.
+
+NOTE: If you need a custom sorting order, you can register your own comparison operator for a specific model attribute using +SC.Query.registerComparison+. Please refer to the "documentation":http://docs.sproutcore.com/symbols/SC.Query.html#SC.Query%23registerComparison for further details.
+
+
+h5. Scoped Queries
+
+All the queries you used until now will cause the store to match all records in memory with the query's conditions. You can also build one query on top of another to construct more efficient query graphs:
+
+<javascript>
+query1 = SC.Query.local(MyApp.Contacts, {
+ conditions: 'age > 30',
+});
+aboveThirty = MyApp.store.find(query1);
+
+query2 = SC.Query.local(MyApp.Contacts, {
+ conditions: 'lastName BEGINS_WITH "K"'
+})
+results = aboveThirty.find(query2);
+</javascript>
+
+The second query is based on the first one by calling find on the +RecordArray+ of the first query insead of on +MyApp.store+. The second query matches the results of the first query against its own conditions. In this case it would return all +Contact+ records where +age+ is greater than 30 and the last name start with the letter "K".
+
+
+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 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.
+
+WARNING: You should use local queries in almost all cases. If you use remote queries you probably know what you're doing and have already passed the scope of this guide...
+
+
+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:
+
+<javascript>
+SC.Query.registerQueryExtension('BITAND', {
+ reservedWord: true,
+ leftType: 'PRIMITIVE',
+ rightType: 'PRIMITIVE',
+ evalType: 'BOOLEAN',
+
+ evaluate: function (r,w) {
+ var left = this.leftSide.evaluate(r,w);
+ var right = this.rightSide.evaluate(r,w);
+ return ((left & right) !== 0);
+ }
+});
+</javascript>
+
+We call +SC.Query.registerQueryExtension+ to register the new operator with the name +BITAND+ as first argument. The key components of the hash passed as second argument are +evalType+ and +evaluate+. +evalType+ is either +BOOLEAN+ (if you return a boolean value in +evaluate+) or +PRIMITIVE+ (if you return e.g. a number or a string). The actual operation is implemented in the +evaluate+ function after the arguments are retrieved by +this.leftSide.evaluate(r,w)+ and +this.rightSide.evaluate(r,w)+.
+
+NOTE: Look at the source of SC.Query ("datastore/system/query.js") for more examples of how to implement query operators.
+
+
+h3. In-Depth Information about Record States
+
+We have covered the primary record states and the occurrence of some of the possible substates above. If you want to gain a complete understanding of what record states exist and what they mean, you will find a short description of all states below as well as complete state transition diagrams.
+
+h4. READY Substates
+
++SC.Record.READY_NEW+: A record in this state was created in memory but has not yet been committed back to the server. If you commit changes on your store, this record will be sent to the data source automatically. Likewise if you destroy this record it will simply be removed from memory without notifying the server. The data source also cannot modify records while they are in this state since they have local changes that would be lost.
+
++SC.Record.READY_CLEAN+: This is the default state of a record once it has been loaded from the server or its changes were committed. A record in this state exists on the server and has not been modified locally. If you commit changes on your store, this record will NOT be included. Likewise, if your data source receives a notification from the server that this record has changed, it can modify this record without any kind of error since no changes would be lost.
+
++SC.Record.READY_DIRTY+: This is the state of a record that exists on the server but has since been modified in your SproutCore application. It has pending changes that will be sent to the server when you commit changes in your store. If your data source tries to modify this record, it will throw an error since local changes would be lost.
+
+!images/records/ready_substates.png!
+
+
+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_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_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_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!
+
+
+h4. DESTROYED Substates
+
++SC.Record.DESTROYED_CLEAN+: A record in this state has been destroyed both in the store and on the server. A record enters this state, for example, if your data source destroys the record or if you destroy the record in memory then commit that change successfully to the server.
+
++SC.Record.DESTROYED_DIRTY+: A record in this state was destroyed in the store by the application, but it has not yet been destroyed on the server. A record in this state cannot accept changes from the server unless the server also puts it into a destroyed state. Committing changes on your store will ask the data source to send this destroy to the server.
+
+!images/records/destroyed_substates.png!
+
+
+
+
+h3. Changelog
+
+* February 6, 2011: initial version by "Florian Kugler":credits.html#fkugler
+
+
Please sign in to comment.
Something went wrong with that request. Please try again.