Ember-Orbit is a library that integrates Orbit.js with Ember.js to provide flexibility and control in your application's data layer.
Ember-Orbit features:
-
Compatibility with any sources that implement interfaces from the Orbit Common library.
-
Stores that wrap Orbit sources and provide access to their underlying data as easy to use records and record arrays.
-
Stores that can be cloned (fully or partially) and edited in isolation, so changes can be either discarded or applied back to the original source.
-
Live-updating filtered record arrays and model relationships.
-
A data schema that's declared through simple model definitions.
-
The ability to connect any number of sources together with Orbit transform and request connectors.
-
Deterministic change tracking including forward, inverse and transactional changes.
Ember-Orbit is undergoing heavy development. Interfaces may change, so please use with caution.
Many capabilities of Orbit must still be accessed through Orbit objects that are not yet fully encapsulated by Ember-Orbit. There are plans to "Emberify" these aspects of Orbit so that they are more easily and consistently used from with Ember-Orbit.
Orbit and Ember-Orbit can be installed with Bower:
bower install orbit.js
bower install ember-orbit
A separate shim repo is maintained for Ember-Orbit's Bower releases.
For an Ember CLI application, see the instructions further below.
If you just want to use the global (non-AMD) builds of Orbit and Ember-Orbit, include the following scripts on your page:
<script src="/bower_components/orbit.js/orbit.js"></script>
<script src="/bower_components/orbit.js/orbit-common.js"></script>
<script src="/bower_components/ember-orbit/ember-orbit.js"></script>
These three files will provide the Orbit
, OC
, and EO
global namespaces,
respectively.
You may also want to include scripts that provide additional Orbit sources.
Configure Ember-Orbit with an application initializer that sets up Orbit and registers a "main" store and schema to be available in your routes and controllers:
App.initializer({
name: 'injectStore',
initialize: function(container, application) {
Orbit.Promise = Ember.RSVP.Promise;
application.register('schema:main', EO.Schema);
application.register('store:main', EO.Store);
application.inject('controller', 'store', 'store:main');
application.inject('route', 'store', 'store:main');
}
});
Source are very thin wrappers over Orbit sources. A source can be declared as follows:
LocalStorageSource = EO.Source.extend({
orbitSourceClass: OC.LocalStorageSource,
orbitSourceOptions: {
namespace: "myApp" // n.s. for localStorage
}
});
Orbit sources can be directly accessed in any instantiated EO.Source via
the orbitSource
property.
Stores extend sources and are also repositories for models. All of the data in a store is maintained in its internal source.
Stores are by default defined with the orbitSourceClass
set to an Orbit
memory source class: OC.MemorySource
.
Stores provide immediate (i.e. synchronous) access to its models through the following methods:
-
all
- returns all models of a specific type. For example:store.all('planet')
. -
filter
- returns a live record array of models of a specific type that are filtered by a filter function. -
retrieve
- returns model with a specific client or remote id. For example:store.retrieve('planet', '123')
orstore.retrieve('planet', {id: '1'})
.
You can make requests to stores through the following asynchronous methods, all of which return a promise:
find
add
remove
patch
findLink
addLink
removeLink
Stores themselves are promisified and resolve when all outstanding async
requests resolve. For example: store.then(function() { didComplete() });
It's often more convenient to call methods directly on records (i.e. model instances).
Your model definitions help define the central schema used by Orbit throughout your application.
Model definitions can use the following helpers:
key()
- keysattr()
- attributeshasOne()
- has-one relationshipshasMany()
- has-many relationships
Keys uniquely identify a record of a particular model type.
Every model must define a single "primary key", which will be used throughout
Orbit to identify records of that type uniquely. This should be indicated with
the field option primaryKey: true
.
If no primary key is defined on a model, a single primary key named id
will be
automatically generated. This key's defaultValue
will be set to the v4 UUID
generator used within Orbit.
When working with remote servers that do not support UUID primary keys, it's necessary to correlate Orbit IDs with IDs that are generated remotely. In order to support this scenario, one or more "secondary keys" may also be defined for a model.
Let's say that you want to track Orbit's primary key locally as a UUID named
cid
and also define a remote key named id
. You could define the following
keys in all models by modifying the base Model
class:
Model.reopen({
cid: key({primaryKey: true, defaultValue: uuid}),
id: key()
});
Note that Orbit can maintain different keys across different models, even in the same schema.
A relationship can optionally declare an inverse
, which should point to a key
on the related model. Orbit will ensure that relationships and their inverses
will be updated together.
Here's an example set of model definitions:
Star = Model.extend({
name: attr('string'),
planets: hasMany('planet', {inverse: 'sun'})
});
Moon = Model.extend({
name: attr('string'),
planet: hasOne('planet', {inverse: 'moons'})
});
Planet = Model.extend({
name: attr('string'),
classification: attr('string'),
sun: hasOne('star', {inverse: 'planets'}),
moons: hasMany('moon', {inverse: 'planet'})
});
Records can access properties and relationships through get
and set
.
Relationships are stored in proxy objects which will be kept in sync with
the underlying store's source data.
store.add("planet", {name: "Jupiter"}).then(
function(jupiter) {
jupiter.get("moons").pushObject(io);
jupiter.set("classification", "gas giant");
}
);
store.then(function() { // all requests resolve
console.log(io.get("planet.name")); // Jupiter - inverse relationship has resolved
});
You can also make requests to a model's underlying store through the following asynchronous methods, all of which return a promise:
remove
patch
findLink
addLink
removeLink
Note that working with a model synchronously via get
and set
is generally
more convenient than calling the above async methods.
A model's relationships can be re-queried by calling reload()
on a
relationship object. This triggers the associated findLink
call and returns
its promise. Related records will be updated through transformations to the
underlying source. Any changes should be reflected by the time the promise
resolves.
For example:
planet.get('moons').reload().then(function() {
console.log(planet.get('moons.length'));
});
Many aspects of Orbit are not yet encapsulated by Ember-Orbit and must be handled by working with the underlying Orbit sources directly.
Orbit sources can be directly accessed in any EO.Source
(or EO.Store
) via
orbitSource
.
Use sources to configure connectors, monitor transforms, and access remote servers.
First, remove ember-data
, which is configured by default:
npm rm ember-data --save-dev
bower uninstall --save ember-data
Next, add orbit.js
and ember-orbit
as dependencies in bower.json
and
install them by running this command.
bower install --save orbit.js ember-orbit
In your Brocfile
, import dependencies for Orbit and Ember-Orbit. It's
recommended that you import the named AMD ('.amd.js') files so that modules
may be used within ember-cli.
For example:
var EmberApp = require('ember-cli/lib/broccoli/ember-app');
var app = new EmberApp();
// Required Orbit imports
app.import('bower_components/orbit.js/orbit.amd.js', {
exports: {'orbit': ['default']}
});
app.import('bower_components/orbit.js/orbit-common.amd.js', {
exports: {'orbit-common': ['default']}
});
// Optional import of local storage source
app.import('bower_components/orbit.js/orbit-common-local-storage.amd.js', {
exports: {'orbit-common/local-storage-source': ['default']}
});
// Optional import of JSON API source and serializer
app.import('bower_components/orbit.js/orbit-common-jsonapi.amd.js', {
exports: {'orbit-common/jsonapi-source': ['default'],
'orbit-common/jsonapi-serializer': ['default']}
});
// Required Ember-Orbit import
app.import('bower_components/ember-orbit/ember-orbit.amd.js', {
exports: {'ember-orbit': ['default']}
});
module.exports = app.toTree();
Create an initializer for Ember-Orbit within your application's initializers
directory.
The following example creates a store backed by local storage:
import Ember from 'ember';
import Orbit from 'orbit';
import LocalStorageSource from 'orbit-common/local-storage-source';
import EO from 'ember-orbit';
import config from '../config/environment';
var LocalStorageStore = EO.Store.extend({
orbitSourceClass: LocalStorageSource,
orbitSourceOptions: {
namespace: config.modulePrefix // n.s. for localStorage
}
});
export var initialize = function(container, application) {
Orbit.Promise = Ember.RSVP.Promise;
application.register('schema:main', EO.Schema);
application.register('store:main', LocalStorageStore);
application.inject('controller', 'store', 'store:main');
application.inject('route', 'store', 'store:main');
};
export default {
name: 'inject-store',
initialize: initialize
};
You can customize functions used to pluralize/singularize model and field names:
var Schema = EO.Schema.extend({
pluralize: function(word){
// information is irregular
return word === 'information' ? 'information' : word + 's';
},
singularize: function(word){
// information is irregular
return word === 'information' ? 'information' : word.substr(0, word.length - 1);
}
});
Customization of ID fields and generators should be done on individual models.
In addition to this documentation, you may find it helpful to see an example application that works works with Ember-Orbit and Ember-CLI:
https://github.com/opsb/ember-orbit-todos
Install the CLI for Broccoli globally:
npm install -g broccoli-cli
Install the rest of ember-orbit's dependencies:
npm install
Distributable versions of ember-orbit can be built to the /build
directory
by running:
npm run build
ember-orbit can be tested in CI mode by running:
npm test
Or directly with testem (useful for configuring options):
testem ci
ember-orbit can be tested within a browser (at http://localhost:4200/tests/) by running:
npm start
Or directly with broccoli
(useful for configuring the port, etc.):
broccoli serve
Install yuidoc globally:
npm install -g yuidocjs
Generate docs in the /docs
directory:
yuidoc .
Ember-Orbit owes a great deal to Ember Data, not only for the design of several interfaces but also for the base code used to manage record arrays.
It is hoped that, by tracking Ember Data's features and interfaces where possible, Ember Orbit will also be able to contribute back to Ember Data.
Copyright 2014 Cerebris Corporation. MIT License (see LICENSE for details).