Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 994 lines (772 sloc) 27.974 kb
d2b9c6a Update README.
Tom Dale authored
1 ## Ember Data
82d7467 Initial import of data store library
Tom Dale authored
2
d2b9c6a Update README.
Tom Dale authored
3 Ember Data is a library for loading models from a persistence layer (such as
4 a JSON API), updating those models, then saving the changes. It provides many
5 of the facilities you'd find in server-side ORMs like ActiveRecord, but is
6 designed specifically for the unique environment of JavaScript in the browser.
7
8 This release is definitely alpha-quality. The basics work, but there are for
9 sure edge cases that are not yet handled. Please report any bugs or feature
10 requests, and pull requests are always welcome.
11
09b146c WIP on associations documentation
Tom Dale authored
12 #### Is It Good?
d2b9c6a Update README.
Tom Dale authored
13
14 Yes.
15
09b146c WIP on associations documentation
Tom Dale authored
16 #### Is It "Production Ready™"?
d2b9c6a Update README.
Tom Dale authored
17
7985150 Include list of breaking API changes
tomhuda authored
18 No. Breaking changes, indexed by date, are listed in
19 [`BREAKING_CHANGES.md`](https://github.com/emberjs/data/blob/master/BREAKING_CHANGES.md).
d2b9c6a Update README.
Tom Dale authored
20
09b146c WIP on associations documentation
Tom Dale authored
21 #### Roadmap
d2b9c6a Update README.
Tom Dale authored
22
23 * Handle error states
24 * Better built-in attributes
db39991 @tomdale Update roadmap
tomdale authored
25 * Editing "forked" records
b904f9e @trevor small README changes
trevor authored
26 * Out-of-the-box support for Rails apps that follow the
27 [`active_model_serializers`](https://github.com/josevalim/active_model_serializers) gem's conventions.
4ce451f @tomdale Update README to clarify REST adapter support and add items to roadmap
tomdale authored
28 * Handle partially-loaded records
d2b9c6a Update README.
Tom Dale authored
29
30 ### Creating a Store
31
32 Every application has one or more stores. The store will be the repository
33 that holds loaded models, and is responsible for retrieving models that have
34 not yet been loaded.
35
36 ```javascript
07cca37 The 'integer' attribute should be called 'number'
tomhuda authored
37 App.store = DS.Store.create({
5f82309 @joliss Bump revision in README
joliss authored
38 revision: 4
07cca37 The 'integer' attribute should be called 'number'
tomhuda authored
39 });
d2b9c6a Update README.
Tom Dale authored
40 ```
07cca37 The 'integer' attribute should be called 'number'
tomhuda authored
41
42 > NOTE: The revision property is used by `ember-data` to notify you of
43 > breaking changes to the public API before 1.0. For new applications,
44 > just set the revision to this number. See
b904f9e @trevor small README changes
trevor authored
45 > [BREAKING_CHANGES.md](https://github.com/emberjs/data/blob/master/BREAKING_CHANGES.md)
07cca37 The 'integer' attribute should be called 'number'
tomhuda authored
46 > for more information.
b904f9e @trevor small README changes
trevor authored
47
d2b9c6a Update README.
Tom Dale authored
48 You can tell the store how to talk to your backend by specifying an *adapter*.
20b1fda @arailsdemo Update README.md
arailsdemo authored
49 Ember Data comes with a RESTful JSON API adapter. You can specify this adapter
50 by setting the `adapter` property:
d2b9c6a Update README.
Tom Dale authored
51
52 ```javascript
53 App.store = DS.Store.create({
5f82309 @joliss Bump revision in README
joliss authored
54 revision: 4,
07cca37 The 'integer' attribute should be called 'number'
tomhuda authored
55 adapter: DS.RESTAdapter.create({ bulkCommit: false })
d2b9c6a Update README.
Tom Dale authored
56 });
57 ```
58
9f5f940 @joliss Document namespace option of RESTAdapter (added in 95d938ef)
joliss authored
59 The `RESTAdapter` supports the following options:
60
47613a2 @frodsan bulk commit default: false
frodsan authored
61 * `bulkCommit` (default: false): If your REST API does not support bulk
9f5f940 @joliss Document namespace option of RESTAdapter (added in 95d938ef)
joliss authored
62 operations, you can turn them off by setting `bulkCommit` to false.
63
64 * `namespace` (default: undefined): A leading URL component under which all
65 REST URLs reside, without leading slash, e.g. `api` (for
66 `/api/authors/1`-type URLs).
4ce451f @tomdale Update README to clarify REST adapter support and add items to roadmap
tomdale authored
67
68 The RESTful adapter is still in progress. For Rails applications, we plan to make
69 it work seamlessly with the `active_model_serializers` gem's conventions. In
d2b9c6a Update README.
Tom Dale authored
70 the meantime, see the section on rolling your own adapter.
71
72 ### Defining Models
73
74 For every type of data you'd like to represent, create a new subclass of
75 `DS.Model`:
76
77 ```javascript
78 App.Person = DS.Model.extend();
79 ```
80
81 You can specify which attributes a model has by using `DS.attr`. An attribute
82 represents a value that will exist in the underlying JSON representation of
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
83 the object which you'd also want to expose through the Ember object.
d2b9c6a Update README.
Tom Dale authored
84
85 You can use attributes just like any other property, including as part of a
86 computed property. These attributes ensure that the values can be retrieved
87 from the underlying JSON representation and persisted later as needed.
88
89 ```javascript
90 App.Person = DS.Model.extend({
91 firstName: DS.attr('string'),
92 lastName: DS.attr('string'),
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
93 birthday: DS.attr('date'),
d2b9c6a Update README.
Tom Dale authored
94
95 fullName: function() {
96 return this.get('firstName') + ' ' + this.get('lastName');
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
97 }.property('firstName', 'lastName')
d2b9c6a Update README.
Tom Dale authored
98 });
99 ```
100
b904f9e @trevor small README changes
trevor authored
101 Valid attribute types are `string`, `number`, `boolean`, and `date`. You
d2b9c6a Update README.
Tom Dale authored
102 can also register custom attribute types. For example, here's a `boolString`
103 attribute type that converts booleans into the string `"Y"` or `"N"`:
104
105 ```javascript
22d702b Fix code example
sandstrom authored
106 DS.attr.transforms.boolString = {
d2b9c6a Update README.
Tom Dale authored
107 from: function(serialized) {
108 if (serialized === 'Y') {
109 return true;
110 }
b904f9e @trevor small README changes
trevor authored
111
d2b9c6a Update README.
Tom Dale authored
112 return false;
113 },
114
115 to: function(deserialized) {
116 if (deserialized) {
117 return "Y";
118 }
119 return "N";
120 }
121 }
122 ```
123
124 Built-in attribute types are currently very primitive. Please help us
125 flesh them out with patches and unit tests!
126
127 By default, the store uses a model's `id` attribute as its primary key.
128 You can specify a different key by setting the `primaryKey` property:
129
130 ```javascript
131 DS.Model.extend({
132 primaryKey: 'guid'
133 });
134 ```
135
136 ### Associations
137
09b146c WIP on associations documentation
Tom Dale authored
138 Models can be associated with other models. Ember Data includes several
139 built-in types to help you define how your models relate to each other.
140
141 #### Has One
142
143 A `hasOne` association declares that a model is associated with exactly
144 one other model. For example, imagine we're writing a blog application that
145 allows authors to post entries. Each author has a profile associated with
146 their account that is displayed when visitors want to learn more about them.
d2b9c6a Update README.
Tom Dale authored
147
148 ```javascript
09b146c WIP on associations documentation
Tom Dale authored
149 App.Profile = DS.Model.extend({
150 about: DS.attr('string'),
151 postCount: DS.attr('number')
d2b9c6a Update README.
Tom Dale authored
152 });
153
09b146c WIP on associations documentation
Tom Dale authored
154 App.Author = DS.Model.extend({
d89ff1a Update docs to suggest best practices
tomhuda authored
155 profile: DS.hasOne('App.Profile'),
09b146c WIP on associations documentation
Tom Dale authored
156 name: DS.attr('string')
d2b9c6a Update README.
Tom Dale authored
157 });
158 ```
159
09b146c WIP on associations documentation
Tom Dale authored
160 Now, when we have an `Author` record, we can easily find its related `Profile`:
161
162 ```javascript
163 var author = App.store.find(App.Author, 1);
164 author.get('name'); // "Timothy Leary"
165 author.get('profile'); // App.Profile
166 author.getPath('profile.postCount'); // 1969
167 ```
168
169 #### Belongs To
170
171 Similar to `hasOne`, `belongsTo` sets up a one-to-one relationship from one model
172 to another. Let's revise the example above so that, in addition to being able
173 to find an author's profile, we can find the author associated with a profile:
174
175 ```javascript
176 App.Profile = DS.Model.extend({
177 about: DS.attr('string'),
178 postCount: DS.attr('number'),
179 author: DS.belongsTo('App.Author')
180 });
181
182 App.Author = DS.Model.extend({
d89ff1a Update docs to suggest best practices
tomhuda authored
183 profile: DS.hasOne('App.Profile'),
09b146c WIP on associations documentation
Tom Dale authored
184 name: DS.attr('string')
185 });
186 ```
187
188 So, when should you use `hasOne` and when should you use `belongsTo`? The difference
189 is where the information about the relationship is stored at the persistence layer.
190
191 The record with the `belongsTo` relationship will save changes to the association
192 on itself. Conversely, the record with the `hasOne` relationship asks the persistence
193 layer what record belongs to it. If the relationship changes, only the record with
194 the `belongsTo` relationship must be saved.
195
196 #### Has Many
197
198 Use the `hasMany()` method to describe a relationship where multiple models belong
199 to a single model. In our blog engine example, a single blog post may have multiple
200 comments:
201
202 ```javascript
203 App.Comment = DS.Model.extend({
204 content: DS.attr('string'),
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
205 post: DS.belongsTo('App.Post')
09b146c WIP on associations documentation
Tom Dale authored
206 });
207
208 App.Post = DS.Model.extend({
209 content: DS.attr('string'),
d89ff1a Update docs to suggest best practices
tomhuda authored
210 comments: DS.hasMany('App.Comment')
09b146c WIP on associations documentation
Tom Dale authored
211 });
212 ```
213
214 ### Representing Associations
215
216 #### One-to-One
217
218 The default REST adapter supports several different variations for
219 representing associations to best fit the needs of your application.
220 We'll examine each of the different types of associations in turn.
221
222 Let us first discuss a typical one-to-one relationship. We'll use the
223 example of the `Profile` and `Author` from above. In that example, we
224 have a `Profile` that belongs to an `Author`, and an `Author` that has
225 one `Profile`. The simplest way to represent these two records' JSON
226 structure is as follows:
227
228 ```javascript
229 // Author
230 {
b904f9e @trevor small README changes
trevor authored
231 "author": {
232 "id": 1,
233 "name": "Tom Dale"
234 }
235 }
09b146c WIP on associations documentation
Tom Dale authored
236 }
237
238 // Profile
239 {
b904f9e @trevor small README changes
trevor authored
240 "profile": {
241 "id": 1,
242 "about": "Tom Dale is a software engineer that drinks too much beer.",
243 "postCount": 1984,
244 "author_id": 1
245 }
09b146c WIP on associations documentation
Tom Dale authored
246 }
247 ```
248
249 Note that in the above example, the JSON for our `Author` does not contain
250 any information about how to find its related `Profile`. If you were to
251 request the profile, like this:
252
253 ```javascript
254 author.get('profile');
255 ```
256
257 …the REST adapter would send a request to the URL
258 `/profiles?author_id=1`. (Asking the `Profile` for its `Author` would
259 not generate an additional request, because the ID of the associated
260 `Author` is built-in to the response.)
261
262 As a performance optimization, the REST API can return the ID of the
263 `Profile` in the `Author` JSON:
264
265 ```javascript
266 // Author with included Profile id
267 {
b904f9e @trevor small README changes
trevor authored
268 "author": {
269 "id": 1,
270 "name": "Tom Dale",
271 "profile_id": 1
272 }
09b146c WIP on associations documentation
Tom Dale authored
273 }
274 ```
275
276 Now, if you ask for the author's profile, one of two things will happen.
277 If the `Profile` with that ID has already been loaded at any point
278 during the execution of the app, it will be returned immediately without
279 any additional requests. Otherwise, the REST adapter will make a request
280 to `/profile/1` to load that specific profile.
281
282 In some cases, if you know that you will always being using both records
283 in an association, you may want to minimize the number of HTTP requests
284 by including both records in the same JSON.
285
286 One option is to embed the association directly in the parent record.
287 For example, we could represent the entirety of the association above
288 like this:
d2b9c6a Update README.
Tom Dale authored
289
290 ```javascript
291 {
09b146c WIP on associations documentation
Tom Dale authored
292 "authors": [{
d2b9c6a Update README.
Tom Dale authored
293 "id": 1,
294 "name": "Tom Dale",
09b146c WIP on associations documentation
Tom Dale authored
295 "profile": {
296 "id": 1,
37444a9 @nicholaides Adding necessary quotes to example JSON.
nicholaides authored
297 "about": "Tom Dale is a software engineer that drinks too much beer.",
298 "postCount": 1984,
299 "author_id": 1
09b146c WIP on associations documentation
Tom Dale authored
300 }
301 }]
d2b9c6a Update README.
Tom Dale authored
302 }
303 ```
304
ff80b3c @joliss Document delayed loading of embedded records (#214)
joliss authored
305 If you do this, note that `Profile.find(1)` will still trigger an Ajax request
306 until you access the embedded profile record for the first time
307 (`Author.find(1).get('profile')`).
308
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
309 Another option is to use the format described above (with the ID embedded),
09b146c WIP on associations documentation
Tom Dale authored
310 then "sideloading" the records. For example, we could represent the
311 entirety of the association above like this:
d2b9c6a Update README.
Tom Dale authored
312
313 ```javascript
09b146c WIP on associations documentation
Tom Dale authored
314 {
315 "authors": [{
d2b9c6a Update README.
Tom Dale authored
316 "id": 1,
09b146c WIP on associations documentation
Tom Dale authored
317 "name": "Tom Dale",
318 "profile_id": 1
319 }],
d2b9c6a Update README.
Tom Dale authored
320
09b146c WIP on associations documentation
Tom Dale authored
321 "profiles": [{
322 "id": 1,
37444a9 @nicholaides Adding necessary quotes to example JSON.
nicholaides authored
323 "about": "Tom Dale is a software engineer that drinks too much beer.",
324 "postCount": 1984,
325 "author_id": 1
09b146c WIP on associations documentation
Tom Dale authored
326 }]
327 }
d2b9c6a Update README.
Tom Dale authored
328 ```
329
b904f9e @trevor small README changes
trevor authored
330 However, imagine the JSON returned from the server for a `Person` looked like this:
d2b9c6a Update README.
Tom Dale authored
331
332 ```javascript
333 {
b904f9e @trevor small README changes
trevor authored
334 "person": {
d2b9c6a Update README.
Tom Dale authored
335 "id": 1,
b904f9e @trevor small README changes
trevor authored
336 "name": "Tom Dale",
337 "tags": [{
338 "id": 1,
339 "name": "good-looking"
340 },
09b146c WIP on associations documentation
Tom Dale authored
341
b904f9e @trevor small README changes
trevor authored
342 {
343 "id": 2,
344 "name": "not-too-bright"
345 }]
346 }
d2b9c6a Update README.
Tom Dale authored
347 }
348 ```
349
350 In this case, instead of the association being an array of ids, it is an
351 array of *embedded* objects. To have the store understand these correctly,
352 set the `embedded` option to true:
353
354 ```javascript
355 App.Person = DS.Model.extend({
d89ff1a Update docs to suggest best practices
tomhuda authored
356 tags: DS.hasMany('App.Tag', { embedded: true })
d2b9c6a Update README.
Tom Dale authored
357 });
358 ```
359
6f9436c @heycarsten Added an example for using a custom data attribute in DS.hasMany asso…
heycarsten authored
360 It is also possible to change the data attribute that an association is mapped
b904f9e @trevor small README changes
trevor authored
361 to. Suppose the JSON for a `Person` looked like this:
6f9436c @heycarsten Added an example for using a custom data attribute in DS.hasMany asso…
heycarsten authored
362
363 ```javascript
364 {
b904f9e @trevor small README changes
trevor authored
365 "person": {
6f9436c @heycarsten Added an example for using a custom data attribute in DS.hasMany asso…
heycarsten authored
366 "id": 2,
367 "name": "Carsten Nielsen",
368 "tag_ids": [1, 2]
b904f9e @trevor small README changes
trevor authored
369 }
6f9436c @heycarsten Added an example for using a custom data attribute in DS.hasMany asso…
heycarsten authored
370 }
371 ```
372
373 In this case, you would specify the key in the association like this:
374
375 ```javascript
376 App.Person = DS.Model.extend({
d89ff1a Update docs to suggest best practices
tomhuda authored
377 tags: DS.hasMany('App.Tag', { key: 'tag_ids' })
6f9436c @heycarsten Added an example for using a custom data attribute in DS.hasMany asso…
heycarsten authored
378 });
379 ```
380
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
381 ### Finding a Specific Record Instance
d2b9c6a Update README.
Tom Dale authored
382
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
383 You can retrieve a record by its unique ID by using the `find` method:
d2b9c6a Update README.
Tom Dale authored
384
385 ```javascript
386 var model = App.store.find(App.Person, 1);
387 ```
388
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
389 If that specific record has already been loaded, it will be returned
d2b9c6a Update README.
Tom Dale authored
390 immediately. Otherwise, an empty object will be returned. You can setup
391 bindings and observers on the properties you're interested in; as soon
392 as the data returns from the persistence layer, all of the attributes
393 you specified will be updated automatically.
394
395 Besides `find()`, all of the methods described below operate in a similar
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
396 fashion.
d2b9c6a Update README.
Tom Dale authored
397
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
398 ### Querying Record Instances
d2b9c6a Update README.
Tom Dale authored
399
400 You can make a server query by passing an Object as the second parameter to
401 find. In this case, you will get back a `ModelArray` object.
402
403 ```javascript
404 App.people = App.store.find(App.Person, { page: 1 });
405 ```
406
407 At first, this `people` array will have no elements. Later, we will see how
408 your adapter will populate the `people`. Because the `people` array is an
409 Ember Array, you can immediately insert it into the DOM. When it becomes
410 populated later, Ember's bindings will automatically update the DOM.
411
412 ```html
413 <ul>
414 {{#each App.people}}
415 <li>{{fullName}}</li>
416 {{/each}}
417 </ul>
418 ```
419
420 This will allow you to ask the store for an Array of information, and keep your
421 view code completely agnostic to how the Array becomes populated.
422
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
423 Note: If manually retrieving records from a `ModelArray`, you must use
d2b9c6a Update README.
Tom Dale authored
424 the `objectAt(index)` method. Since the object is not a JavaScript Array,
425 using the `[]` notation will not work.
426
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
427 ### Finding All Records of a Model Type
d2b9c6a Update README.
Tom Dale authored
428
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
429 To find all records of a certain type, use the store's `findAll()` method:
d2b9c6a Update README.
Tom Dale authored
430
431 ```javascript
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
432 var people = App.store.findAll(App.Person);
d2b9c6a Update README.
Tom Dale authored
433 ```
434
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
435 All currently loaded records of that type will be immediately returned
d2b9c6a Update README.
Tom Dale authored
436 in a `ModelArray`. Your adapter will also have an opportunity to load
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
437 additional records of that type if necessary.
d2b9c6a Update README.
Tom Dale authored
438
02410d3 @wycats Clarify that `findAll` returns a live ModelArray
wycats authored
439 Whenever a new record is loaded into the store for the type in question,
440 the `ModelArray` returned by `findAll` will update to reflect the new
441 data. This means that you can pass it to a `#each` in an Ember template
442 and it will stay up to date as new data is loaded.
443
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
444 ### Filtering Loaded Records
d2b9c6a Update README.
Tom Dale authored
445
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
446 You can filter all records of a model type by calling the store's `filter()`
447 method with a function that determines whether the record should
448 be included or not. To avoid materializing record objects needlessly, only
d2b9c6a Update README.
Tom Dale authored
449 the raw data hash returned from the persistence layer is passed.
450
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
451 To include a record, return `true`. If a record should not be included,
d2b9c6a Update README.
Tom Dale authored
452 return `false` or `undefined`.
453
454 ```javascript
3cee7b8 @ebryn Be consistent with usage of App.store
ebryn authored
455 var oldPeople = App.store.filter(App.Person, function(data) {
d2b9c6a Update README.
Tom Dale authored
456 if (data.age > 80) { return true; }
457 });
458 ```
459
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
460 ### Creating New Records
d2b9c6a Update README.
Tom Dale authored
461
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
462 You can create new record based on a particular model definition with `createRecord()`:
d2b9c6a Update README.
Tom Dale authored
463
464 ```javascript
38825f0 @tchak update README with new names
tchak authored
465 var wycats = App.store.createRecord(App.Person, { name: "Brohuda" });
d2b9c6a Update README.
Tom Dale authored
466 ```
467
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
468 New records are not saved back to the persistence layer until the
d2b9c6a Update README.
Tom Dale authored
469 store's `commit()` method is called.
470
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
471 ### Updating Records
d2b9c6a Update README.
Tom Dale authored
472
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
473 To update records, simply change a property on them. Updated records
d2b9c6a Update README.
Tom Dale authored
474 will not be saved until the store's `commit()` method is called, which
475 allows you to batch changes.
476
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
477 ### Deleting Records
d2b9c6a Update README.
Tom Dale authored
478
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
479 To delete a record, call its `deleteRecord()` method:
d2b9c6a Update README.
Tom Dale authored
480
481 ```javascript
482 var person = App.store.find(App.Person, 1);
38825f0 @tchak update README with new names
tchak authored
483 person.deleteRecord();
d2b9c6a Update README.
Tom Dale authored
484 ```
485
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
486 The record will not be deleted in the persistence layer until the store's
487 `commit()` method is called. However, deleted records will immediately be
488 removed from its `ModelArray` and associations.
d2b9c6a Update README.
Tom Dale authored
489
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
490 ### Record Lifecycle
d2b9c6a Update README.
Tom Dale authored
491
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
492 You can be notified when certain events occur in a record's lifecycle by
d2b9c6a Update README.
Tom Dale authored
493 implementing methods on them:
494
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
495 * `didCreate` - called when the record has been successfully created in the persistence layer
d2b9c6a Update README.
Tom Dale authored
496 * `didUpdate` - called when changes have been successfully saved to the persistence layer
497 * `didLoad` - called when data has finished loading from the persistence layer
498
499 For example:
500
501 ```javascript
502 App.Person = DS.Model.extend({
503 didLoad: function() {
504 alert(this.get('firstName') + " finished loading.");
505 }
506 });
507 ```
508
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
509 You can also determine the state of a record by checking its state properties.
d2b9c6a Update README.
Tom Dale authored
510
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
511 * `isLoaded` - true when the record has finished loading, always true for models created locally.
512 * `isDirty` - true for created, updated, or deleted records that have not yet been saved
513 * `isSaving` - true if the record is in the process of being saved
514 * `isDeleted` - true if the record has been deleted, either locally or on the server
515 * `isError` - true if the record is in an error state
d2b9c6a Update README.
Tom Dale authored
516
517 ### Loading Data
518
519 You can "pre-load" data into the store, so it's ready for your users
520 as soon as they need it.
521
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
522 To load an individual record, use the `load()` method:
d2b9c6a Update README.
Tom Dale authored
523
524 ```javascript
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
525 App.store.load(App.Person, {
d2b9c6a Update README.
Tom Dale authored
526 id: 1,
527 firstName: "Peter",
528 lastName: "Wagenet"
529 });
530 ```
531
532 You can load multiple records using `loadMany()`:
533
534 ```javascript
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
535 App.store.loadMany(App.Person, [{
d2b9c6a Update README.
Tom Dale authored
536 id: 2,
537 firstName: "Erik",
538 lastName: "Brynjolsofosonsosnson"
539 },
540
541 {
542 id: 3,
543 firstName: "Yehuda",
544 lastName: "Katz"
545 }]);
546 ```
547
548 ## Adapter API
549
550 An adapter is an object that receives requests from a store and translates
551 them into the appropriate action to take against your persistence layer. The
552 persistence layer is usually an HTTP API, but may be anything, such as the
553 browser's local storage.
554
555 ### Creating an Adapter
556
557 First, create a new instance of `DS.Adapter`:
558
559 ```javascript
560 App.adapter = DS.Adapter.create();
561 ```
562
563 To tell your store which adapter to use, set its `adapter` property:
564
565 ```javascript
566 App.store = DS.Store.create({
b904f9e @trevor small README changes
trevor authored
567 revision: 3,
07cca37 The 'integer' attribute should be called 'number'
tomhuda authored
568 adapter: App.adapter
d2b9c6a Update README.
Tom Dale authored
569 });
570 ```
571
572 Next, implement the methods your adapter needs, as described below.
573
574 ### find()
575
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
576 Implement `find()` to fetch and populate a record with a specific ID. Once the
577 record has been found, call the store's `load()` method:
d2b9c6a Update README.
Tom Dale authored
578
579 ```javascript
580 App.Person = DS.Model.extend();
581 App.Person.reopenClass({
582 url: '/people/%@'
583 });
584
585 DS.Adapter.create({
586 find: function(store, type, id) {
b80bf76 Fixed several typos with 'get' and associations
Cory Loken authored
587 var url = type.url;
d2b9c6a Update README.
Tom Dale authored
588 url = url.fmt(id);
589
590 jQuery.getJSON(url, function(data) {
591 // data is a Hash of key/value pairs. If your server returns a
592 // root, simply do something like:
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
593 // store.load(type, id, data.person)
d2b9c6a Update README.
Tom Dale authored
594 store.load(type, id, data);
027876d @cmeiklejohn Fix a few missing );'s in the documentation.
cmeiklejohn authored
595 });
d2b9c6a Update README.
Tom Dale authored
596 }
597 });
598 ```
599
600 The store will call your adapter's `find()` method when you call
601 `store.find(type, id)`.
602
603 **Note** that for the rest of this documentation, we will use the `url` property in
604 our adapter. This is *not* the only way to write an adapter. For instance, you
605 could simply put a case statement in each method and do something different per
606 type. Or you could expose different information on your types that you use in
607 the adapter. We are simply using `url` to illustrate how an adapter is written.
608
609 ### findMany()
610
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
611 Implement `findMany()` to fetch and populate all of the records for a given list
d2b9c6a Update README.
Tom Dale authored
612 of IDs. The default `findMany()` will repeatedly invoke `find()`, but this may
613 be extremely inefficient. If you can, your server should support a way to find
614 many items by a list of IDs.
615
616 Once you are ready to populate the store with the data for the requested IDs,
617 use the loadMany method:
618
619 ```javascript
620 App.Person = DS.Model.extend();
621 App.Person.reopenClass({
a51daed Fix mismatched quote in README
tomdale authored
622 url: '/people?ids=%@'
d2b9c6a Update README.
Tom Dale authored
623 });
624
625 DS.Adapter.create({
626 findMany: function(store, type, ids) {
b80bf76 Fixed several typos with 'get' and associations
Cory Loken authored
627 var url = type.url;
d2b9c6a Update README.
Tom Dale authored
628 url = url.fmt(ids.join(','));
b904f9e @trevor small README changes
trevor authored
629
d2b9c6a Update README.
Tom Dale authored
630 jQuery.getJSON(url, function(data) {
631 // data is an Array of Hashes in the same order as the original
632 // Array of IDs. If your server returns a root, simply do something
633 // like:
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
634 // store.loadMany(type, ids, data.people)
d2b9c6a Update README.
Tom Dale authored
635 store.loadMany(type, ids, data);
027876d @cmeiklejohn Fix a few missing );'s in the documentation.
cmeiklejohn authored
636 });
d2b9c6a Update README.
Tom Dale authored
637 }
638 });
639 ```
640
641 #### Implementing findMany in Rails
642
643 It is extremely easy to implement an endpoint that will find many items in
644 Ruby on Rails. Simply define the `index` action in a standard resourceful
645 controller to understand an `:ids` parameter.
646
647 ```ruby
648 class PostsController < ApplicationController
649 def index
650 if ids = params[:ids]
651 @posts = Post.where(:id => ids)
652 else
653 @posts = Post.scoped
654 end
655
656 respond_with @posts
657 end
658 end
659 ```
660
661 ### findQuery()
662
663 Called when the store's `find()` method is called with a query. Your adapter's
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
664 `findQuery()` method will be passed a `ModelArray` that you should populate with
d2b9c6a Update README.
Tom Dale authored
665 the results returned by the server.
666
667 ```javascript
668 App.Person = DS.Model.extend();
669 App.Person.reopenClass({
670 collectionUrl: '/people'
671 });
672
673 DS.Adapter.create({
674 findQuery: function(store, type, query, modelArray) {
b80bf76 Fixed several typos with 'get' and associations
Cory Loken authored
675 var url = type.collectionUrl;
b904f9e @trevor small README changes
trevor authored
676
d2b9c6a Update README.
Tom Dale authored
677 jQuery.getJSON(url, query, function(data) {
678 // data is expected to be an Array of Hashes, in an order
679 // determined by the server. This order may be specified in
680 // the query, and will be reflected in the view.
681 //
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
682 // If your server returns a root, simply do something like:
683 // modelArray.load(data.people)
d2b9c6a Update README.
Tom Dale authored
684 modelArray.load(data);
685 });
06b079d @richardiux Remove extra parenthesis on example (Readme)
richardiux authored
686 }
d2b9c6a Update README.
Tom Dale authored
687 });
688 ```
689
690 You can do whatever you want with the query in your adapter, but most commonly,
691 you will just send it along to the server as the `data` portion of an Ajax
692 request.
693
694 Your server will then be responsible for returning an Array of JSON data. When
695 you load the data into the `modelArray`, the elements of that Array will be
696 loaded into the store at the same time.
697
698 ### findAll()
699
700 Invoked when `findAll()` is called on the store. If you do nothing, only
701 models that have already been loaded will be included in the results. Otherwise,
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
702 this is your opportunity to load any unloaded records of this type. The
d2b9c6a Update README.
Tom Dale authored
703 implementation is similar to findMany(); see above for an example.
b904f9e @trevor small README changes
trevor authored
704
38825f0 @tchak update README with new names
tchak authored
705 ### createRecord()
d2b9c6a Update README.
Tom Dale authored
706
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
707 When `commit()` is called on the store and there are records that need to be
d2b9c6a Update README.
Tom Dale authored
708 created on the server, the store will call the adapter's `create()` method.
709
710 Once the store calls the adapter's `create` method, it will be put into a
711 `saving` state, and further attempts to edit the model will result in an
712 error.
713
714 Implementing a `create` method is straight forward:
715
716 ```javascript
717 App.Person = DS.Model.extend();
718 App.Person.reopenClass({
719 url: '/people/%@'
720 });
721
722 DS.Adapter.create({
38825f0 @tchak update README with new names
tchak authored
723 createRecord: function(store, type, model) {
b80bf76 Fixed several typos with 'get' and associations
Cory Loken authored
724 var url = type.url;
d2b9c6a Update README.
Tom Dale authored
725
726 jQuery.ajax({
727 url: url.fmt(model.get('id')),
04d8aaf @tchak Fix create example in README
tchak authored
728 data: model.get('data'),
d2b9c6a Update README.
Tom Dale authored
729 dataType: 'json',
730 type: 'POST',
b904f9e @trevor small README changes
trevor authored
731
d2b9c6a Update README.
Tom Dale authored
732 success: function(data) {
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
733 // data is a hash of key/value pairs representing the record.
d2b9c6a Update README.
Tom Dale authored
734 // In general, this hash will contain a new id, which the
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
735 // store will now use to index the record. Future calls to
736 // store.find(type, id) will find this record.
242aca0 @tchak renaming from did*Model to did*Record
tchak authored
737 store.didCreateRecord(model, data);
d2b9c6a Update README.
Tom Dale authored
738 }
739 });
740 })
741 });
742 ```
743
38825f0 @tchak update README with new names
tchak authored
744 ### createRecords()
d2b9c6a Update README.
Tom Dale authored
745
38825f0 @tchak update README with new names
tchak authored
746 For better efficiency, you can implement a `createRecords` method on your adapter,
d2b9c6a Update README.
Tom Dale authored
747 which should send all of the new models to the server at once.
748
749 ```javascript
750 App.Person = DS.Model.extend();
751 App.Person.reopenClass({
752 collectionUrl: '/people'
753 });
754
755 DS.Adapter.create({
38825f0 @tchak update README with new names
tchak authored
756 createRecords: function(store, type, array) {
d2b9c6a Update README.
Tom Dale authored
757 jQuery.ajax({
b80bf76 Fixed several typos with 'get' and associations
Cory Loken authored
758 url: type.collectionUrl,
d2b9c6a Update README.
Tom Dale authored
759 data: array.mapProperty('data'),
760 dataType: 'json',
761 type: 'POST',
b904f9e @trevor small README changes
trevor authored
762
d2b9c6a Update README.
Tom Dale authored
763 success: function(data) {
764 // data is an array of hashes in the same order as
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
765 // the original records that were sent.
242aca0 @tchak renaming from did*Model to did*Record
tchak authored
766 store.didCreateRecords(type, array, data);
d2b9c6a Update README.
Tom Dale authored
767 }
768 });
769 })
770 });
771 ```
772
38825f0 @tchak update README with new names
tchak authored
773 ### updateRecord()
d2b9c6a Update README.
Tom Dale authored
774
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
775 Update is implemented the same as `createRecord()`, except after the record has been
242aca0 @tchak renaming from did*Model to did*Record
tchak authored
776 saved, you should call the store's `didUpdateRecord()` method.
d2b9c6a Update README.
Tom Dale authored
777
778 ```javascript
779 App.Person = DS.Model.extend();
780 App.Person.reopenClass({
781 url: '/people/%@'
782 });
783
784 DS.Adapter.create({
38825f0 @tchak update README with new names
tchak authored
785 updateRecord: function(store, type, model) {
b80bf76 Fixed several typos with 'get' and associations
Cory Loken authored
786 var url = type.url;
d2b9c6a Update README.
Tom Dale authored
787
788 jQuery.ajax({
789 url: url.fmt(model.get('id')),
790 dataType: 'json',
791 type: 'PUT',
b904f9e @trevor small README changes
trevor authored
792
d2b9c6a Update README.
Tom Dale authored
793 success: function(data) {
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
794 // data is a hash of key/value pairs representing the record
d2b9c6a Update README.
Tom Dale authored
795 // in its current state on the server.
242aca0 @tchak renaming from did*Model to did*Record
tchak authored
796 store.didUpdateRecord(model, data);
d2b9c6a Update README.
Tom Dale authored
797 }
798 });
799 })
800 });
801 ```
802
38825f0 @tchak update README with new names
tchak authored
803 ### updateRecords()
d2b9c6a Update README.
Tom Dale authored
804
38825f0 @tchak update README with new names
tchak authored
805 Again, `updateRecords()` is very similar to `createRecords()`.
d2b9c6a Update README.
Tom Dale authored
806
807 ```javascript
808 App.Person = DS.Model.extend();
809 App.Person.reopenClass({
810 collectionUrl: '/people'
811 });
812
813 DS.Adapter.create({
38825f0 @tchak update README with new names
tchak authored
814 updateRecords: function(store, type, array) {
d2b9c6a Update README.
Tom Dale authored
815 jQuery.ajax({
b80bf76 Fixed several typos with 'get' and associations
Cory Loken authored
816 url: type.collectionUrl,
d2b9c6a Update README.
Tom Dale authored
817 data: array.mapProperty('data'),
818 dataType: 'json',
819 type: 'PUT',
b904f9e @trevor small README changes
trevor authored
820
d2b9c6a Update README.
Tom Dale authored
821 success: function(data) {
822 // data is an array of hashes in the same order as
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
823 // the original records that were sent.
242aca0 @tchak renaming from did*Model to did*Record
tchak authored
824 store.didUpdateRecords(array);
d2b9c6a Update README.
Tom Dale authored
825 }
826 });
827 })
828 });
829 ```
830
38825f0 @tchak update README with new names
tchak authored
831 ### deleteRecord()
d2b9c6a Update README.
Tom Dale authored
832
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
833 To delete a record, implement the `deleteRecord()` method, and call the store's
242aca0 @tchak renaming from did*Model to did*Record
tchak authored
834 `didDeleteRecord()` method when completed.
d2b9c6a Update README.
Tom Dale authored
835
836 ```javascript
837 App.Person = DS.Model.extend();
838 App.Person.reopenClass({
839 url: '/people/%@'
840 });
841
842 DS.Adapter.create({
38825f0 @tchak update README with new names
tchak authored
843 deleteRecord: function(store, type, model) {
b80bf76 Fixed several typos with 'get' and associations
Cory Loken authored
844 var url = type.url;
d2b9c6a Update README.
Tom Dale authored
845
846 jQuery.ajax({
847 url: url.fmt(model.get('id')),
848 dataType: 'json',
849 type: 'DELETE',
b904f9e @trevor small README changes
trevor authored
850
d2b9c6a Update README.
Tom Dale authored
851 success: function() {
242aca0 @tchak renaming from did*Model to did*Record
tchak authored
852 store.didDeleteRecord(model);
d2b9c6a Update README.
Tom Dale authored
853 }
854 });
855 })
856 });
857 ```
858
38825f0 @tchak update README with new names
tchak authored
859 ### deleteRecords()
d2b9c6a Update README.
Tom Dale authored
860
861 Are you getting it?
862
863 ```javascript
864 App.Person = DS.Model.extend();
865 App.Person.reopenClass({
866 collectionUrl: '/people'
867 });
868
869 DS.Adapter.create({
38825f0 @tchak update README with new names
tchak authored
870 deleteRecords: function(store, type, array) {
d2b9c6a Update README.
Tom Dale authored
871 jQuery.ajax({
b80bf76 Fixed several typos with 'get' and associations
Cory Loken authored
872 url: type.collectionUrl,
d2b9c6a Update README.
Tom Dale authored
873 data: array.mapProperty('data'),
874 dataType: 'json',
875 type: 'DELETE',
b904f9e @trevor small README changes
trevor authored
876
d2b9c6a Update README.
Tom Dale authored
877 success: function(data) {
242aca0 @tchak renaming from did*Model to did*Record
tchak authored
878 store.didDeleteRecords(array);
d2b9c6a Update README.
Tom Dale authored
879 }
880 });
881 })
882 });
883 ```
884
885 ### commit()
886
887 For maximum turbo-efficiency, you can package all pending changes (creates,
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
888 updates, and deletes) into one mega package of data awesomeness. To do so,
d2b9c6a Update README.
Tom Dale authored
889 implement `commit()`, which will be called with everything that needs
890 to be sent to the persistence layer.
891
892 Here's what the default adapter's `commit()` method looks like:
893
894 ```javascript
895 commit: function(store, commitDetails) {
896 commitDetails.updated.eachType(function(type, array) {
38825f0 @tchak update README with new names
tchak authored
897 this.updateRecords(store, type, array.slice());
d2b9c6a Update README.
Tom Dale authored
898 }, this);
899
900 commitDetails.created.eachType(function(type, array) {
38825f0 @tchak update README with new names
tchak authored
901 this.createRecords(store, type, array.slice());
d2b9c6a Update README.
Tom Dale authored
902 }, this);
903
904 commitDetails.deleted.eachType(function(type, array) {
38825f0 @tchak update README with new names
tchak authored
905 this.deleteRecords(store, type, array.slice());
d2b9c6a Update README.
Tom Dale authored
906 }, this);
907 }
908 ```
909
910 ### Connecting to Views
911
b904f9e @trevor small README changes
trevor authored
912 Ember Data will always return records or arrays of records of a certain type
913 immediately, even though the underlying JSON objects have not yet been returned
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
914 from the server.
d2b9c6a Update README.
Tom Dale authored
915
916 In general, this means that you can insert them into the DOM using Ember's
917 Handlebars template engine, and they will automatically update when your
918 adapter has populated them.
919
920 For example, if you request a `ModelArray`:
921
922 ```javascript
8f1ef1e Added App namespace to one more part of documentation.
Cory Loken authored
923 App.people = App.store.find(App.Person, { firstName: "Tom" });
d2b9c6a Update README.
Tom Dale authored
924 ```
925
926 You will get back a `ModelArray` that is currently empty. Ember Data will then
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
927 ask your adapter to populate the `ModelArray` with records, which will usually make an Ajax
d112829 @nicholaides Spelling correction.
nicholaides authored
928 request. However, you can immediately refer to it in your templates:
d2b9c6a Update README.
Tom Dale authored
929
930 ```html
931 <ul>
932 {{#each App.people}}
933 <li>{{fullName}}</li>
934 {{/each}}
935 </ul>
936 ```
937
938 Once the Adapter calls `modelArray.load(array)`, the DOM will automatically
939 populate with the new information.
940
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
941 The same is true of records themselves. For instance, you can make a request
942 for a single record:
d2b9c6a Update README.
Tom Dale authored
943
944 ```javascript
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
945 App.person = App.store.find(App.Person, 1);
d2b9c6a Update README.
Tom Dale authored
946 ```
947
948 You will immediately receive back a new unpopulated `Person` object. You can
949 refer to it in the view right away:
950
951 ```html
952 {{App.person.fullName}}
953 ```
954
955 Initially, this will be empty, but when your adapter calls `store.load(hash)`,
956 it will update with the information provided.
957
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
958 If you'd like to show different content while a record is in the process of
959 being loaded, you can use the record's `isLoaded` property:
d2b9c6a Update README.
Tom Dale authored
960
961 ```html
962 {{#with App.person}}
963 {{#if isLoaded}}
474deac @ebryn One last fix
ebryn authored
964 Hello, {{fullName}}!
d2b9c6a Update README.
Tom Dale authored
965 {{else}}
966 Loading...
967 {{/if}}
968 {{/with}}
969 ```
970
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
971 Note that the same principle applies to `ModelArray`s, as well. Like records, a
972 `ModelArray` has an `isLoaded` property that you can use to display different
d2b9c6a Update README.
Tom Dale authored
973 content.
974
64e49cc @roydaniels Updated readme to reflect differences between models and records.
roydaniels authored
975 You can also indicate to users when a record is saving, for example:
d2b9c6a Update README.
Tom Dale authored
976
977 ```html
978 {{#with App.person}}
60eee75 @ebryn A couple more fixed typos
ebryn authored
979 <h1 {{bindAttr class="isSaving"}}>{{fullName}}</h1>
d2b9c6a Update README.
Tom Dale authored
980 {{/with}}
981 ```
982
983 In this case, you could make the `is-saving` class in your CSS grey out the
984 content or add a spinner alongside it, for instance.
82d7467 Initial import of data store library
Tom Dale authored
985
986 ## Unit Tests
987
c5270a9 @wycats Clarify the README to require bundle exec
wycats authored
988 To run unit tests, run `bundle exec rackup` from the root directory and visit
82d7467 Initial import of data store library
Tom Dale authored
989 `http://localhost:9292/tests/index.html?package=ember-data`.
990
d2b9c6a Update README.
Tom Dale authored
991 ### What next?
992
993 Profit.
Something went wrong with that request. Please try again.