Skip to content

Instantly share code, notes, and snippets.

@tomdale
Created December 11, 2012 23:05
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save tomdale/4263171 to your computer and use it in GitHub Desktop.
Save tomdale/4263171 to your computer and use it in GitHub Desktop.
Ember Data Read-Only API Proposal

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
@dmonagle
Copy link

This looks like a really good idea. I have run into the problem with "generated" attributes coming from Rails and not wanting Ember to attempt to send them back to the API. This would indeed address that issue.

@dmonagle
Copy link

@tomdale, have you given this any more thought? I think it will be a extremely useful and I hope it's still under consideration for the upcoming works with Ember Data!

@kevinansfield
Copy link

This proposal looks great. I'm finding I'm having to filter out a lot of additional params keys in my Rails controllers right now due to ember-data sending everything back over the wire.

@ssoroka
Copy link

ssoroka commented Jun 18, 2013

+1

@jetako
Copy link

jetako commented Jul 26, 2013

There are cases where I would like to modify an attribute without dirtying the record or including it in the payload, but I still want it to be deserialized. This proposal would meet my requirements if it allowed read-only attributes to be modified, but then the semantics of "readOnly" are muddied.

@i0n
Copy link

i0n commented Aug 9, 2013

Is the plan to integrate this with 'readOnlyKeys'? I like the way that works. If it marked keys that were generated by associations as readonly too that would be great.

@i0n
Copy link

i0n commented Aug 9, 2013

I just realised that readOnlyKeys is something I have added to my own custom serializer. I have it working nicely with attributes and associations, is there a branch for this already? I'm happy to submit a PR.

@kumavis
Copy link

kumavis commented Aug 27, 2013

👍

@KeKs0r
Copy link

KeKs0r commented Jun 3, 2014

@i0n did you submit a PR? I am looking for a solution for readOnly relations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment