• If you are citizen of an European Union member nation, you may not use this service unless you are at least 16 years old.

  • You already know Dokkio is an AI-powered assistant to organize & manage your digital files & messages. Very soon, Dokkio will support Outlook as well as One Drive. Check it out today!

View
 

DataStore-Using Fixtures

Page history last edited by Remy Munch 13 years, 11 months ago

Once you’ve defined your basic data model, you can generally get right to work building the rest of your application.   Before we start hacking away, however, it would help to have some initial data to get started with; at least for testing. 

Although you could take the time to get your server working right away, it’s usually better to get your app up and running first.  This is why the DataSource framework comes with built-in support for fixtures to get you moving quickly.

 

Fixtures are simply a sample set of data hashes that your application can use instead of talking to a real server.  Whenever the store needs data, instead of asking a server to provide it, it will simply look in the local fixtures instead.

 

Adding Fixture Data

 

If you used sc-gen to create your application, you don’t need to do anything special to get your application using fixtures.  Just define your data and away you go.

 

Normally your fixture data is stored in a “fixtures” directory in your project.  Whenever you add a model to your project using sc-gen, it automatically adds a file to the fixtures directory as well where you can place your sample data.

 

For example, let’s say you generate a new Contact model on your application using the following sc-gen command (from the terminal):

 

sc-gen model MyApp.Contact

 

You would then find a new fixtures file at your my_app/fixtures/contact.js that looks something like this:

 

// Comments removed for brevity

sc_require('models/contact');

 

MyApp.Contact.FIXTURES = [

  // { guid: 1,

  //   firstName: "Michael",

  //   lastName: "Scott" },

  //

  // { guid: 2,

  //   firstName: "Dwight",

  //   lastName: "Schrute" }

];

 

The first line is a compiler directive that tells the build tools to make sure your Contact model loads before the fixtures.  The next line simply assigns an empty array to MyApp.Contact.FIXTURES.  As you might guess from the comments, you are supposed to populate this array with data hashes that will comprise your sample data.

For example, you might delete the comments above and add a few hashes like the following:

 

MyApp.Contact.FIXTURES = [

  { guid: 1,

    firstName: “John”,

    lastName:  “Doe” },

  

  { guid: 2,

    firstName: “Jan”,

    lastName:  “Novak” }

];

 

Remember that fixtures represent the actual JSON data hashes you would normally exchange with your server.  Data hashes can only contain JSON values (i.e. Numbers, Strings, Booleans, Arrays, and other Hashes). It is important to name your fixtures as regular hashes since it will allow you to verify that your Record model is able to convert the JSON into properly structured data.

 

You can generally populate the fixtures with any data hashes that make sense for your application.  The only property that is required is a primary key.  (In the above example, the records use the default primary key “guid”.  You can set the primaryKey for a record; see the previous chapter.)  Leaving out this key will not raise an exception but your data will never actually be loaded since the DataStore uses the primary key to lookup records. 

 

Defining Relationships

 

DataStore-Defining Your Model has more information regarding relationships and their properties (inverse, isMaster etc.)

 

Since fixture data are simply data hashes you exchange with the server, you should model relationships using foreign keys – just like you would get for the actual server.  For example, let’s say your Contact records have a “group” property that points to the group the contact belongs to.  The definitions for the Group and Contact records might look like this:

 

MyApp.Contact = SC.Record.extend({

  firstName: SC.Record.attr(String),

  lastName:  SC.Record.attr(String),

  group:     SC.Record.toOne(“MyApp.Group”, {

     inverse: "contacts", isMaster: NO

  })

});

 

MyApp.Group = SC.Record.extend({

  name:     SC.Record.attr(String),

       contacts:  SC.Record.toMany("Myapp.Contact", {

            inverse: "group", isMaster: YES

       }) 

});

 

