Skip to content

Instantly share code, notes, and snippets.

@bobey
Forked from tomdale/gist:4263171
Created February 18, 2013 13:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bobey/4977535 to your computer and use it in GitHub Desktop.
Save bobey/4977535 to your computer and use it in GitHub Desktop.

Read-Only

An important feature for Ember Data is providing helpful error messages when your application tries to do something that is forbidden by the data model.

To that end, we would like for the data model to be able to mark certain records, attributes, and relationships as read-only. This ensures that we can provide errors immediately, and not have to wait for a return trip from the adapter.

This also means that the adapter does not need to handle the case where an application developer inadvertently makes changes to a record that it does not conceptually make sense to modify on the client.

Requirements

Requirement 1: Read-Only Records

Consider a case where application configuration data will be sent along with a record. You can imagine an application where different available subscription levels are sent with the user:

{
  "firstName": "Patrick",
  "lastName": "Gibson",
  "availableSubscriptions": [
    {
      "name": "El Cheapo",
      "price": "99"
    },
    
    {
      "name": "El Jefe",
      "price": "499"
    }
  ]
}

The available configuration options are sent in the user payload, but are not editable on the client and should never be sent back to the persistence layer. In this sense, they are the same as the embedded content without server IDs (as described in Embedded Data), but should never be serialized when the parent is saved.

Requirement 2: Read-Only Attributes

Some record attributes may only be allowed to be populated by the server. For example, each record may contain an updatedAt attribute that describes when the last successful change was made. It would be inappropriate for the application to try to modify this.

While obviously the server would still need to enforce this constraint, it would be nice to be able to notify application developers immediately if they make a mistake.

Requirement 3: Read-Only Associations

For read-only associations, the associated record can be modified, but the association itself cannot be changed. For example, imagine a tweet API where each status embeds a user:

{
  "id": "9091832973546542908432132749873247",
  "status": "You guys, I just ate the best buffalo wings.",
  "user": {
    "id": "12345678910",
    "name": "ebryn",
    "avatar": "http://twimg.com/patrickstewart.jpg"
  }
}

Because the user here is globally addressible (it has an associated server ID), we can make changes to the user (like changing his avatar). But it would not be possible to change the user associated with the tweet after it has been posted.

In other words, the belongsTo property would become read-only and any attempt to modify it would raise an exception.

Similarly, a read-only has-many relationship would allow changes to the individual elements of the ManyArray, but no changes to it. In essence, the ManyArray would be an immutable Ember.Array rather than a mutable one.

Proposal

Currently, the serializer is responsible for materializing attributes and associations via materializeAttribute, materializeHasMany, and materializeBelongsTo.

This proposal adds a hook that serializers can use to specify that an attribute is read-only. The hash returned by this hook is passed to the record's materializeAttribute method.

App.Serializer = DS.Serializer.extend({
  optionsForMaterializedAttribute: function(type, attributeName) {
    if (attributeName === 'updatedAt') {
      return { readOnly: true };
    }
  }
});

// for 'updatedAt', the serializer will call:
// record.materializeAttribute('updatedAt', extractedDate, { readOnly: true })

As with the other hooks, there will be an analogous hook for has-many and belongs-to associations:

App.Serializer = DS.Serializer.extend({
  optionsForMaterializedBelongsTo: function(type, associationName) {
    if (associationName === 'user') {
      return { readOnly: true };
    }
  }
});

If a serializer wants to mark all attributes and relationships of a record as read-only, it can implement the materializeOptions hook:

App.Serializer = DS.Serializer.extend({
  materializeOptions: function(record, hash) {
    if (hash.read_only === true) {
      record.materializeOptions({ readOnly: true });
    }
  }
});

This allows the server to easily mark entire records as being read-only by including a property in the JSON payload.

Declarative Form

It will be very common to want to declare specific attributes and associations as read-only for a particular model, and it will be annoying to have to override the serializer hooks for those cases.

Instead, you can use the existing map API to mark an attribute or association as read-only:

DS.RESTAdapter.map('App.Tweet', {
  updatedAt: { readOnly: true, keyName: 'last_date' }
});

You can configure that an entire record is read-only via a new configure API:

DS.RESTAdapter.configure('App.Tweet', {
  readOnly: true
});

Configuring the primary key will also be done via the configure method to remove a "reserved word" from the map API. As a result, the map API's keys can be any attribute name.

DS.RESTAdapter.configure('App.Tweet', {
  primaryKey: '__legacy_id__'
});

Read-Only Attributes

If an attribute is read-only, any attempts to set it will raise an exception:

You attempted to modify the attribute "updatedAt" on the record <App.Post:ember357>, but
that attribute was marked as read-only by the adapter.

Read-Only Belongs-To Relationships

If a belongs-to relationship is read-only, any attempts to set is will raise an exception:

You attempted to modify the relationship "user" on the record <App.Post:ember357>, but
that relationship was marked as read-only by the adapter.

Attempts to modify the writable inverse of a read-only belongs-to relationship will fail. In other words, if one side of a relationship is marked as read-only, the other side implicitly becomes read-only.

Read-Only Has-Many Relationships

A read-only has-many relationship is an immutable Ember.Array. Any attempts to mutate it will raise an exception:

You attempted to modify the has-many relationship "links" on the record <App.Tweet:ember345>,
but this relationship was marked as read-only by the adapter.

Attempts to modify the writable inverse of a read-only has-many relationship will fail. In other words, if one side of a relationship is marked as read-only, the other side implicitly becomes read-only.

Read-Only Records

If an entire record is marked read-only, all its attributes and associations are read-only. Records that are marked as being read-only by the adapter have their isReadOnly flag set to true:

var otherUser = App.User.find(1234);
otherUser.get('isReadOnly'); // => true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment