Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Adjusting style, improving text, and examples #81

Merged
merged 11 commits into from

1 participant

@ghost

As I'm walking through the guides, just taking the opportunity to improve them a bit.

@wagenet wagenet commented on the diff
source/fixtures.textile
@@ -104,16 +106,16 @@ MyApp.Employee.FIXTURES = [
{ guid: 3,
firstName: "Juan",
lastName: "Pinzon",
- company: 2 } // Apple
+ company: 2 } // Apple, Inc.
@wagenet
wagenet added a note

This is matched with the fixture below which specifies the name only as "Apple" if you want to make this change, you should also change that to "Apple, Inc."

@ghost
ghost added a note

Sorry, missed that. Just fixed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@wagenet wagenet merged commit d7da1b5 into sproutcore:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
21 README.md
@@ -1,25 +1,26 @@
-SproutGuides
-============
+SproutCore Guides
+=================
-Documentation for the SproutCore framework. For more information on SproutCore,
-see the [homepage](http://www.sproutcore.com) or the [GitHub repository](https://github.com/sproutcore/sproutcore).
-These guides are based on the [Rails Guides](http://guides.rubyonrails.org/).
+This is the official documentation for the [SproutCore](http://www.sproutcore.com) framework.
+Go to [SproutCore Guides](http://guides.sproutcore.com) homepage and check it out.
## Getting Started
-In order to start generating documentation, complete the following steps:
+In order to start contributing, follow these steps:
-- install the [SproutGuides package](http://guides-pkg.strobeapp.com/Guides.pkg)
-- clone SproutGuides from git://github.com/sproutcore/sproutguides.git
+- git clone git://github.com/sproutcore/sproutguides.git
+- install the [Guides package](http://guides-pkg.strobeapp.com/Guides.pkg)
Once you've completed these steps, you're ready to start working with
-SproutGuides. You'll do your work inside the source/ directory. To see
+SproutCore Guides. You'll do most of your work inside the source/ directory. To see
your changes as you work, run `guides preview`. By default, preview will
show guides and content that is still under construction. Under
construction content is not displayed on guides.sproutcore.com, if you
would like to see what content will be deployed run
`guides preview --production`.
+For more information on SproutCore Guides, see the [homepage](http://guides.sproutcore.com/contribute.html).
+
If you have any questions, the team can be reached at [@sproutcore](http://twitter.com/#!/sproutcore)
-or [#sproutcore](irc://irc.freenode.net/sproutcore)
+or [#sproutcore](irc://irc.freenode.net/sproutcore)
View
99 source/fixtures.textile
@@ -1,17 +1,17 @@
h2. Using Fixtures
-This guide covers the use of fixtures in your app. By referring to this guide, you will be able to:
+This guide covers the use of fixtures in your application. By referring to this guide, you will be able to:
-* Understand the purpose of fixtures
-* Write your own fixtures
-* Set up relationships within fixtures
-* Use the fixtures data source
+* Understand the purpose of fixtures.
+* Write your own fixtures.
+* Set up relationships within fixtures.
+* Use the fixtures data source.
endprologue.
h3. The Purpose of Fixtures
-Fixtures are a quick and easy way to preload your app with sample data without having to write a custom data source. This is especially useful in the early stages of app development as it allows you to focus on the general app structure without having to be concerned with your app's backend.
+Fixtures are a quick and easy way to preload your application with sample data without having to write a custom data source. This is especially useful in the early stages of development as it allows you to focus on the general structure without having to be concerned with your application's backend.
h4. How Fixtures Work
@@ -19,8 +19,8 @@ Fixtures are defined as an array of hashes with each hash containing the attribu
A sample fixture looks something like:
-<javascript filename="apps/my_app/fixtures/my_model.js">
-MyApp.MyModel.FIXTURES = [
+<javascript filename="apps/app/fixtures/my_model.js">
+App.MyModel.FIXTURES = [
{ guid: 1,
firstName: "Michael",
lastName: "Scott" },
@@ -33,25 +33,25 @@ MyApp.MyModel.FIXTURES = [
h4. Defining Fixtures
-SproutCore looks for fixtures assigned to +MyApp.MyModel.FIXTURES+. By convention, fixtures are defined in +my_app/fixtures/my_model.js+. By default fixtures are not included in production builds.
+SproutCore looks for fixtures assigned specifically to a model (i.e. +App.MyModel.FIXTURES+ on the above example). By convention, fixtures are defined in the +app/fixtures+ folder of your application (i.e. +app/fixtures/my_model.js+). By default fixtures are not included in production builds.
NOTE: If you use +sc-gen+ to create your models then you may have noticed that some placeholder fixtures already exist. You will still need to modify these fixtures to suit your needs. Read on to learn how.
h3. Writing Your Own Fixtures
-Writing fixtures is relatively straight forward. As you can see in the above example, each item gets its own hash in the format of +propertyName: value+. If your model looks like
+Writing fixtures is relatively straightforward. Each item representing a record gets its own hash in the format of +propertyName: value+. If your model looks like:
-<javascript filename="apps/my_app/models/article.js">
-MyApp.Article = SC.Record.extend({
+<javascript filename="apps/app/models/article.js">
+App.Article = SC.Record.extend({
title: SC.Record.attr(String),
body: SC.Record.attr(String)
});
</javascript>
-then your fixture will look like
+Your fixture will look like:
-<javascript filename="apps/my_app/fixtures/article.js">
-MyApp.Article.FIXTURES = [
+<javascript filename="apps/app/fixtures/article.js">
+App.Article.FIXTURES = [
{ guid: 1,
title: "Writing a SproutCore app",
body: "Writing a SproutCore app is fun and exciting!" },
@@ -68,29 +68,31 @@ WARNING: You must specify a value for the +primaryKey+ in your fixtures.
h4. Defining Relationships
-Since relationships are only specified with the foreign key it is quite easy to set them up in your fixtures. If you have model declaration like the following
+Since relationships are only specified with the foreign key it is quite easy to set them up in your fixtures. If you have model declaration, like the following, where an employee belongs to a company and a company has many employees:
-<javascript filename="apps/my_app/models/employee.js">
-MyApp.Employee = SC.Record.extend({
+<javascript filename="apps/app/models/employee.js">
+App.Employee = SC.Record.extend({
firstName: SC.Record.attr(String),
lastName: SC.Record.attr(String),
- company: SC.Record.toOne("MyApp.Company", {
- inverse: "contacts", isMaster: NO
+ company: SC.Record.toOne("App.Company", {
+ inverse: "contacts",
+ isMaster: NO
})
});
-MyApp.Company = SC.Record.extend({
+App.Company = SC.Record.extend({
name: SC.Record.attr(String),
- employees: SC.Record.toMany("MyApp.Employee", {
- inverse: "group", isMaster: YES
+ employees: SC.Record.toMany("App.Employee", {
+ inverse: "group",
+ isMaster: YES
})
});
</javascript>
-then you would set up your fixtures like
+You would set up your fixtures like:
-<javascript filename="apps/my_app/fixtures/employee.js">
-MyApp.Employee.FIXTURES = [
+<javascript filename="apps/app/fixtures/employee.js">
+App.Employee.FIXTURES = [
{ guid: 1,
firstName: "Peter",
lastName: "Wagenet",
@@ -104,16 +106,16 @@ MyApp.Employee.FIXTURES = [
{ guid: 3,
firstName: "Juan",
lastName: "Pinzon",
- company: 2 } // Apple
+ company: 2 } // Apple, Inc.
@wagenet
wagenet added a note

This is matched with the fixture below which specifies the name only as "Apple" if you want to make this change, you should also change that to "Apple, Inc."

@ghost
ghost added a note

Sorry, missed that. Just fixed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
];
-MyApp.Company.FIXTURES = [
+App.Company.FIXTURES = [
{ guid: 1, name: "Strobe, Inc.", employees: [1,2] },
- { guid: 2, name: "Apple", employees: [3] }
+ { guid: 2, name: "Apple, Inc.", employees: [3] }
];
</javascript>
-When the fixtures are loaded into your app the relationships automatically take over and point to the appropriate records.
+When the fixtures are loaded into your application the relationships automatically take over and point to the appropriate records.
WARNING: Unlike a traditional relational database it is necessary to specify both sides of the relationship.
@@ -121,49 +123,44 @@ TIP: When defining the 'many' side of the fixture record, be certain to include
h4. Defining Non-String Values
-The above examples have referred mostly to string values in the fixture. As most data is either passed into the app via JSON or XML, +SC.Record+ is capable of transforming data from a string into the appropriate object type. See +SC.RecordAttribute.registerTransform+ for more information.
+The above examples have referred mostly to string values in the fixture. As most data is either passed into the application via "JSON":http://www.json.org or "XML":http://www.w3.org/XML/, +SC.Record+ is capable of transforming data from a string into the appropriate object type. See +SC.RecordAttribute.registerTransform+ for more information.
h3. Hooking Up Your Fixtures
-SproutCore comes with a built in data source for your fixtures. To use the fixtures data source you merely need to set the following in your app's +core.js+
-
-<javascript filename="in apps/my_app/core.js">
-MyApp = SC.Application.create({
- // ...
+SproutCore comes with a built-in data source for your fixtures. To use the fixtures data source you merely need to set the following in your application's +core.js+.
+<javascript filename="in apps/app/core.js">
+App = SC.Application.create({
store: SC.Store.create().from(SC.Record.fixtures)
-
- // ...
});
</javascript>
-You can extend the built in fixture data source to simulate real world performance (important in managing user expectations).
+You can extend the built-in fixture data source to simulate real world performance (important in managing user expectations).
+
<shell>
-$ sc-gen data-source MyApp.MyAppsFixturesDataSource SC.FixturesDataSource
+$ sc-gen data-source App.AppsFixturesDataSource SC.FixturesDataSource
</shell>
Delete the boilerplate code in the fixture data source so your code looks like:
-<javascript filename="apps/my_app/data_sources/my_apps_fixtures.js">
-MyApp.MyAppsFixturesDataSource = SC.FixturesDataSource.extend({
+
+<javascript filename="apps/app/data_sources/apps_fixtures.js">
+App.AppsFixturesDataSource = SC.FixturesDataSource.extend({
simulateRemoteResponse: YES,
- latency: 500 // 500 mS latency
+ latency: 500 // 500 ms latency
});
</javascript>
-This simulates a remote response with 500 milliseconds round trip latency. Tweak the value to match the performance of your network and servers.
+This simulates a remote response with 500 milliseconds round trip latency. Tweak the value to match the performance of your network and servers.
Next, point your store to the new fixtures data source.
-<javascript filename="in apps/my_app/core.js">
-MyApp = SC.Application.create({
- // ...
-
- store: SC.Store.create().from('MyApp.MyAppsFixturesDataSource')
- // ...
+<javascript filename="in apps/app/core.js">
+App = SC.Application.create({
+ store: SC.Store.create().from('App.AppsFixturesDataSource')
});
</javascript>
-TIP: Notice the quotes around the data source name? +MyApp.MyAppsFixturesDataSource+ doesn't exist until its instantiated during the loading process.
+TIP: Notice the quotes around the data source name? +App.AppsFixturesDataSource+ doesn't exist until its instantiated during the loading process.
h3. Changelog
View
336 source/records.textile
@@ -1,17 +1,15 @@
-h2. Using Records and the Data Store
+h2. SproutCore Records
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
+* 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 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.
@@ -20,18 +18,16 @@ The model layer is also responsible for talking to your server, fetching and com
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.
-
-
-
+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
+
+* 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 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.
@@ -41,9 +37,9 @@ Each record has a unique store key which is assigned when the record is created.
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.
-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.
+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.
-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.
+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
@@ -58,36 +54,32 @@ The names of these states are pretty self explanatory: EMPTY indicates a non exi
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.
-
-
-
-
-h3. Defining your models
+h3. Defining Your Models
Defining a model in SproutCore is as easy as subclassing +SC.Record+:
-<javascript filename="apps/my_app/models/contact.js">
-MyApp.Contact = SC.Record.extend({
-
+<javascript filename="apps/app/models/contact.js">
+App.Contact = SC.Record.extend({
+
});
</javascript>
-You just have created your custom +MyApp.Contact+ model class. However, this empty model is only of limited use, so let's add some record attributes.
+You just have created your custom +App.Contact+ model class. However, this empty model is only of limited use, so let's add some record attributes.
-<javascript filename="apps/my_app/models/contact.js">
-MyApp.Contact = SC.Record.extend({
+<javascript filename="apps/app/models/contact.js">
+App.Contact = SC.Record.extend({
firstName: SC.Record.attr(String),
lastName: SC.Record.attr(String),
age: SC.Record.attr(Number)
});
</javascript>
-WARNING: Property names defined on +SC.Record+ itself 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 attribtutes. 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:
-<javascript filename="apps/my_app/models/contact.js">
-MyApp.Contact = SC.Record.extend({
+<javascript filename="apps/app/models/contact.js">
+App.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 })
@@ -100,133 +92,117 @@ NOTE: The +defaultValue+ will not be written to the underlying data hash and the
If the name of the model's attribute property differs from the name you want to use in the data hash, you can specify a custom key for each attribute which will be used to access the data hash:
-<javascript filename="apps/my_app/models/contact.js">
-MyApp.Contact = SC.Record.extend({
+<javascript filename="apps/app/models/contact.js">
+App.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. Attribute 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 filename="in apps/my_app/models/contact.js">
-MyApp.Contact = SC.Record.extend({
- // ...
+Additionally SproutCore comes with a predefined attribute helper for date and time values.
+<javascript filename="in apps/app/models/contact.js">
+App.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 "SC.DateTime#toFormatedString":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.
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 filename="in apps/my_app/models/contact.js">
-MyApp.Contact = SC.Record.extend({
- primaryKey: 'uid',
-
- // ...
+<javascript filename="in apps/app/models/contact.js">
+App.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.
+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. And if you do so, you should know what you are doing and probably not need this guide...
+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.
-
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:
-<javascript filename="apps/my_app/models/address.js">
-MyApp.Address = SC.Record.extend({
+<javascript filename="apps/app/models/address.js">
+App.Address = SC.Record.extend({
street: SC.Record.attr(String),
- number: SC.Record.attr(Number),
+ 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 filename="in apps/my_app/models/contact.js">
-MyApp.Contact = SC.Record.extend({
- // ...
-
+<javascript filename="in apps/app/models/contact.js">
+App.Contact = SC.Record.extend({
address: SC.Record.toOne(
- 'MyApp.Address',
+ 'App.Address',
{ isMaster: YES, inverse: 'contact' }
)
});
</javascript>
-<javascript filename="in apps/my_app/models/address.js">
-MyApp.Address = SC.Record.extend({
- // ...
-
+<javascript filename="in apps/app/models/address.js">
+App.Address = SC.Record.extend({
contact: SC.Record.toOne(
- 'MyApp.Contact',
+ 'App.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 you alter the relation.
+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.
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.
-NOTE: It is not obligatory to define both directions of the relation if you don't need it.
+NOTE: It is not mandatory to define both directions of the relation if you don't need it.
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 filename="in apps/my_app/models/contact.js">
-MyApp.Contact = SC.Record.extend({
- // ...
-
+<javascript filename="in apps/app/models/contact.js">
+App.Contact = SC.Record.extend({
address: SC.Record.toMany(
- 'MyApp.Address',
+ 'App.Address',
{ isMaster: YES, inverse: 'contact' }
)
});
</javascript>
-<javascript filename="in apps/my_app/models/address.js">
-MyApp.Address = SC.Record.extend({
- // ...
-
+<javascript filename="in apps/app/models/address.js">
+App.Address = SC.Record.extend({
contact: SC.Record.toOne(
- 'MyApp.Contact',
+ 'App.Contact',
{ isMaster: NO }
)
});
@@ -236,27 +212,25 @@ The only thing that changed compared to the one-to-one example above is the +toM
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.
+
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 filename="in apps/my_app/models/contact.js">
-MyApp.Contact = SC.Record.extend({
- // ...
-
+<javascript filename="in apps/app/models/contact.js">
+App.Contact = SC.Record.extend({
address: SC.Record.toMany(
- 'MyApp.Address',
+ 'App.Address',
{ isMaster: YES, inverse: 'contact' }
)
});
</javascript>
-<javascript filename="in apps/my_app/models/address.js">
-MyApp.Address = SC.Record.extend({
- // ...
-
+<javascript filename="in apps/app/models/address.js">
+App.Address = SC.Record.extend({
contact: SC.Record.toMany(
- 'MyApp.Contact',
+ 'App.Contact',
{ isMaster: NO }
)
});
@@ -266,55 +240,51 @@ Again the only thing that changed compared to the one-to-many example from above
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.
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 filename="in apps/my_app/models/contact.js">
-MyApp.Contact = SC.Record.extend({
+<javascript filename="in apps/app/models/contact.js">
+App.Contact = SC.Record.extend({
// transient property
isContact: YES,
// transient computed property
fullName: function() {
return this.get('firstName') + ' ' + this.get('lastName');
- }.property('firstName', 'lastName').cacheable(),
-
- // ...
+ }.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.
+NOTE: If you use the +set+ method 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 "core.js" file:
-
-<javascript filename="in apps/my_app/core.js">
-MyApp = SC.Application.create({
- // ...
+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 <code>core.js</code> file:
- store: SC.Store.create().from(SC.Record.fixtures()),
+<javascript filename="in apps/app/core.js">
+App = SC.Application.create({
+ store: SC.Store.create().from(SC.Record.fixtures())
});
</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.
-
h4. Creating Records
You can create records of a previously defined record type like this:
<javascript>
-myContact = MyApp.store.createRecord(MyApp.Contact, {});
+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:
<javascript>
-myContact = MyApp.store.createRecord(
- MyApp.Contact,
+contact = App.store.createRecord(
+ App.Contact,
{ firstName: 'Florian', lastName: 'Kugler' },
99
);
@@ -323,8 +293,8 @@ myContact = MyApp.store.createRecord(
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,
+contact = App.store.createRecord(
+ App.Contact,
{ firstName: 'Florian', lastName: 'Kugler' },
- Math.random(Math.floor(Math.random() * 99999999))
);
@@ -339,65 +309,63 @@ 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);
+contact = App.store.createRecord(App.Contact, {/*...*/}, 1);
+address = App.store.createRecord(App.Address, {/*...*/}, 1);
// for a toOne relation
-myContact.set('address', myAddress);
+contact.set('address', address);
-// example for a toMany relation
-myContact.get('address').pushObject(myAddress);
+// for a toMany relation
+contact.get('address').pushObject(address);
</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.
-
+In this case we're adding the +Address+ record to the +Contact+ record, because +Contact+ is defined as master in this relation 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 record attributes is as easy as calling +set()+:
+Updating record attributes is as easy as calling the +set+ method:
<javascript>
-myContact.set('firstName', 'Jack');
+contact.set('firstName', 'Jack');
</javascript>
-In order to be able to update record attributes the record has to be in a +READY+ state. If you update an attribute of a newly created record (which starts out as +READY_NEW+), 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+. Dirty states always indicate that the record needs to be committed back to the server.
+In order to be able to update record attributes the record has to be in a +READY+ state. If you update 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+.
+NOTE: Dirty states always indicate that the record needs to be committed back to the server.
h4. Destroying Records
-To delete a certain record, just call +destroy()+ on it:
+To delete a certain record, just call the +destroy+ method on it:
<javascript>
-myContact.destroy();
+contact.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+ (or +READY_DIRTY+ if you changed it before) to +DESTROYED_DIRTY+, indicating that the server needs to be notified about this destroy action.
-
-
+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.
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:
+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 = myContact.get('id');
-storeKey = myContact.get('storeKey');
-status = myContact.get('status');
+id = contact.get('id');
+storeKey = contact.get('storeKey');
+status = contact.get('status');
</javascript>
To test if the record is currently in a certain state, use JavaScript's binary operators:
<javascript>
-status = myContact.get('status');
+status = contact.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>
@@ -412,10 +380,10 @@ 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);
+contact = App.store.find(App.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+.
+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.
@@ -424,21 +392,21 @@ 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);
+contacts = App.store.find(App.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]
+contactsAndAddresses = App.store.find(
+ [App.Contact, App.Address]
);
</javascript>
You can also find all records of a certain type and all its subclasses:
<javascript>
-allRecords = MyApp.store.find(SC.Record);
+allRecords = App.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.
@@ -448,64 +416,67 @@ Internally +find+ converts the specified record types to a query, it's just a co
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. 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);
+query = SC.Query.local(App.Contact);
+contacts = App.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([App.Contact, App.Address]);
+contactsAndAddresses = App.store.find(query);
query = SC.Query.local(SC.Record);
-allRecords = MyApp.store.find(query);
+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:
<javascript>
-myContacts.firstObject(); // returns first result
-myContacts.objectAt(3); // returns fourth result
-myContacts.lastObject(); // returns last result
+contacts.firstObject(); // returns first result
+contacts.objectAt(3); // returns fourth result
+contacts.lastObject(); // returns last result
</javascript>
Please refer to the "documentation of SC.Array":http://docs.sproutcore.com/symbols/SC.Array.html to learn more 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.
+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.
h5. Conditions
You can limit the results of a query to match certain conditions:
<javascript>
-query = SC.Query.local(MyApp.Contacts, {
+query = SC.Query.local(App.Contacts, {
conditions: 'firstName = "Florian"'
});
-results = MyApp.store.find(query);
+
+results = App.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 parentheses +(+ and +)+ for grouping:
+The above query returns all records of type +App.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 parentheses +(+ and +)+ for grouping:
<javascript>
-query = SC.Query.local(MyApp.Contacts, {
+query = SC.Query.local(App.Contacts, {
conditions: 'firstName = "Florian" AND lastName = "Kugler"'
});
-query = SC.Query.local(MyApp.Contacts, {
+query = SC.Query.local(App.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 named parameters. Lets rephrase the above query using sequential parameters:
+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 named parameters. Lets rephrase the above query using sequential parameters:
<javascript>
-query = SC.Query.local(MyApp.Contacts, {
+query = SC.Query.local(App.Contacts, {
conditions: '(firstName = %@ AND lastName = %@) OR age > %@',
parameters: ['Florian', 'Kugler', 30]
});
@@ -514,13 +485,12 @@ query = SC.Query.local(MyApp.Contacts, {
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}',
+query = SC.Query.local(App.Contacts, {
+ conditions: '(firstName = {first} AND lastName = {last}) ' + 'OR age > {age}',
parameters: {
- first: 'Florian',
- last: 'Kugler',
- age: 30
+ first: 'Florian',
+ last: 'Kugler',
+ age: 30
}
});
</javascript>
@@ -528,28 +498,29 @@ query = SC.Query.local(MyApp.Contacts, {
Which of these methods you use is mainly a matter of personal preference and the complexity of your query.
The arguments 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)
+
+* Attribute names of the record type queried for.
+* +null+ and +undefined+.
+* +true+ and +false+.
+* Integer and floating point numbers.
+* Strings (single or double 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)
+* +=+, +!=+, +<+, +<=+, +>=+.
+* +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, {
+query = SC.Query.local(App.Contacts, {
conditions: 'age > 30',
orderBy: 'lastName, firstName ASC'
});
@@ -564,27 +535,30 @@ 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 trees:
<javascript>
-query1 = SC.Query.local(MyApp.Contacts, {
+query1 = SC.Query.local(App.Contacts, {
conditions: 'age > 30',
});
-aboveThirty = MyApp.store.find(query1);
-query2 = SC.Query.local(MyApp.Contacts, {
+aboveThirty = App.store.find(query1);
+
+query2 = SC.Query.local(App.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 instead of +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 starts with the letter "K".
+The second query is based on the first one by calling +find+ on the +RecordArray+ of the first query instead of +App.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 starts with the letter "K".
+NOTE: Scope queries can be thought as chained queries using the AND logical operator.
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. 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...
+WARNING: You should use local queries in almost all cases unless you know what you're doing.
h4. Extending SproutCore's Query Language
@@ -607,7 +581,7 @@ SC.Query.registerQueryExtension('BITAND', {
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 operands 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.
+NOTE: Look at the "source of SC.Query":https://github.com/sproutcore/sproutcore/blob/master/frameworks/datastore/system/query.js for more examples of how to implement query operators.
h3. In-Depth Information about Record States
@@ -615,7 +589,7 @@ We have covered the primary record states and the occurrence of some of the poss
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_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.
@@ -623,7 +597,6 @@ h4. READY Substates
!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.
@@ -640,7 +613,6 @@ h4. BUSY Substates
!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.
@@ -649,12 +621,8 @@ h4. DESTROYED Substates
!images/records/destroyed_substates.png!
-
-
-
h3. Changelog
* February 6, 2011: initial version by "Florian Kugler":credits.html#fkugler
* March 2, 2011: added filenames and small fixes by "Topher Fangio":credits.html#topherfangio
-* March 2, 2011: minor corrections by "Florian Kugler":credits.html#fkugler
-
+* March 2, 2011: minor corrections by "Florian Kugler":credits.html#fkugler
View
10 source/views.textile
@@ -13,7 +13,7 @@ h3. Introduction
After you've created your first SproutCore project, you'll notice the file +main_page.js+ in your resources folder. This is the main page that your app appends to the DOM in your +main+ function. Let's start by looking at what every SproutCore project starts with as its main page:
-INFO: The file +main_page.js+ is included into the application's resources folder when using <code>sc-init</code> without the <code>--template</cpde> param.
+INFO: The file +main_page.js+ is included into the application's resources folder when using <code>sc-init</code> without the <code>--template</code> param.
<javascript filename="apps/app/resources/main_page.js">
App.mainPage = SC.Page.design({
@@ -149,13 +149,13 @@ Because we are only going to be working with one object at a time, +allowsMultip
And then an +SC.ObjectController+ that proxies individual contact objects:
-<javascript filename="apps/my_app/controllers/contact.js">
-MyApp.contactController = SC.ObjectController.create({
- contentBinding: 'MyApp.contactsController.selection'
+<javascript filename="apps/app/controllers/contact.js">
+App.contactController = SC.ObjectController.create({
+ contentBinding: 'App.contactsController.selection'
});
</javascript>
-NOTE: An object controller will automatically detect if you have a single selection and will automatically set it's content to the only item in the selection set. If you are in the rare case of needing to force a single selection, you can use +SC.Binding.single('MyApp.contactsController.selection')+.
+NOTE: An object controller will automatically detect if you have a single selection and will automatically set it's content to the only item in the selection set. If you are in the rare case of needing to force a single selection, you can use +SC.Binding.single('App.contactsController.selection')+.
Now we can bind our source list to the +SC.ArrayController+ to get a dynamic list of contacts and individual contact selection support:
Something went wrong with that request. Please try again.