Model — JazzRecord JavaScript ORM Documentation
Docs Sections
The Model class is the primary facilitator in JazzRecord, and as such has a rich set of features. Chief among them are the ability to create new tables (if using automigrations), associate models with one another, and make queries on these tables. It also provides the vector for enabling your Record objects with validation, custom methods, and callbacks.
The constructor for Model takes an options object which has a large number of options:
{
// required data
table: "",
columns: {},
// association data
foreignKey: "",
hasOne: {},
belongsTo: {},
hasMany: {},
hasAndBelongsToMany: {},
// events
events: {},
// custom finders/methods
modelMethods: {},
recordMethods: {},
// validation function
validate: $empty
}
Required Parameters
The constructor's table and columns options parameters are not optional. These two pieces enable JazzRecord to properly create the table (if it does not already exist) and to allow the Model instance to query the appropriate table with knowledge of the columns and types therein.
table is simply a string specifying the name for the table in the database.
columns is an object literal containing matched pairs of column names and column types, with types specified as strings:
columns: {
name: "text",
age: "number",
home_id: "number",
income: "float",
has_vehicle: "bool"
}
The following data types are currently available as column types in JazzRecord:
Optional Parameters
The rest of the options parameters for Model constructors are not strictly necessary to enable queries to a model's table, but are useful for building associations, setting up callbacks, and enabling validation of data prior to saving.
Associations
The following group of options (denoted in the code above with the comment "association data") are used for setting up associations with related Model objects.
The one unique property here is foreignKey, and its purpose is to define the name by which any associated models will store the current model's primary key (the automatically-created column "id").
The remaining association properties are object literals, like columns. These are taken mostly verbatim from ActiveRecord. Each of them contains matching pairs of "association names" (the name by which you will refer to sub-objects loaded into a Record generated by this Model) and the table name through which each association occurs:
foreignKey: "person_id",
hasOne: { home: "homes"},
hasMany: { vehicles: "vehicles"}
Their individual meanings are as follows:
hasOne: This record has a single associated record which references it. This record's foreign key exists as a column in the associated record.belongsTo: This record is associated with another record by a reference stored on this record. The associated record's foreign key exists on this record.hasMany: This record has [potentially] many associated records which reference it. This record's foreign key exists as a column in the associated record(s).hasAndBelongsToMany: This record is associated with [potentially] several records by means of an intermediary (mapping) table, which allows multiple records to be associated in both directions.
The last of these will lead to the generation of a mapping table, the name of which is a concatenation of the two associated table names in alphabetical order and separated by a space.
All of these association properties are accessed during queries to the model's table, and their definition is what allows the query to "walk the tree" of associated records and load their data appropriately.
Callbacks
Callbacks, or event handlers, allow you to specify that certain code is executed at certain points when a record is performing a certain action. These callback functions are specified in the form of a literal object (events) with matching pairs of event names and function literals. The function assigned to an event name will be performed at the time specified by that event name.
The following event hooks are available, and perform the assigned tasks at the time specified:
onDestroy: Before the record is destroyed.onSave: Before the record is saved (brand new or existing)onCreate: Before the record is saved (brand new).onUpdate: Before the record is saved (existing).
Validation
Validation is a means for preventing dirty data from cluttering your database. The optional validate parameter in the model constructor is a function literal which will be called before saving a record. Several validation methods are predefined on the Record object, and the user may specify custom requirements as well. Calls to the built-in validation methods, as well as custom requirements, should all be processed within the function literal passed in during Model definition. This will then be called by the Record instance before it can be successfully saved.
If any of the validation methods fails (meaning the record's data does not pass the requirements set forth in the model definition) it will push a string onto the record's errors array, and the record will not be saved until the data is corrected so that it passes validation.
The following validation methods are available:
validatesAcceptanceOf(column, customErrorText): Ensures your value is a boolean evaluating to true. Useful for TOS Agreements, etc.validatesConfirmationOf(column, customErrorText): Used to ensure two values are equal. Useful for confirming user passwords, etc.validatesExclusionOf(column, values, customErrorText): Used to check that a value does NOT contain any specified keywords.validatesInclusionOf(column, values, customErrorText): Used to check that a value containsat leastone-of-many, specified keywords.validatesLengthOf(column, options, customErrorText): Used to check the length of a string. Supports Minimum, Maximum, Is -or- Exactly, and allow-nill.validatesNumericalityOf(column, customErrorText): Used to check if value is numeric. (Int, Number or Float)validatesFormatOf(column, regex, customErrorText): Used to validate a value against a user defined regex.validatesPresenceOf(column, customErrorText): Used to check if value is null or empty, Returns false if null or empty.validatesUniquenessOf(columnName, value, customErrorText): Checks an entire database table to ensure a field contains a unique value. Useful for ensuring a username is unique.
Generic Validations
validateIsString(column, customErrorText): Validates that a value is a string.validateIsBool(column, customErrorText): Validates that a value is a boolean.validateIsInt(column, customErrorText): Validates a value is an int.validateIsFloat(column, customErrorText): Validates a value is a float.
Validation Examples
Validations are defined in each model, which supports three different validation events.
atSave: These validations are executed before a record is saved.atUpdate: These validations are exectuted only when a record already exists, and it's data has been modified.atCreate: These validations are executed upon the creation of a record.
For example, say you have defined this model:
var Programmer = new JazzRecord.Model({
table: "programmer",
columns: {
name: "text",
income: "float"
},
validate: {
atUpdate: function() {
this.validatesIsString("name", "You must have a name!");
this.validatesIsFloat("income" "We will glady pay you a null salery!");
},
atSave: function() {
this.validatesIsString("name", "You must have a name!");
this.validatesIsFloat("income" "We will glady pay you a null salery!");
}
}
});
Now, we have defined a few validations for our model, specifically we have told it: Before anything is saved, or updated, "Name" must be a string, and "Income" must be a float.
If either one of these conditions are not met, the record will not save. So how can you tell if the record did not save? Well, save() will return false, of course.
Your errors array will also be populated with which validations failed. In this case, the error list would contain: "You must have a name!", and "We will gladly pay you a null salery!".
Note: You can access the errors array by simply refering to it via your model.
programmer = Programmer.first(); programmer.salery = null; if ( ! programmer.save() ) { $each(programmer.errors, function(error) { alert(error); }); }
Further Validation Examples
"ValidatesExclusionOf()" ` validate: { atSave: function() {
bannedUserNames = Array("admin", "moderator","administrator");
this.ValidatesExclusionOf("username", bannedUserNames, "Sorry! You are not a site admin!");
} } ` In this example, we have defined a validation, which disallows a user from picking the usernames: administrator, admin, or moderator. This is a very simple example of how this method could be used in a practicle way.
"ValidatesInclusionOf()" ` validate: { atSave: function() {
AllowedExtension = Array("jpg","gif","png");
this.ValidatesInclusionOf("fileExtension", AllowedExtension, "Sorry, only: jpg, gif and png's are supported.");
} } ` Pretty much the exact opposite of validatesExclusionOf. Here we simply define a list of values which are required in our value. Note: only 'one' of the values must be equal to the columns value in order to pass this validation.
"validatesLengthOf()" ` validate: { atSave: function() {
lenOptions = {
minimum: 1;
maximum: 15;
};
this.validatesLengthOf("username", lenOptions, "Sorry, your username must be between 1 and 15 characters long.");
}
}
ValidatesLengthOf` accepts an options param, which allows for a good amount of control over string lengths. Here, for example, we have specified that a username can be
no less than 1 character in length, and no more than 15 characters in length, or this record will NOT be saved.
validatesLengthOf also recognises these options:
* minimum: length: Sets the minimum allowed amount of characters.
* maximum: length: Sets the maximum allowed amount of characters
* is: length: String must be exactly N characters.
* exactly: length: - Alias of is
* allow_nil: true or false: - Allows you to define if a columns value may or may not be null.
More documentation of validations will come soon, I think most of the difficult ones have been covered here.
See the documentation for validation on Record objects for more on this topic.
Finders
Querying the model's table is accomplished by calling a model's finder methods. These are taken, again, directly from ActiveRecord. Finders in JazzRecord all preload associated records as sub-objects of the primary returned record object. Sub-objects are preloaded to a depth governed by the configuration option, but can be optionally overridden per finder call if necessary.
The available finders are as follows:
find(id), find([ids]), find(options): Finds one or more records matching the passed-in ID or array of IDs. If passing in an object literal, the object requires an id property.findBy(colName, value, depth): Finds a single record whose column (name matching the first parameter) holds the value of the second parameter. Returns the first match. Optional third parameter specifies depth of association-loading.all(options): Returns all records in the tablefindAllBy(colName, value, depth): Finds an array of records whose column (name matching the first parameter) hold the value of the second parameter. Returns array of all matches. Optional third parameter specifies depth of association-loading.first(options): Finds the first record in the table.last(options): Finds the last record in the table.count(conditions): Returns the number of records stored in the table.
All of the finders except findBy(), findAllBy(), and count() accept an options object, which allows you to modify the query significantly. options is an object literal with any of these parameters:
select: Modify theSELECTclause for the query. Alters which columns are actually returned from the query.conditions: Modify theWHEREclause for the query. Alters conditions for returning data.order: Modify theORDERclause for the query. Alters the sort order/sort column for the query.limit: Modify theLIMITclause for the query. Alters the number of records returned.offset: Modify theOFFSETclause for the query. Alters the starting record returned.group: Modify theGROUPclause for the query. Groups all results across specific column data.depth: (unique to JazzRecord) Alters the depth of the association-loading. By default this is determined by the configuration option. Setting to 0 will result in only first-order data being retrieved for the record returned.
Calls to findBy(), findAllBy(), and count() accept variations on this idea. findBy() and findAllBy() both accept an optional depth parameter, and calls to count accept an optional conditions parameter.
Example calls with optional parameters:
var jesse = Person.first({conditions: "name LIKE '%es%'"});
//returns Jesse's record.
var nickWithNoCar = Person.findBy("name", "Nick", 0);
//returns Nick's record, but doesn't load associated records
var numberOfvehicleOwners = Person.count("has_vehicle = 1");
//3 folks have cars.
The results of a call to any of the finders is a Record object or an array of Record objects, depending on the particular finder and its parameters.
Custom Finders/Methods
Passing in object literals with member functions defined for modelMethods and recordMethods in a model's constructor can provide the model and/or records of the model with custom finders and other methods.
Any functions defined in a modelMethods object literal will be copied into the model itself, and so will recordMethods be copied into records of the model.
This can be used to provide custom finders for the model. Such methods would allow for custom conditions and avoid repeat calls to standard finders with conditions parameters.
Likewise, any record returned or created by a model will have access to methods defined in recordMethods. These methods can perform any task, but are especially suited to processing data unique to each individual record.
An example might help to illustrate the purpose of custom methods:
Animal = new JazzRecord.Model({
table: "animals",
foreignKey: "animal_id",
columns: {
name: "text",
species: "text",
say: "text"
},
modelMethods: {
findTiger: function() {
return this.findBy("species", "tiger");
}
},
recordMethods: {
speak: function() {
return this["say"];
}
}
});
In this example, findTiger is a custom finder on Animal, which returns only those animals which are tigers. You could also provide more sophisticated SQL WHERE clause conditions, matching on multiple columns, for instance.
Also in this example, speak is a custom method provided on records of the Animal model. Calling speak on an animal will cause the animal to return its say string. This is a trivial example, but in the real world the model could cause changes to DOM, trigger additional querying on the database, etc.
Creating new Records
The Model class provides two methods for creating new Record objects: newRecord() and create():
newRecord(options)creates a record object. If an options object literal is passed, it will also intitialize the record by copying in data for the columns specified in the options object. You can also pass in callbacks in aneventsobject literal within the options object as specified above in the Model constructor explanation. These callbacks will only be applied to the returned Record instance.create(options)creates a new object, saves it and returns it, in one step. All options available on newRecord() are also available.
Destruction Unleashed
Three destructive methods are provided for calling on the model object.
destroy()destroys a single record or a set of records when passed a single ID or an array of IDs.destroyAll()destroys all records in the model's table.dropTable()drops the model's table itself.