To define the group property on your Contact fixtures, you will first need some Group fixtures.  If you used sc-gen, you would find your groups fixtures in my_app/fixtures/group.js.  You could define a few simple ones like so:

 

MyApp.Group.FIXTURES = [

  { guid: 1, name: “Friends”, contacts: [1] },

  { guid: 2, name: “Family”, contacts: [2] }

];

 

The corresponding group property in your Contact fixtures should simply contain the primary key of the group the contact belongs to.  For example:

 

MyApp.Contact.FIXTURES = [

  { guid: 1,

    firstName: “John”,

    lastName:  “Doe”,

    group: 1 }, // i.e. ‘Friends’ group

  

  { guid: 2,

    firstName: “Jan”,

    lastName:  “Novak”,

    group: 2 }, // i.e. ‘Family’ group

];

 

*NOTE Fixtures don't behave exactly as a substitute for a relational database: the foreign key association must be declared on each side of the relationship. Check the declarations in bold above

 

When you actually load this data into your store, the toOne() relationship you defined on your Contact record will automatically dereference this group id and return an actual group.

 

Using the Fixtures Data Source

 

Now that you’ve defined your data, how does it actually get loaded into memory?  Remember that in your application, all of the active, in-memory data is kept in a store.  Whenever you try to get data that is not yet loaded, the store will ask its data source to load it.

 

In a finished application, the data source is an object that you will usually write or customize to communicate with your backend server.  When you create a new application, however, the store is configured to talk to a built-in fixtures data source instead.

 

You can see this configuration if you open the core.js file in your application.  You’ll see something like this: 

 

MyApp = SC.Application.create({

  …

  store: SC.Store.create().from(SC.Record.fixtures)

});

 

The from() method connects your application store to the built-in fixtures data source.  Later on, when you are ready to talk to your real server, you will change this method to hook up your custom data source instead.  For now, however, the fixtures will do.

 

The implementation of the fixtures data source is very simple.  Whenever the store asks the data source to retrieve a record of a particular type, it looks for a FIXTURES array on the record type class.  It then loads any data hashes it finds in there into the store memory.  It’s that simple.

 

NOTE: Since fixtures are stored locally, the fixtures data source generally loads data synchronously.  When you switch to loading from your server, you will load data asychronously instead.  Often find you may have a few timing bugs to iron out due to this.  Resist the temptation to just make the Ajax requests you kick off synchronous.  This will cause the browser to appear to hang; leading to a poor user experience. 

 

Protip 1: Swapping Fixture Data

 

Sometimes you may find yourself wanting to test multiple scenarios with different data sets.  It appears, however, that FIXTURES are defined on each record type globally.  How can you do this?

 

Thanks to the dynamic nature of JavaScript, it’s actually very easy to swap out fixtures early in your app.  For example, you might set a global constant in your core.js indicating the which model you want:

 

// in my_app/core.js:

MyApp.SCENARIO = ‘A’; // or ‘B’

 

Then you can use an if/else statement in your fixtures files to swap in the proper fixtures:

 

// in my_app/fixtures/contact.js:

