RRM
Javascript ORM
Install / Use
/learn @Burgov/RRMREADME
RRM
This library will help you manage relations between entities in JavaScript.
To use, simply include rrm.js in your project and follow these steps:
Instantiate the ObjectManager
The very first thing you need to do is create a new ObjectManager instance:
var om = new ObjectManager();
Define your entities
For every entity you're going to use in your project, you need to define it's schema. Then you need to combine the class and the schema. The ObjectManager class provides a helper method for that. Here's an example of two schema's with relations to each other:
var ProjectSchema = function() {
this.id = new RRM.Property.Int('id', { writable: false, persistable: false });
this.start = new RRM.Property.Date('start', { writable: false, persistable: false });
this.name = new RRM.Property.String('name');
this.products = new RRM.Relation.OneToMany('products', { entityClass: 'product', backReference: 'project' });
}
var Project = function() {
}
ObjectManager.prepareEntity('project', Project, ProjectSchema);
var ProductSchema = function() {
this.id = new RRM.Property.Int('id', { writable: false, persistable: false });
this.name = new RRM.Property.String('name');
}
var Product = function() {
}
ObjectManager.prepareEntity('product', Product, ProductSchema);
Load the data
You're now ready to use the RRM. In order to load a Project into the RRM, you need to call:
var data = {
id: 3,
name: 'My project',
start: '2014-01-01T10:00:00+00:00'
products: [
{
id: 1,
name: 'Product 1'
},
{
id: 2,
name: 'Product 2'
}
]
}
var project = om.create('project', data);
Usage details
Defining properties
As you can see above, we're defining various properties on the Project and Product entities. There are quite some predefined property definitions which all have their own functionality. Also, some properties have additional options defined. These will be explained below the property list.
Properties
| Property | Function |
| ---------------------- | ------------------------------------------------------------------------------- |
| RRM.Property.Int | Will make sure the value passed to it is cast to an integer |
| RRM.Property.String | Will make sure the value passed to it is cast to a string |
| RRM.Property.Boolean | Will make sure the value passed to it is cast to a Boolean |
| RRM.Property.Array | Supports the loading of simple array data without the need to define a relation |
| RRM.Property.Object | Same as array, but for simple objects |
| RRM.Property.Date | Will cast the value into a Date object using the Date constructor |
Options
| Option | Default | Function |
| ------------------ | ------- | ------------------------------------------------------------- |
| readable | true | The value can be read |
| writable | true | The value can be updated |
| loadable | true | The value should be loaded from the data passed to create() |
| persistable | true | The value should be returned when calling toArray() |
Defining your own property type
Defining your own property type is done by extending one of the existing property types. Let's take the Int property
as an example:
// Int
RRM.Property.Int = function() {
RRM.Property.Base.apply(this, arguments);
};
RRM.Property.Int.prototype = Object.create(RRM.Property.Base.prototype);
RRM.Property.Int.prototype.constructor = RRM.Property.Int;
Don't pay too much attention to these first 5 lines. Just copy them as is and rename them as you wish. If you want to know more about what is happening here, read Introduction to Object-Oriented JavaScript
Now you have a basic property defined, but it doesn't do much interesting yet. It will just pass around the value you
put into it and fetch from it as-is. If you want to add some magic, define the transform and/or reverseTransform properties:
Object.defineProperty(RRM.Property.Int.prototype, 'transform', {
value: function(value) {
return parseInt(RRM.Property.Base.prototype.transform.call(this, value), 10);
}
});
(while it is not really necessary to call the base transform method now, it's good practice to do it still to be ready for a BC change in that part.)
Now our Int property will make sure any value passed into it is cast into an integer when the entity data is set. The
reverseTransform property functions mainly the same.
You can always call the transform or reverseTransform method of another property definition by calling for example:
Object.defineProperty(RRM.Property.Int.prototype, 'transform', {
value: function(value) {
var stringValue = RRM.Property.String.prototype.transform.call(this, value);
return parseInt(string, 10);
}
});
Keep in mind: transform is called when setting raw data to the entity (both when loading initially and when setting the
property later on). reverseTransform is called for example when you use the ObjectManager.toArray method to convert
the Entity back into a simple object.
Defining what property to use as "identifier"
By default, RRM will assume there is a property called id, and will use it as identifier. You can override this by defining
an id property on the schema constructor:
Object.defineProperty(ProjectSchema, 'id', { value: 'name' });
Now the name property will be used to identify the projects.
Relations
These are pretty self explanatory, so here's a simple list of supported relations:
RRM.Relation.OneToMany, RRM.Relation.ManyToOne, RRM.Relation.OneToOne, RRM.Relation.ManyToMany
These all support the same options as the simple properties.
The only relation to have a special option is RRM.Relation.OneToMany. It supports a backReference which you can use
to tell RRM to populate that specific property of the other side of the relation with the instance itself.
Basically it makes the following example possible, given the mapping and data examples from the top of this documentation:
var project = om.create('project', data);
console.log(project === project.products[0].project); // true
Storing data into the ObjectManager
As shown above you can store data into the ObjectManager using the create() method. If the entity already exists in
the ObjectManager, it will update that instance with the new data:
var product = om.create('project', { id: 7, name: 'test' });
console.log(project.name); // "test"
om.create('project', { id: 7, name: 'other test' });
console.log(project.name); // "other test"
Loading your data from the ObjectManager
You can fetch your data from the ObjectManager using either the get() method or the getReference() method. The first
one will fail if the object is not loaded yet, the second will create a Proxy object for you (see below).
var project = om.get('project', 1); // Project entity
var project2 = om.get('project', 2); // throws an error
var project = om.getReference('project', 1); // The same Project entity
var project2 = om.getReference('project', 2); // A Proxy object for Project with ID 2
Using the getAll() method you can fetch all the loaded entities of a specific class;
var projects = om.getAll('project');
Internal properties
Every entity is automatically populated with some internal properties.
$raw: this contains the raw, unprocessed data that was fed to RRM.$values: all the processed values accessible without touching getters and setters.$dirty: whenever a property is updated by other code than RRM, this is set to true. It is reset tofalsewhentoArrayis called on the entity.$writeCounter: whenever a property is updated, be it by RRM or by other code, this counter is raised by 1.
Hooks
You can define hooks on your entity object. These hooks will be called during the lifetime of an entity. Just define them as functions on your object prototype or in it's constructor:
$postCreate: after the entity is created and loaded with data usingom.create(), this function is called$postUpdate: after the entity is updated usingom.create(), this function is called
Working with proxies
In the example above we are loading the whole schema (Project and its Products) at once, but this might not always be necessary or possible due to performance issues. Imagine a Project with 1000 Products, which all have to be downloaded through an API. Instead, you can just load a list of ID's. These will then be converted to Proxy objects:
var data = {
id: 3,
name: 'My project',
start: '2014-01-01T10:00:00+00:00'
products: [ 1, 2, 3, 4, 5, 6 ]
}
var project = om.create('project', data);
Now, when you try to access a property of one of the Products, you will get an error. However, you do have access to the IDS.
console.log(project.products[0].id) // 1
console.log(project.products[0].name) // throws an error
This allows you to lazy load the data of

