JazzRecord: You Have The Power!

Since I recently held a conversation over IRC about some of how JazzRecord’s association loading and saving works, I thought I’d share it with the world as well.

The coolest two things about JazzRecord, and likewise Rails’ ActiveRecord:

* the fact they automatically load associated records as objects, based on models’ knowledge of interrelations with other models, and that associated records’ linkage is handled transparently to the user in such a way as to make total sense. The *databaseness* of working with things melts away to pure OO code.
* the fact the built in validations lend so much power and usefulness when it comes to business logic, without sacrificing readability of code.

In order to maintain the ease of use that ActiveRecord affords, I had to put JazzRecord together carefully. One thing I really wanted to maintain, which I’ve seen fall by the wayside in similar JavaScript ORMs, is the ability to simply assign new values to properties of a record object and have them be validated at save time, saved down as appropriate column/field data in the database, and even reload appropriate associated records from different tables. This requires that record objects have knowledge, or rather the ability to communicate with objects that *do* have knowledge (models), of interrelations between tables and knowledge of how to validate the properties.

Even knowledge of a record’s model and its associations is not enough to overcome the language barriers faced when porting a Ruby-language project to JavaScript: JavaScript function calls require the use of parenthesis, and because of this field name getters/setters cannot be treated as simple properties the way ActiveRecord field names are. To avoid having to unnecessarily clutter up API code with function calls for simple setting and getting of field data, JazzRecord introduces the idea that fields are simply properties of record objects – it just doesn’t care about any extraneous properties not defined within a record’s model declaration. At the time of save, and partially at the time a record is validated, a record’s data are examined in the context of those fields defined on the record’s model, and those associations defined in the same place. Any additional properties are simply ignored by JazzRecord. This stresses the importance of tying the record object to its basis, a model. Without the knowledge inherent in this connection, the record would have no way of loading associated data, and no means for validating field data. Without this connection, each record object would have to carry around its own set of validation rules and association-loading methods. And that would be slow.

An example may help to illustrate the ease of use/power combo better: Say we’re dealing with a *Home* model and a *Person* model. The relationship is 1 to N, where Person is the N end. Let’s say we also have a *Vehicle* model, which has a relationship of 1 to 1, with Vehicle belonging to Person. We’ve set up some nominal validation setup for ensuring a person’s age is an integer. Setup code will follow the usage examples.

Here’s a few things JazzRecord does or lets you do:

* Everyone knows finders: `var p = Person.findBy(”name”, “Nick”)`
* And getting and setting local column properties of the record is simple: `p.age //returns 28`, `p.age = “29yrs” //it’s my birthday!`
* Calling `isValid()` on a record object will return a boolean, as well as populating the error object with appropriate error information.
* Calling `p.save()` will also cause validation methods to run. Since this is an existing record, any *validatesAtUpdate* or *validatesAtSave* methods defined on the model will run.

Our record will fail to save because we’ve got errors: p.errors.age is an array of all errors relating to the *age* field, and it contains the helpful default string “age is not an integer”. This error message could easily be customized for use as actual presentational text to be used in an application to show users entering invalid data.

Correcting the age to a simple number and saving again will fix this and clear out the errors arrays: `p.age = 29; p.save()` This now succeeds, and indeed our p.errors object is re-initialized to an empty state.

Did you know JazzRecord will also reload all associated records? Didja even know it loaded them to begin with? Associations are easy to use. As soon as a finder returns a record, or as soon as a save finishes, your new record has not only the immediate field properties defined on its own model, but the associated data of any immediately-related records are loaded automatically. Here’s what this means:

* `p.vehicle` is an actual vehicle record. It has all the capabilities p has, such as retrieving/editing immediate fields and saving them back down. `p.vehicle.model` returns the string “Forenza”, and setting it `p.vehicle.model = “Corvette”` and saving `p.save()` will update the record accordingly. This is quite useful.
* if validations were defined for the vehicle record, they would be run prior to actually updating it.
* Records can be loaded more than 1 level deep by calling the `load(colname)` method of a record with unloaded subordinate records: `p.vehicle.load(”dealership”)`, perhaps, if a dealership association was defined and such data existed.

Upon loading the new data, all of the newly-loaded record’s immediate data is available for tinkering with, just as with the automatically-loaded association data.

Be careful! Loading associated records which are already present earlier in the scope chain may lead to unpredictable and undesirable results. Never do `p.vehicle.load(”owner”)`, for instance, as this will load a *second* instance of the same person record stored in *p*, and these two objects cannot be kept in sync. This is a language limitation and is unavoidable. **Do NOT load redundant records from earlier in the scope chain!**

The last piece of the powerful parts of JazzRecord puzzle (say that three times fast!) is auto-linking and unlinking of associations in the database based on logical user interaction in the API.

* A user can add or change a foreign-key reference manually: `vehic.person_id = 1`, or she can change it through the association property itself: `vehic.owner = Person.last()`. After calling `vehic.save()`, the association and the ID are both set correctly, no matter which was the triggering mechanism. Note that trying to assign both the key manually and the association can lead to confusion and is best avoided. If one has been set and you want to reset before setting the other, feel free to call the record’s *revert()* method. It will return to the state it was in after the last find or save operation.

* Likewise, deleting a foreign-key manually or deleting an association both work: `delete vehic.person_id` and `delete vehic.owner` work equally well. After calling `vehic.save()`, both association and foreign key will be deleted.

This is a useful concept. It allows the user to think in more abstract terms than setting keys all over the place. It works on all supported association types, even N to N. (it will delete and add records to mapping tables automatically) It *also* works from either side of an association. More on that in a second.

We’ve seen that deleting a record’s association when it “belongs to” another record is easy. But we can also link or unlink a record from the other side: `p.vehicle = Vehicle.find(3)` followed by a save will cause the original vehicle’s foreign key reference to disappear and the foreign key on the new vehicle (#3) to be set to the person’s ID. Again, powerful stuff. Deleting an association works the same: `h = Home.first(); delete h.people[0]; h.save()` causes the first Home record to be loaded, and removes the association of the first person object from the equation. The person object’s `home_id` will now be gone.

As for the models which were used in this article…

var Home = new JazzRecord.Model({
table: “homes”,
foreignKey: “home_id”,
hasMany: { people: “people”},
columns: {
address: “text”,
}
});

var Person = new JazzRecord.Model({
table: “people”,
foreignKey: “person_id”,
belongsTo: { home: “homes”},
hasOne: { vehicle: “vehicles”},
columns: {
name: “text”,
age: “number”,
home_id: “number”,
},

validate: {
atSave: function() {
this.validatesIsInt(”age”);
}
}
});

var Vehicle = new JazzRecord.Model({
table: “vehicles”,
foreignKey: “vehicle_id”,
belongsTo: { owner: “people”},
columns: {
make: “text”,
model: “text”,
person_id: “number”
}
});

More complex versions of these models are in the *example_models.js* file which comes in the zip file of source code for JazzRecord.

Working with the library functions almost identically to most general usage of Rails’ ActiveRecord, and this ease of use/identical behavior was a driving goal in its development. Because of its scope and methodology, actually sitting down to use JazzRecord may be the easiest way to learn it.

Post a Comment

Your email is never shared. Required fields are marked *

*
*