DataCrux Main | Architecture | Documentation | Downloads

Note that if you're using any post-December build of DataCrux, you don't need to manually create the database. The framework will generate it for you automatically. You can still generate it manually if you'd like more control, though. A lot of this documentation is very outdated, but will be refreshed soon.

Setting up the Database

You'll need two things to get started: a SQLite database and a property list file for configuration. We'll use the DataCruxNotebook project as a working example. Until DataCrux includes a tool to do this for you, you'll need to create the database yourself. Download and build the sqlite source, and use the 'sqlite' tool to create a new database:

cd ~/Library/Application\ Support/DataCrux/
/usr/bin/sqlite com.treehouseideas.notebook.db

Now, create the tables inside sqlite:

create table documents (
  id INTEGER PRIMARY KEY,
  title text,
  content text,
  date text,
  category_id integer,
  project_id integer
);

create table categories (
  id INTEGER PRIMARY KEY,
  name text
);

create table projects (
  id INTEGER PRIMARY KEY,
  name text
);

SQLite is actually typeless. The column types are basically just a hint to the person manipulating the database. However, it is important that the primary key column is assigned a type of 'INTEGER PRIMARY KEY', which is a hint to SQLite to assign autoincrement key values to each new record. DataCrux uses this feature to automatically generate new records/objects when the -newItemOfType: method is called.

Property List I: Mapping

The relationship property list is what DataCrux uses to sync your custom objects and their children to rows in the database.

The first section off of the root -- a dictionary labeled maps -- links custom classes from your application to DataCrux units, which roughly equate to tables. It's probably easier if the unit name matches the table name, but this isn't required. In this example, we have custom data classes from our DataCruxNotebook application called Category, Document, and Project. They are being mapped and reverse-mapped to DataCrux units with the same names, but in lower case.

  • maps (dict)

    • classesToUnits (dict)
      • Category: category
      • Document: document
      • Project: project

    • unitsToClasses
      • category: Category
      • document: Document
      • project: Project

Property List II: Units

Next is the units section, which is where the DataCrux types called "units" are defined. The most common implementation of an app that uses DataCrux will have the same number of DataCrux units and database tables as it has custom data classes.

In the case of DataCruxNotebook, we have three custom data classes: Document, Category, and Project. Category and Project are child objects of a Document object. In the database, these relationships will be stored as matching foreign/primary keys. We'll start with the definition for the "Category" unit for simplicity:

  • category (dict)

    • components (dict)

      • cat_id (dict)
        • column: id
        • hint: __value__
        • keyPath: uniqueID

      • cat_name (dict)
        • column: name
        • hint: __value__
        • keyPath: name

    • primaryKey: cat_id
    • table: categories

So, we have a "category" DataCrux unit, which has two components, cat_id and cat_name. These are not the column names. At the moment, they're just used internally by DataCrux, so they could be any string. Note that the primaryKey property takes the component name (cat_id), not the database column name. The table property should be the name of the database table for this unit.

Now let's go through each of the keys for the "cat_name" component under the "category" unit.

+ column:
The name of the database column this unit's data will be stored in. For cat_name, we simply use the value "name."

+ hint: ( __value__ or __primary_key__ )
If the component represents a child object that is not an NSString, use primary key. Otherwise, use value. The name member is an NSString, so we use "__value__".

+ keyPath:
This is the Cocoa key-value coding keypath to the string value.
For example, cat_name specifies a keyPath of "name". This means that for the class Category, you need to have a KVC-compliant method for the member "name". In most cases, this will just be a method called "name." You could also use something like "names.common.first", as long as it's a valid KVC keypath.

Property List III: Child Objects

The project unit is identical to category except for column names, so let's skip right ahead to the document unit, which incorporates the idea of mapping child objects to foreign keys in the database.

  • document (dict)

    • components (dict)

      • doc_id (dict)
        • column: id
        • hint: __value__
        • keyPath: uniqueID

      • doc_contents (dict)
        • column: contents
        • hint: __value__
        • keyPath: contents

      • category (dict)
        • column: name
        • hint: __primary_key__
        • keyPath: name
        • unitName: category

      • project (dict)
        • column: name
        • hint: __primary_key__
        • keyPath: name
        • unitName: project

    • primaryKey: cat_id
    • table: categories

Some components have been clipped out here for space conservation, but the most important pieces are the components named "category" and "project". These represent Document's child objects. What is required to store data for a child object? Two things have to be provided in the component definition:

+ A hint property set to "__primary_key__".
+ A unitName property equal to the name of another unit defined elsewhere in the property list


So, putting it all together...

On the application side, the Document object has a child object, Category.

In DataCrux, the Document class has a unit definition (document) which contains a component called "category". The category component has a hint set to "__primary_key__" and a unitName property of "category."

This points to the Category class's unit definition, also called category, that we set up earlier. The category unit definition contains a property called primaryKey, which names cat_id as the component with the string value to use as a primary key for the Category object.

So, what this all means is that when a Document object is stored in the document table in the database, the category_id column will contain the value found at the Document's category.uniqueID keypath.


This really is all much simpler than it sounds. At this point, it's probably best to look at DataCruxNotebook's source code and com.treehouseideas.notebook.plist, and compare them to what you've read above. I'll work to get better configuration documentation available at some point, hopefully accompanied by tools to make everything easier. I could use help with both of these things.

Using the API

Once you have the database and relationships setup, you're ready to start storing data.

Whenever you use DataCrux, you first need to supply it with a formatID, which is simply the name shared by your database and property list file:

DataCrux *crux = [[DataCrux cruxWithFormatID:@"com.treehouseideas.DataCruxnotebook"] retain];

In DataCruxNotebook, I create this object on startup, and retain it until dealloc. Here are some examples of when requests are actually made to DataCrux. Creating a new blank record in database and generate a blank object in current scope:

- (IBAction)newItem:(id)sender
{
  id item = [[self crux] newItemOfType:@"document"];
  [self addDocument: item];
  [table reloadData];
}

Creating a new database record from a fully-populated object. No need to supply the DataCrux type, it is automatically determined from the class name:

- (IBAction)saveItem:(id)sender
{
  int row = [table selectedRow];
  id doc = [self documentAtIndex:row];
  [[self crux] storeItem:doc];
}

Here, changes are submitted to DataCrux based on data entered by the user into an NSTableView:

- (void) tableView:(NSTableView *)table setObjectValue:(id)object forTableColumn:(NSTableColumn *)column row:(int)row
{
  NSString * key = [column identifier];
  Document * doc = [self documentAtIndex:row];

  [doc takeValue:object forKey:key];
  [[self crux] updateItem:doc];
}

Conclusion

I realize this isn't a very in-depth tutorial, and assumes general understanding of SQL and such. I hope to address that in future releases of DataCrux and its documentation. I hope it's enough to get started for now. I'm very interested in any feedback on the docs or the framework itself.