Ember-Data: How do "mappings" work

MilkyWayJoe picture MilkyWayJoe · Aug 29, 2012 · Viewed 8.1k times · Source

I'm currently trying to put something together with ember + emberdata + router + asp.net web api. Most of it seem to work, however I stuck in an error message I get when ember-data tries to findAll through the adapter for my models.

In my backend I have a model like this (C#):

public class Genre {
    [Key]
    public int Id { get; set; }
    [Required]
    [StringLength(50, MinimumLength=3)]
    public string Name { get; set; }
}

Which in my app I represent it like this using ember-data:

App.Genre = DS.Model.extend({
    id: DS.attr("number"),
    name: DS.attr("string")
}).reopenClass({
    url: 'api/genre'
});

I have also a Store defined in my App using the RESTAdapter like so:

App.store = DS.Store.create({
    revision: 4,
    adapter: DS.RESTAdapter.create({
        bulkCommit: false
    })
});

And the store is used in my controller as below:

App.GenreController = Ember.ArrayController.extend({
    content: App.store.findAll(App.Genre),
    selectedGenre: null
});

The router is defined as

App.router = Em.Router.create({
    enableLogging: true,
    location: 'hash',
    root: Ember.Route.extend({
        //...

        genre: Em.Route.extend({
            route: '/genre',
            index: Ember.Route.extend({
                connectOutlets: function (router, context) {
                    router.get('applicationController').connectOutlet('genre');
                }
            })
        }),

        //...
    })
})

When I run my application, I get the following message for every object that has this same structure:

Uncaught Error: assertion failed: Your server returned a hash with the key 0 but you have no mappings

For reference, here's the json the service is returning:

[
  {
    "id": 1,
    "name": "Action"
  },
  {
    "id": 2,
    "name": "Drama"
  },
  {
    "id": 3,
    "name": "Comedy"
  },
  {
    "id": 4,
    "name": "Romance"
  }
]

I cannot tell exactly what the problem is and since the assertion is mentioning that I need mapping, I'd like to know:

  1. What this mapping is and how to use it.
  2. Since the returned json is an array, should I be using a different type of controller in my app ,or is there anything I should know about when working with this type of json in ember-data? or should I change the JsonFormatter options in the server?

Any help is welcome.

I can definitely add more information if you feel this isn't enough to understand the problem.

EDIT: I've changed a few things in my backend and now my findAll() equivalent action in the server serializes the the output as the following json:

{
  "genres": [
      { "id": 1, "name": "Action" },
      { "id": 2, "name": "Drama" },
      { "id": 3, "name": "Comedy" },
      { "id": 4, "name": "Romance" }
   ]
}

But I still can't get it to populate my models in the client and my error message has changed to this:

Uncaught Error: assertion failed: Your server returned a hash with the key genres but you have no mappings

Not sure what else I might be doing wrong.

The method that throws this exception is sideload and checks for the mappings like this:

sideload: function (store, type, json, root) {
        var sideloadedType, mappings, loaded = {};

        loaded[root] = true;

        for (var prop in json) {
            if (!json.hasOwnProperty(prop)) { continue; }
            if (prop === root) { continue; }

            sideloadedType = type.typeForAssociation(prop);

            if (!sideloadedType) {
                mappings = get(this, 'mappings');
                Ember.assert("Your server returned a hash with the key " + prop + " but you have no mappings", !!mappings);
//...

This call sideloadedType = type.typeForAssociation(prop); returns undefined and then I get the error message. The method typeForAssociation() checks for the for 'associationsByName' key which returns an empty Ember.Map.

Still no solution for this at the moment.

By the way...

My action is now like this:

    // GET api/genres
    public object GetGenres() {
        return new { genres = context.Genres.AsQueryable() };
    }

    // GET api/genres
    //[Queryable]
    //public IQueryable<Genre> GetGenres()
    //{
    //    return context.Genres.AsQueryable();
    //}

I had to remove the original implementation which gets serialized by json.NET as I could not find config options to produce a json output as Ember-Data expects ( as in {resource_name : [json, json,...]}). Side effect of this is that I've lost built-in OData support, but I'd like to keep it. Does anyone know how could I configure it to produce different json for a collection?

Answer

sly7_7 picture sly7_7 · Aug 31, 2012

The mapping can be defined in the DS.RESTAdapter. I think you could try to define something like this:

App.Store = DS.Store.extend({
  adapter: DS.RESTAdapter.create({
    bulkCommit: true,
    mappings: {
      genres: App.Genre
    },
    // you can also define plurals, if there is a unregular plural
    // usually, RESTAdapter simply add a 's' for plurals.
    // for example at work we have to define something like this
    plurals: {
      business_process: 'business_processes' 
      //else it tries to fetch business_processs
    }
  }),
  revision: 4
});

Hope this resolves your problem.

Update:

At this time, this is not well documented, I don't remember if we found it by ourself reading the code, or perhaps Tom Dale pointed on it.
Anyway, here is the point for plurals For the mappings, I think we were driven by the same error as you, and either we tried, either Tom teached us about this.