26 June 2013

Client-Side IDs with Ember Data

Why Client-Generated IDs?

In most client-side frameworks (e.g. Backbone, Angular, Ember) models created
in the web browser get temporary IDs (client IDs) and get their permanent
ID from the server after a successful save.

For client-heavy applications, generating IDs on the client can simplify
interaction with the server. Consider the case where the user creates a post
with several attachments. You’ll need to make sure that the post is saved and
has its ID resolved before attempting to save the child models in order to
preserve this relationship. Take a look at this Ember
issue
and you’ll start to
appreciate the complexity of this use case.

It can be liberating to know the ID of a model before it is saved. While
uploading an attachment, you could associate it with other models or send
someone a link to where the attachment will be. Users could work offline
and save records to the database later without worrying about how to resolve
and update IDs.

Because we’re using
UUIDs, you could
hypothetically find any record in your application given only an ID. I’ve not
seen this used for anything practical yet, but there could be interesting
possibilities if we kept a global store of IDs and their associated objects on
the client.

If you find yourself generating secret IDs for resources (think
account activation URLs) you may save yourself some code by using unique IDs.
Security-wise, it really can’t
hurt
.

A Brief History of (My Experience With) Client-Side IDs

I first encountered client-generated IDs when working with
Spine. Looking back at older
versions you can
see that Spine included a UUID
generator
.
Every model was was given a unique
ID upon creation
.

Spine Author, Alex Maccaw, was critical of Backbone’s
temporary client IDs
:

… a solution that Backbone uses is generating an internal cid (or
client id). You can use this cid temporarily before the server responds
with the real identifier… I’m not such a fan of that solution, so I’ve
taken a different tack with Spine.

However, in one commit that says it all (“add CIDs (inspired by
Backbone)”
)
Alex brought Spine inline with Backbone’s
server-generated ID strategy.

Despite this new direction, Spine remained
flexible
and is a great platform for
client-generated IDs. Backbone isn’t as friendly for client-generated IDs,
mainly because it uses the existence of a permanent ID to determine whether a
record has been
persisted
.
But, as Zed Shaw would say, Programming Motherf*er.

class App.Model extends Backbone.Model
  initialize: ->
    unless @id
      @set('id', utils.guid()) unless @id
      @_new = true
      @once 'sync', => @_new = false

  isNew: ->
    @_new

Client-Side IDs with Ember Data and PostgreSQL

Ember Data is rarin’ to go with client-generated
IDs
.
Override the dedicated method in the RESTAdapter and you’re golden.

DS.RESTAdapter.reopen
  generateIdForRecord: ->
    'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace /[xy]/g, (c) ->
      r = Math.random()*16|0
      v = if c == 'x' then r else (r&0x3|0x8)
      v.toString(16)

That code and the inclusion of a specification for client-generated IDs on
jsonapi.org
gives me confidence that
Ember will continue to be a good fit for this approach.

Even though we’re talking about client-generated IDs, there will be times when
the server needs to generate IDs of its own. If you’re using Mongo, you’re in
luck – it already uses unique IDs. Here’s how you can whip up some matching
unique IDs in PostgreSQL and Rails 4 with a couple of migrations.

# Enable UUID generation in PostgreSQL
class SetupDatabase < ActiveRecord::Migration
  def change
    enable_extension "uuid-ossp"
  end
end
class CreateDocuments < ActiveRecord::Migration
  def change
    # Use {id: :uuid} to use UUIDs as the primary key.
    create_table :areas, id: :uuid do |t|
      t.string :name
      # t.belongs_to :category will use an integer. You'll need to setup your
      # associations like this.
      t.uuid :category_id
    end
  end
end

Tradeoffs

I haven’t hit any major pain points with client-generated IDs yet but
here are some quick considerations before you dive in:

  • Model.first and Model.last won’t behave the way you expect in Rails, since
    the IDs may not be sequential.
  • There are some rumblings on the Internet about the impact of unique IDs relation join performance, but I couldn’t find anything conclusive.
  • URLs are uglier.

So give client-generated IDs a try if you’ve got a good use case. It can
go a long way to making a user experience less dependent on a round trip
to the server.

Want to learn the latest on Ember Data? Check our our updated Introduction to Ember.js class online to learn about Ember from its creators.

Heads up! This article may make reference to the Gaslight team—that's still us! We go by Launch Scout now, this article was just written before we re-introduced ourselves. Find out more here.

Related Posts

Want to learn more about the work we do?

Explore our work

Ready to start your software journey with us?

Contact Us