public
Description: JavaScript Application Framework - JS library only
Home | Edit | New

SproutCore's Modern Model Layer: Part 3

by Erich Ocean (onitunes)

Relationships between objects are central to the model layer. Those relationships and the objects they’re made between constitute the next major part of SproutCore’s model layer support: the object graph.

The Object Graph

It’s all well and good to be able to create records and add them to the Store, but we also want to be able to relate those records to each other. The set of objects in the Store, and the relationships between them, are collectively referred to as the “object graph”.

NOTE: “Graph” in this context refers to a mathematical object, like an array or a dictionary. It does not refer to something you might put in a PowerPoint presentation. Mathematical graphs are composed of two things: vertices and edges. In SproutCore object graphs, model objects are the vertices, and relationships between them are the edges. See Wikipedia for more information.

The key idea behind the object graph is this: if you can somehow get a reference to just one object in the graph, you can use that reference to visit other objects in the graph. The way to “visit other objects” in SproutCore is by using key-value coding.

Recall from Part 1 how to access the isCompleted property of a todo object using key-value coding:

var yn = todo.get(‘isCompleted’) ;

In this case, the value returned is a JavaScript Boolean object. If instead we had returned another SC.Record object, we would be using the object graph to traverse a relationship. So let’s do that.

First, let’s set up our application:

$ sproutcore my_app
$ cd my_app
$ sc-gen model my_app/todo_list
$ sc-gen model my_app/todo

Okay, that gives us our basic application structure. Let’s launch the SproutCore dev server and start playing:

$ sc-server

In FireFox, load http://localhost:4020/my_app and then start up FireBug. At the FireBug console, do:

> SC.Store.addRecord( MyApp.TodoList.create({ 'name': "My List" }) );
> SC.Store.addRecord( MyApp.Todo.create({ 'name': "Something to do" }) );
> SC.Store.records();
[MyApp.TodoList({ guid="@468" }) name=My List _bindings=[0] _observers=[0], MyApp.Todo({ guid="@571" }) name=Something to do. _bindings=[0] _observers=[0]]

Just as in Part 2, our records got created and added to the Store. However, adding records each time we reload the application sure is tedious. Let’s fix that.

Using Fixtures

Although we could add the code lines above to clients/my_app/main.js, SproutCore includes a nifty facility called fixtures that allows us to automatically create records when the application loads, so let’s take advantage of that. Open up clients/my_app/fixures/todo_list.js and make it look like this:

// ==========================================================================
// MyApp.TodoList Fixtures
// ==========================================================================

require('core') ;

MyApp.FIXTURES = MyApp.FIXTURES.concat([
  
  { guid: 1,
    type: 'TodoList', 
    name: "My List"
  }

]);

Also modify clients/my_app/fixures/todo.js and make it look like this:

// ==========================================================================
// MyApp.Todo Fixtures
// ==========================================================================

require('core') ;

MyApp.FIXTURES = MyApp.FIXTURES.concat([
  
  { guid: 1, 
    type: 'Todo', 
    name: "Something to do"
  }

]);

In development mode, when our application loads, MyApp.FIXTURES will contain these record templates. In production, MyApp.FIXTURES will just be an empty array.

In clients/my_app/main.js, you should already see the line:

MyApp.server.preload(MyApp.FIXTURES) ;

That line is what loads our fixtures and turns them into actual records. As it turns out, preload() will eventually insert our records into SC.Store, so we still will be retrieving records from the Store as we’ve done before.

See also: Generating Fixtures with Code

Finding Records in the Store

One major advantage of using fixtures is that it allows you to give your records stable guid properties, which in turn allows you to find them in the Store easily. This is great for unit testing and also just messing around in the FireBug console.

To test out our new fixtures, refresh the page in FireFox and type this in the FireBug console:

> SC.Store.records();
[MyApp.Todo({ guid=1 }) guid=1 _bindings=[0] _observers=[0] _properties=[1], MyApp.TodoList({ guid=1 }) guid=1 _bindings=[0] _observers=[0] _properties=[1]]

Great. They loaded propertly. We can even find them one-at-a-time, now that they have a stable guid property:

> SC.Store.findRecords({guid:1}, MyApp.Todo);
[MyApp.Todo({ guid=1 }) guid=1 _bindings=[0] _observers=[0] _properties=[1]]
> SC.Store.findRecords({guid:1}, MyApp.TodoList);
[MyApp.TodoList({ guid=1 }) guid=1 _bindings=[0] _observers=[0] _properties=[1]]

An even easier way to do that is using the find() method our records inherit from SC.Record:

> MyApp.Todo.find(1);
MyApp.Todo({ guid=1 }) guid=1 _bindings=[0] _observers=[0] _properties=[1]
> MyApp.TodoList.find(1);
MyApp.TodoList({ guid=1 }) guid=1 _bindings=[0] _observers=[0] _properties=[1]

Before we move on, notice that we used type: 'Todo' in our fixture, not type: 'MyApp.Todo'. How did SproutCore know that we wanted MyApp.Todo? The answer lies in clients/my_app/core.js, where you’ll see the following line:

server: SC.Server.create({ prefix: [‘MyApp’] }),

That tells the server to look for our record types with a MyApp prefix.

Setting Up the Relationship

The goal is to be able to write the following code (which won’t work yet, this is just what we’re shooting for):

> /* assume todo and todoList are defined somewhere */
> todo.get('todoList');
MyApp.TodoList({ guid=1 }) name=My List _bindings=[0] _observers=[0]
> todoList.get('todos');
SC.Collection:233 conditions=Object _bindings=[0] _observers=[0]

In other words, a todo list has many todos, and a todo has one todo list.

