Billy Data is not as lightweight as Ember Model or Ember Resource. It is a full data framework. I'm pretty sure it can do at least 95% of what Ember Data can do, except for custom adapters/serializers. Here are some important differences and a couple of features I find useful.
Filtered record array that updates live. Also works with sorting, so that records are inserted at the correct position. Say you have a filtered record array of Person records with name "Tom" ordered by name. Anytime a Person record changes its name
property, it is checked against all filtered record arrays that "observe" that property. Then it will be either added or removed. And if you change your name to Atom Dale
, your Person record will be moved to the top of the list.
Sparse array support. A record array can query the server and get e.g. the first 100 records and a totalCount
. The length
property will be set to the totalCount
from the response, and every time an index is requested the array loads 100 records around that index, unless they're already loaded of course. Records can still be created locally which will just insert them at the correct spot in the sparse array, and increment the length
property. This works beautifully with 10,000s of thousands of records in an Ember.ListView.
Full relational functionality with belongsTo
and hasMany
. All relationships update automatically, since a hasMany is simply a filtered record array filtered by the parent attribute. It doesn't involve that much code, and it still works with rollbacks and deleted records.
Ability to tell the store that all records of a specific type has been loaded, e.g. App.Contact.loadAll([{…}, …]
. This way future calls to App.Contact
will not send a server request, but can filter the records locally and instantly.
When our app starts up it calls GET /user/bootstrap
from the server. The server will check how many records exists for each of invoices, accounts, contacts etc. and send all of them if there is less than 100 of a particular type (which will be true for most users).
This feature really helps speeding up our app. Many route transitions are instant, since the necessary data is already in memory.
Save a record with altered properties to the server without actually changing the properties in the front-end (yet). Once the server responds with a 2xx code, the properties will be set in the client. This is very useful if you fx have a filtered list of state=draft
invoices. When approving the invoice, you don't want it to be removed from the draft list before we are 100% sure that it got saved on the server. Works by calling invoice.save({properties: {state: 'approved'}})
.
Call save
directly on a single record and get a promise back that you can get the response from when returned from the server. No need for transactions if you just want to save a specific record.
More flexibility with embedded records. Let's say we have App.Invoice
and App.InvoiceLine
. We need to be able to load invoices by themselves (i.e. no lines embedded). We need to be able to save the invoice by itself. We also need to be able to save the invoice with its lines embedded (needs to be db transaction on the server). So in Billy Data, when calling save
on a model, you can choose which embedded relationships you'd like to include in the request. Example: invoice.save({embed: ['lines']})
. In, at least a previous version of, Ember Data you either embedded the records always or never. It also wasn't possible to load an invoice without its lines embedded, and then load the lines later.
Support for anonymous records, which is really useful when you want to POST
data to a "special" endpoint. An example could be a user login form. The App.User
does for obvious reason not have a password
field on it. Instead you can create an anonymous record, populate it with data, and call save
on it - just like a normal record. Here is our login window controller for when a user's session expires:
App.LoginWindowController = Em.ObjectController.extend(App.ui.WindowController, {
model: function() {
return BD.AnonymousRecord.createRecord({
email: App.session.get('user.email'),
password: '',
remember: false
})
}.property(),
submit: function() {
var self = this;
this.get('model').save('/user/login').then(function() {
var callback = self.get('callback');
if (callback) {
callback();
}
self.close();
});
}
});
With BD.AnonymousRecord
all error handling, JSON transforms etc. is automatically handled.
Deleting records does also not require a transaction. You can call deleteRecord()
on any record. If the server request fails, the record is rolled back and is not in an error state. This avoids conflicts when DELETE
requests fail (which they will).
More simple states, which makes sure that records are never stuck in e.g. an error
state. Currently the states are managed by some boolean properties (isCreated
, isLoaded
, isDirty
, isDeleted
). It might make more sense to use a state machine, but it does help to keep it very simple and efficient.
I realize that many of my points are specific to our app, and there are definetely more use cases that needs to be taken into consideration when architecting a one-size-fits-all data framework. These are just my thoughts on what makes it difficult using Ember Data. Let's talk!