Merge multiple record partial data into unique model

Hi, I’m querying multiple endpoints which each are returning part of a model.'s data :

model: function() {
		const id = this.paramsFor('users.user').id;
		const promises = {
			a: this.get('store').queryRecord('mymodel', {
				id: id,
				endpoint: 'a'
			}),
			b: this.get('store').queryRecord('mymodel', {
				id: id,
				endpoint: 'b'
			})
		};

		return hash(promises);
	},

Is there a way of getting a model which concats the data of all the endpoints instead of having to deal the “alias” of each query like : this.model.a.xxxx

Note : I’m using ember 3.14 Note 2 : I’m quite new to the ember world , so maybe i asking something that shouldn’t be done

Thanks :slight_smile:

Hi @grunk, welcome! There are a lot of answers and the right one really depends on your code and APIs, as well as what you’re trying to do.

First let’s make sure we’re disambiguating the term “model” because this can muddy the waters. An Ember Data “Model” is a class that represents the “shape” of a resource. A resource being (typically at least) “a row in a database”. That’s not to say that your models must have 1:1 correlation with your database but Ember Data is designed around the idea of models/resources and if your data doesn’t fit that shape you end up contorting your code. A route “model” on the other hand is just the results of the route’s model hook, and that could be quite literally anything: ember data models, fetch results, raw JSON, anything you want (usually something “thenable” so it can be waited on). The hash method you’re using above is very typical when a route is loading more than one thing at a time. The route model hook should return a single value so the hash is a way to wait on them in parallel and also group them together into a single value.

The first question you should answer is: is Ember Data the right choice for this situation? If not you can just use fetch and easily combine the results of the queries in a simple hash. Do you need to manage these queries using the fancy features of Ember Data records? Do they ever get modified and written back to your APIs? Do they need to be cached in the store? Do you already have heavily customized serializer(s) and adapter(s)? If it’s just a simple query => render scenario you might save yourself some time and trouble by not using Ember Data at all.

If Ember Data is the right choice then the next logical question is “why treat these things as a single model”? Clarifying the architecture is important. If you’re going to jump through hoops you want to make sure it’s justified. When you say “part of a models data” do you mean “parts of data which together make up a single resource”? Or do you simply mean that the route model hook just needs to return multiple things?

If the former then you may want to consider something fancier, like a custom store method or store-like service which fetches and assembles the data and pushes it into the store separately (but this depends entirely on what you want to do with it).

If it’s simply the latter, and you want multiple Ember Data records returned from the same model hook honestly you’re already doing the most conventional thing and should just keep doing that. You’ve just got one more segment in your value paths, which is a little more to type, but it’s also declarative. Don’t get too hung up on the word “model” here.

Anyway hope that helps more than it hurts :laughing: Let me know if you have any follow up questions or want to dig in on your scenario a little bit more.

2 Likes

Thank you for this exhausitve response , i 'll try to adress every part. When i talk about a model , i’m indeed talking about a class that represent a resource stored by my backend. By “Part of a model” data i mean parts of data which together make up a single resource.

To give you a litlle bit more context, let’s imagine that i have this model (the reality is way more complex, but you’ll get the idea):

DS.Model.extend({
    firstname: DS.attr('string'),
    lastname: DS.attr('string'),
    email: DS.attr('string'),
    phone: DS.attr('string')
})

I have 2 endpoints for user , one returning the names the other one returning contact infos (email + phone). My UI is composed of 2 tabs , one displaying the names , the other one displaying the contact info. I need to add a third tab which will display all the data of the model hence the use of hash and the call to the 2 endpoints.

I was hoping to get a full model (with all its data) from the route of this new tab by merging the result of the 2 queryRecord call in order to :

  • Avoid setting an extra parameter in my controller :
setupController: function(controller, model) {
		this._super(...arguments);
		controller.set('a', model.a);
		controller.set('b', model.b);
}

No big deal here , i just don’t find this solution very elegant but if this is the way to go , i’m fine with it.

  • Caching the full model in the store in order to speed up the opening of the other two tabs (kind of preloading the data for the other 2 tabs.

For my last concern, maybe the caching is already working as intended , i just dont get a deep knowledge of ember data mechanism yet

1 Like

I have 2 endpoints for user, one returning the names the other one returning contact infos (email + phone).

The way I’ve usually seen this sort of thing done is via relationships, e.g. one endpoint/resource/model is the user and then contact info would be a different endpoint/resource/model (or sometimes even multiple) and on the front-end those models would be related via relationships. Maybe that doesn’t really fit your data but the reason I bring it up is that it’s unusual to have two backend endpoints with partial data for the same “canonical record” (if you will). Are both of these records the same “type”? And if so do they then have different ids?

merging the result of the 2 queryRecord call

Totally get what you’re saying but since you have two different records here and you’re using Ember Data that kind of thing is going to be way more trouble than it’s worth. Obviously the setupController bit isn’t required at all and it just comes down to opinion on what looks cleaner/more acceptable. I personally would prefer the explicitness of {{this.model.a}} but that’s just me.

  • Caching the full model in the store in order to speed up the opening of the other two tabs (kind of preloading the data for the other 2 tabs.

If each tab is a route then the best way to do this (since you’re using queryRecord instead of findRecord) would be to have a parent route which loads both queries, and then each child tab route “forwards” the parent route model.

If each tab is not route based and there’s only one route involved then the caching works how you want as long as everything is rendered in the same route. The route model will only be fired when the route is visited.

1 Like

another option here is to have the adapter make two requests and merge the response into a single response.