if (MyApp.SCENARIO === ‘A’) {

   MyApp.Contact.FIXTURES = [ … ];

} else if (MyApp.SCENARIO === ‘B’) {

   MyApp.Contact.FIXTURES = [ … ];

 

Protip 2: Using Fixtures Or Not

 

As you get later into your project, you will eventually move away from fixtures.  Sometimes, however, you may want to switch back and forth; testing new features with fixtures first, then testing with your real data source as well.

Go ahead and set your store in core.js to point to your server data source, then add this line to the top of your main() function:

 

// in main.js:

MyApp.main = function() {

  // switch to fixtures if #fixtures in URL

  if (window.location.hash.toString().match(‘fixtures’)) {

    MyApp.store.from(SC.Record.fixtures);

  }

  …

}

 

This way you can reload your app using fixtures simply by adding #fixtures to the end of your URL.  It is important that you make this kind of switch at the start of your main, before you actually load any data.  You are free to reconfigure your  data sources and fixtures as much as you want until you actually start loading the data.  Then things need to stay put until your app is reloaded.

 

Putting It All Together

 

Once you added a few basic records to your fixtures, it’s usually very easy to get started hooking things up to your application.  The example below will show you how all these pieces fit together.

 

Let’s say you’ve defined the Group and Contact record types just like we described above:

 

// in my_app/models/group.js:

MyApp.Group = SC.Record.extend({

  name:     SC.Record.attr(String)

});

 

// in my_app/models/contact.js:

MyApp.Contact = SC.Record.extend({

  firstName: SC.Record.attr(String),

  lastName:  SC.Record.attr(String),

  group:     SC.Record.toOne(“MyApp.Group”)

});

 

With this definition in place, you will need to define fixtures for both sets.  Here are the fixtures we’ll use for this example.  You can fill in anything you want to the FIXTURES array:

 

// in my_app/fixtures/group.js:

MyApp.Group.FIXTURES = [

  { guid: 1, name: “Friends” },

  { guid: 2, name: “Family” }

];

 

// in my_app/fixtures/contact.js:

MyApp.Contact.FIXTURES = [

  { guid: 1,

    firstName: “John”,

    lastName:  “Doe”,

    group: 1 }, // i.e. ‘Friends’ group

  

  { guid: 2,

    firstName: “Jan”,

    lastName:  “Novak”,

    group: 2 }, // i.e. ‘Family’ group

];

 

Remember you default app setup in core.js already has the store talking to the fixtures data source.  If you want to verify this, open your core.js file and look for something like:

 

// in my_app/core.js:

store: SC.Store.create().from(SC.Record.fixtures)

 

Now you should be able to load your app in the web browser and start playing around.  Open the JavaScript console in Firebug or WebKit Inspector and type the following:

 

contact = MyApp.store.find(MyApp.Contact, 1);

>> MyApp.Contact({ guid: 1, firstName: “John”, lastName: “Doe”, group: 1})

 

You should get back a single contact object.  What happens here when you call find() is worth dwelling on for a moment.  

 

Initially your store does not contain any data.  When you call find() with a record type and record ID, it tries to lookup the record only to find it is not loaded; so it asks the data source to provide it.  

 

In this case, the data source is the built-in fixtures data source so it looks up the same record in the MyApp.Contact.FIXTURES array and loads it immediately into your application store, where it is returned to you.

Clever.  It’s just like you were talking to a real server.  All the code paths are the same.

 

Since we’ve defined a toOne relationship on this contact, we should be able to lookup a group now also.  Just type the following on the JavaScript console:

 

contact.get(‘group’)

>> MyApp.Group({ guid: 1, name: “Friends” })

 

The return value from this property should be an actual group object.  In this case, something very similar happened as when you called find().  The Contact record, recognizing that group is a toOne relationship looked up the Group ID you set in the data hash (in this case a “1”).  It then issued a find() on the store to lookup the group.  As before, the store passed the request on to the data source, which loaded your data.

 

 

And that’s how fixtures work.  In this section we’ve just scratched the surface of how you can actually get data from your application store and work with it.  That will be the topic of the next chapter. 

 

Moving On

 

Continue to learn About Records »

or back to DataStore Programming Guide Home »

Comments (3)

Levi McCallum said

at 5:43 pm on Sep 13, 2009

This also needs to describe how a many-to-one and many-to-many relationship is described in fixtures.

Rick Meier said

at 1:04 pm on Sep 18, 2009

You may want to note that in fixture data that guids can not be 0.

mako said

at 1:09 pm on Jan 24, 2010

How a Date attribute is described in fixtures?
I've tried new Date() and "Sun Jan 24 2010 21:34:31 GMT+0100 (CET)" but instead of desired date I'm getting "Jan 1 2010".

You don't have permission to comment on this page.