section ActiveRecord section
Description
ActiveRecord in ActiveJS shares a similar API to the Rails implementation and includes lifecycle events, relationships, validations and REST persistence.
Setup
Before your models can be used you must call the connect method.
ActiveRecord.connect()
Because the connection might involve a network call, a ready event is
available which is triggered when ActiveRecord has finished loading all
data, synchronously or asynchronously.
ActiveRecord.observe('ready',function(){
});
Defining Your Model
ActiveRecord classes are created using the ActiveRecord.create method which takes three arguments: the name of the table that the class will reference, a field definition hash, and optionally a hash of instance methods that will be added to the class. The field definition hash should contain pairs of column names and default values.
var User = ActiveRecord.create('users',{
username: '',
password: '',
post_count: 0,
profile: ''
},{
getProfileWordCount: function(){
return this.get('profile').split(/\s+/).length;
}
});
Creating Updating and Destroying Records
CRUD operations are straightforward:
var jessica = User.create({
username: "Jessica",
password: "rabbit"
});
jessica.updateAttribute('password','rabbit123');
//or
jessica.set('password','rabbit123');
jessica.save();
//or
User.update(jessica.id,{
password: 'rabbit123'
});
jessica.destroy();
//or
User.destroy(jessica.id);
Getters & Setters
All column names that do not conflict with JavaScript keywords or ActiveRecord
methods are accessible via the columns' name. Attributes that conflict
are accessible via the get method. The set method must be used to set
a column's value**.
jessica.username // 'Jessica'
jessica.get('username'); // 'Jessica'
jessica.username = 'new username'; //INCORRECT
jessica.set('username','new username'); //CORRECT
Finding Records
If a field defintion hash was used to create your model, the model class
will automatically have findByX and findAllByX methods created for it.
User.findByUsername('Jessica');
User.findAllByPassword(''); //finds all with blank passwords
Otherwise you can use the base find method, which takes a hash of options,
a numeric id or a complete SQL string:
var posts = Post.find({
all: true,
order: 'id DESC',
limit: 10
});
var post = Post.find(5);
var post = Post.find({
first: true,
where: {
id: 5
}
});
var posts = Post.find('SELECT * FROM posts');
REST Persistence Setup
All location arguments described below can be either a string URI or an array containing:
- String URI
- String HTTP method (GET,POST,PUT,DELETE)
- Object HTTP params or Function returning Object HTTP params
To load data from a remote source the first argument should be a location:
ActiveRecord.connect('my_data_source.json');
//or
ActiveRecord.connect(['http://server/','POST',{session_id:1}]);
Data persistence back to a remote location must be specified per model /
per method. A hash of these specifications is passed as the second argument
to connect. The hash must be in the following format:
ActiveRecord.connect(location,{
ModelName: {
method: location
}
});
"method" can be any of the following keys, each taking a location argument:
- create
- batch_create
- update
- batch_update
- destroy
- batch_destroy
"batch" methods are used when multiple records or ids are passed to create,
update or destroy. If no batch locations are specified and multiple ids
/ records are passed to those methods, multiple HTTP requests will be
initiated.
URI strings in the location arguments can contain dynamic parameters specified with a colon. For instance: "http://server/user/:id.json"
In addition there are two keys that allow modifications of data as it is read or written from the remote source. These are independent of lifecycle callbacks (afterUpdate, etc) and outbound_transform will not modify local record attributes.
- inbound_transform: Function receiving an array of records.
- outbound_transform: Function receiving a single record.
A complete example for a single model would be:
ActiveRecord.connect('/data.json',{
User: {
batch_destroy: ['/users/batch.json','DELETE'],
destroy: ['/users/:id.json','DELETE'],
batch_create: ['/users/batch.json','POST'],
create: ['/users.json','POST'],
update: ['/users/:id.json','PUT'],
batch_update: ['/users/batch.json','PUT'],
inbound_transform: function(users){
for(var id in users){
users[id].modified_at += time_zone_offset;
}
},
outbound_transform: function(user){
user.modified_at -= time_zone_offset;
}
}
});
Server Request and Response Formats
All requests and responses to and from the server are JSON strings. The inital data load request should return a JSON payload in this format:
{
table_name: {
id: {
column: value
}
}
}
In the "User" example above, the server would implement the following request and response format:
| JS Method | HTTP Method | URI | Request | Response |
|---|---|---|---|---|
| User.destroy(batch) | DELETE | /users/batch.json | {Users:[ {column: value, ...}, ... ]} | [{column_name: value, ...}, ...] |
| User.destroy | DELETE | /users/:id.json | {User: {column: value, ...}} | {column_name: value, ...} |
| User.create(batch) | POST | /users/batch.json | {Users:[ {column: value, ...}, ... ]} | [{column_name: value, ...}, ...] |
| User.create | POST | /users.json | {User: {column: value, ...}} | {column_name: value, ...} |
| User.update | PUT | /Users/:id.json | {User: {column: value, ...}} | {column_name: value, ...} |
| User.update(batch) | PUT | /users/batch.json | {Users:[ {column: value, ...}, ... ]} | [{column_name: value, ...}, ...] |
Using REST Persistence
In additon to configuring your models to use REST persistence, the
persistence needs to be triggered. By default calling create, save, etc
will not persist data back to the server. In order to trigger the
persistence pass an extra argument of true, or a function callback to any
of the following methods:
- Class.create
- Class.update
- Class.destroy
- instance.updateAttribute
- instance.updateAttributes
- instance.save
- instance.destroy
When the server responds the local data will be automatically updated and the callback will receive the instance that was create / updated / destroyed. If it was a batch operation the callback will receive an array of instances.
var jessica = User.create({
username: 'Jessica',
password: 'rabbit'
},true);
jessica.set('password','rabbit123');
jessica.save(function(jessica){
//the instance will contain any modifications
//the server made to the attributes
});
Class & Instance Methods
Class or instance methods can be added to all ActiveRecord models by using
the InstanceMethods and ClassMethods objects.
ActiveRecord.ClassMethods.myClassMethod = function(){
//this === model class
};
ActiveRecord.InstanceMethods.myInstanceMethod = function(){
// this === model instance
};
Lifecycle
There are 10 supported lifecycle events which allow granular control over the lifecycle of your data:
- afterFind
- afterInitialize
- beforeSave
- afterSave
- beforeCreate
- afterCreate
- beforeUpdate
- afterUpdate
- beforeDestroy
- afterDestroy
beforeSave and afterSave are called when both creating (inserting) and saving (updating) a record. You can observe events on all instances of a class, or just a particular instnace:
User.observe('afterCreate',function(user){
console.log('User with id of ' + user.id + ' was created.');
});
var u = User.find(5);
u.observe('afterDestroy',function(){
//this particular user was destroyed
});
In the example above, each user that is created will be passed to the first
callback. You can also call stopObserving to remove a given observer, and
use the observeOnce method (same arguments as observe) method if needed.
Alternately, each event name is also a convience method and the following
example is functionally equivelent to the prior example:
User.afterCreate(function(user){
console.log('User with id of ' + user.id + ' was created.');
});
var u = User.find(5);
u.afterDestroy(function(){
//this particular user was destroyed
});
You can stop the creation, saving or destruction of a record by returning false inside any observers of the beforeCreate, beforeSave and beforeDestroy events respectively:
User.beforeDestroy(function(user){
if(!allow_deletion_checkbox.checked){
return false; //record will not be destroyed
}
});
Returning null, or returning nothing is equivelent to returning true in this context and will not stop the event.
To observe a given event on all models, you can do the following:
ActiveRecord.observe('afterCreate',function(model_class,model_instance){});
afterFind works differently than all of the other events. It is only available to the model class, not the instances, and is called only when a result set is found. A find first, or find by id call will not trigger the event.
User.observe('afterFind',function(users,params){
//params contains the params used to find the array of users
});
Validation
Validation is performed on each model instance when create or save is
called. Validation can be applied either by using pre defined validations or by
defining a valid method in the class definition. (or by both). If a record is
not valid, save will return false. create will always return the record,
but in either case you can call getErrors on the record to determine if
there are any errors present.
User = ActiveRecord.create('users',{
username: '',
password: ''
},{
valid: function(){
if(User.findByUsername(this.username)){
this.addError('The username ' + this.username + ' is already taken.');
}
}
});
User.validatesPresenceOf('password');
var user = User.build({
'username': 'Jessica'
});
user.save(); //false
var errors = user.getErrors(); //contains a list of the errors that occured
user.set('password','rabbit');
user.save(); //true
Relationships
Relationships are declared with one of three class methods that are available to all models:
- belongsTo
- hasMany
- hasOne
The related model name should be specified with a string:
User.hasMany('Comment'); //PREFFERED
Each relationship adds various instance methods to each instance of that model. This differs significantly from the Rails "magical array" style of handling relationship logic:
Rails:
u = User.find(5)
u.comments.length
u.comments.create :title => 'comment title'
ActiveJS:
var u = User.find(5);
u.getCommentList().length;
u.createComment({title: 'comment title'});
You can name the relationship (and thus the generate methods) by passing a name parameter:
TreeNode.belongsTo(TreeNode,{name: 'parent'});
TreeNode.hasMany(TreeNode,{name: 'child'});
//instance now have, getParent(), getChildList(), methods
has_and_belongs_to_many, and has_many :through are not yet implemented.


