How am I supposed to define my highly embedded JSON relationship?


#1

I have been struggling with EmberData and using the EmbeddedRecordsMixin to define a particular model relationship I’m using. I have complete control over both the server and Ember application, so I know I can change the format to make this work, but I don’t know the right way to get what I want.

I have a model called mealplan which contains an embedded array of recipe ids, which I want to be loaded automatically by Ember’s hasMany functionality.

Here is the structure of my data returned by my server:

meal_plan: {
      week: {
        1: { day: {
            0: { breakfast: [Number], lunch: [Number], dinner: [Number], snack: [Number] },
            1: { breakfast: [Number], lunch: [Number], dinner: [Number], snack: [Number] },
            2: { breakfast: [Number], lunch: [Number], dinner: [Number], snack: [Number] },
            3: { breakfast: [Number], lunch: [Number], dinner: [Number], snack: [Number] },
            4: { breakfast: [Number], lunch: [Number], dinner: [Number], snack: [Number] },
            5: { breakfast: [Number], lunch: [Number], dinner: [Number], snack: [Number] },
            6: { breakfast: [Number], lunch: [Number], dinner: [Number], snack: [Number] }
          } },
        2: { day: {
            0: { breakfast: [Number], lunch: [Number], dinner: [Number], snack: [Number] },
            1: { breakfast: [Number], lunch: [Number], dinner: [Number], snack: [Number] },
            2: { breakfast: [Number], lunch: [Number], dinner: [Number], snack: [Number] },
            3: { breakfast: [Number], lunch: [Number], dinner: [Number], snack: [Number] },
            4: { breakfast: [Number], lunch: [Number], dinner: [Number], snack: [Number] },
            5: { breakfast: [Number], lunch: [Number], dinner: [Number], snack: [Number] },
            6: { breakfast: [Number], lunch: [Number], dinner: [Number], snack: [Number] }
          } },
      }
}

and here is how I want this relationship to look like in Ember:

week: {
        1: { day: {
            0: { breakfast: DS.hasMany('recipe'), lunch: DS.hasMany('recipe'), dinner: DS.hasMany('recipe'), snack: DS.hasMany('recipe') },
            1: { breakfast: DS.hasMany('recipe'), lunch: DS.hasMany('recipe'), dinner: DS.hasMany('recipe'), snack: DS.hasMany('recipe') },
            2: { breakfast: DS.hasMany('recipe'), lunch: DS.hasMany('recipe'), dinner: DS.hasMany('recipe'), snack: DS.hasMany('recipe') },
            3: { breakfast: DS.hasMany('recipe'), lunch: DS.hasMany('recipe'), dinner: DS.hasMany('recipe'), snack: DS.hasMany('recipe') },
            4: { breakfast: DS.hasMany('recipe'), lunch: DS.hasMany('recipe'), dinner: DS.hasMany('recipe'), snack: DS.hasMany('recipe') },
            5: { breakfast: DS.hasMany('recipe'), lunch: DS.hasMany('recipe'), dinner: DS.hasMany('recipe'), snack: DS.hasMany('recipe') },
            6: { breakfast: DS.hasMany('recipe'), lunch: DS.hasMany('recipe'), dinner: DS.hasMany('recipe'), snack: DS.hasMany('recipe') },
        } },
        2: { day: {
            0: { breakfast: DS.hasMany('recipe'), lunch: DS.hasMany('recipe'), dinner: DS.hasMany('recipe'), snack: DS.hasMany('recipe') },
            1: { breakfast: DS.hasMany('recipe'), lunch: DS.hasMany('recipe'), dinner: DS.hasMany('recipe'), snack: DS.hasMany('recipe') },
            2: { breakfast: DS.hasMany('recipe'), lunch: DS.hasMany('recipe'), dinner: DS.hasMany('recipe'), snack: DS.hasMany('recipe') },
            3: { breakfast: DS.hasMany('recipe'), lunch: DS.hasMany('recipe'), dinner: DS.hasMany('recipe'), snack: DS.hasMany('recipe') },
            4: { breakfast: DS.hasMany('recipe'), lunch: DS.hasMany('recipe'), dinner: DS.hasMany('recipe'), snack: DS.hasMany('recipe') },
            5: { breakfast: DS.hasMany('recipe'), lunch: DS.hasMany('recipe'), dinner: DS.hasMany('recipe'), snack: DS.hasMany('recipe') },
            6: { breakfast: DS.hasMany('recipe'), lunch: DS.hasMany('recipe'), dinner: DS.hasMany('recipe'), snack: DS.hasMany('recipe') },
        } },
}

This hasMany relationship obviously doesn’t work because it’s not defined at the top level. My recipe model is simple enough:

export default DS.Model.extend({
  instructions: DS.attr('string'),
  name: DS.attr('string'),
  cooking_time: DS.attr('string'),
  recipe_yield: DS.attr('string'),
  featured_image: DS.attr('string'),
  ingredients: DS.attr(),
  program_phase: DS.attr(),
  meal_types: DS.attr()
});

The goal with this structure is to display a list of recipes for a given week of the user’s meal plan, such that something like:

mealplan.week["1"].day["0"].breakfast
mealplan.week["1"].day["0"].lunch
mealplan.week["1"].day["0"].dinner
mealplan.week["1"].day["0"].snack

will display my recipe models.

Mainly I am confused about how to properly embed the recipe ids using the structure I need. After reading through the JSON-API spec and looking at its relationships object specification, I don’t think my data can be formatted according to the proper spec. Like I said at the beginning, I have complete control over the server so I can change the structure of my data in order to better comply with JSON-API specifications if that’s what I need to do.


#2

While JSON API adapter can be really easy if you have an API that closely matches it (or you write your server from scratch with JSON API conventions) it’s probably not worth using in other scenarios. I’d recommend using the JSON adapter/serializer or the REST adapter/serializer. We use the REST one in our app.

If it were me, I’d probably normalize the relationships a little further. REST conventions and Ember Data are both built for more flat model structures so nested data like that can be challenging. The good(?) news is that Ember Data is highly customizable so you could do a lot of that normalization on the front-end and leave your server as is. The downside is that your server and client will differ a lot, you’ll need to do some pretty heavy Ember Data customization, and it may cause issues with PUT/POST operations (or at least make that a lot more complex).

I’m no authority on server-side design by any means but I think you would be making it a lot easier on yourself if your API had a flatter resource-oriented structure. 1) because that’s more conventional (AFAIK) and Ember Data and REST in general are suited to that model, so writing the front end will be much easier. 2) I think it can make read/write operations simpler on the server side (at least if you’re using a more traditional relational database).

Either way, once the data gets into Ember Data I would very strongly recommend keeping your models flat and using relationships to “nest” them, sounds like you’re already headed down that path, just thought i’d throw that out there.