To-One Relationships

Many readers will probably be familiar with the Rails approach, which stores the id of the todo list object as a property of the todo object. This is natural in a Rails application because it maps in a straightforward way to storage in a SQL database. This same approach also works in SproutCore.

NOTE: In SproutCore, the Rails id property is actually called guid. They mean the same thing, and are used in the same way.

We’re going to deal with the todotodoList relationship traversal first. It’s clear from our goal that we need a property on our todo objects called todoList, which should return a todo list object.

In SproutCore, we’ll set the relationship name, todoList, to the to do list object’s guid. Then we’ll update our model definition to define the type of model object todoList actually is. (SproutCore doesn’t infer it from the name, like Rails would.)

NOTE: In Rails, we’d have a column in our todos table called todo_list_id and we’d also put has_one :todo_list in our Todo class definition.

In clients/my_app/fixtures/todo.js, change it to look like this:

// ==========================================================================
// MyApp.Todo Fixtures
// ==========================================================================

require('core') ;

MyApp.FIXTURES = MyApp.FIXTURES.concat([
  
  { guid: 1, 
    type: 'Todo', 
    name: "Something to do",
    todoList: 1 // the guid of the todo list object this todo is related to
  }

]);

We need to update our todo model to support the todoList relationship, so modify clients/my_app/models/todo.js to look like this:

// ==========================================================================
// MyApp.Todo
// ==========================================================================

require('core');

/** @class
  
  (Document your class here)

  @extends SC.Record
  @author    AuthorName  
  @version 0.1
*/  
MyApp.Todo = SC.Record.extend(
/** @scope MyApp.Todo.prototype */ {
  
  todoListType: 'MyApp.TodoList'
  
}) ;

Back at the FireBug console, run:

> MyApp.Todo.find(1).get('todoList');
MyApp.TodoList({ guid=1 }) guid=1 _bindings=[0] _observers=[0] _properties=[1]

Bingo! Instant to-one relationship. Now let’s go the other direction.

To-Many Relationships

WARNING: The code below will only work with SproutCore Gem 0.9.12 or later. See the mailing list for a workaround for early version of SproutCore.

First, let’s beef up our todos fixture to include a few more instances. Update clients/my_app/fixtures/todo.js to look like this:

// ==========================================================================
// MyApp.Todo Fixtures
// ==========================================================================

require('core') ;

MyApp.FIXTURES = MyApp.FIXTURES.concat([
  
  { guid: 1, 
    type: 'Todo', 
    name: "Something to do",
    todoList: 1 // the guid of the todo list object this todo is related to
  },

  { guid: 2, 
    type: 'Todo', 
    name: "Something else to do",
    todoList: 1
  },

  { guid: 3, 
    type: 'Todo', 
    name: "Gee, I'm busy.",
    todoList: 1
  }

]);

We also need to update our todo list model to add the todos relationship, so modify clients/my_app/models/todo_list.js to look like this:

// ==========================================================================
// MyApp.TodoList
// ==========================================================================

require('core');

/** @class
  
  (Document your class here)

  @extends SC.Record
  @author    AuthorName  
  @version 0.1
*/  

MyApp.TodoList = SC.Record.extend(
/** @scope MyApp.TodoList.prototype */ {
  
  todos: SC.Record.hasMany('MyApp.Todo', 'todoList')
  
}) ;

NOTE: In Rails, we’d put has_many :todos in our TodoList model class definition.

And with that change made, let’s head back to FireFox, refresh the page, and head to the FireBug command line:

> var c = MyApp.TodoList.find(1).get('todos'); // c will be an SC.Collection
> c.count();
2

It worked! We can grab a todo list and use the todos relationships to grab all of the todos, as a collection. To show the real power of SproutCore, continue on at the console:

> var rec = MyApp.Todo.create({ 'name': "Something to do" });
> rec.set('todoList', MyApp.TodoList.find(1));
> SC.Store.addRecord( rec );
> c.count();
3

That, gentle reader, is why SproutCore is so great to work with: the relationship collection stayed current (i.e. count() increased to 3) automatically as we updated the Store, without any additional code.

Think about it: we could have pushed in that record from anywhere (such as a remote server, via Ajax), and our collection would still be updated for us, automatically. And since SproutCore uses bindings to connect views to controllers, and controllers to models, that means our views would be updated as well.

This bears repeating: in SproutCore, the UI will automatically stay in sync with the Store, through bindings.

And that concludes Part 3 of the modern model layer series!

Comments

A nice series. Thank you. – Anita.

It would be nice to have a little of the ‘SproutCore Voodoo’ in this article demystified by adding additional explanation regarding the following:

  • While it is obvious what the todoList attribute on each todo item in the fixture does… functions basically as a Foreign Key to the todo’s parent todoList object… it would be helpful to have 1 or 2 sentences explain why it is needed, the reason why its name the way it is (i.e., what naming convention is followed) and what link it has to the 2nd argument passed in the call to the SC.Record.hasMany() method.
  • Explain what adding the todoListType property to the Todo Model’s class definition does and also what is the naming convention being followed (i.e., is it the name of the Parent’s Class with a ‘Type’ suffix added?)
  • Also explain what the todos property on the TodoList Class definition does, its naming convention (i.e., is it the name of the child’s Class with an ‘s’ suffix added?). Explain what the SC.Record.hasMany() method does and explain what each of its parameters are and are used for.

Very helpful! Thanks! – Daniel Cohen

Edited some wonky formatting resulting from the nesting of code and pre tags, and extra brackets. – thinkingman

Last edited by thinkingman, Tue Mar 24 22:06:46 -0700 2009
Home | Edit | New
Versions: