Pushing JSON directly into an Ember Data 1.13+ store

First, some background

Ember Data 1.13 consolidated the methods for retrieving records into peek peekAll & peekRecord), find (findAll & findRecord) and query (& queryRecord) to make it clearer how data is being pulled from the store. e.g.

this.store.findRecord(‘type’, id);

You can read in more detail the rationale behind this change in the Ember Data 1.13 Release Notes but my view is that it is a great improvement and makes understanding what requests are being made more intuitive.

Sometimes, though, you need to interact with a custom API endpoint that doesn’t immediately lend itself to this approach without some adaptation.

In my case, I had an endpoint which was a subset of models of a specific type, in the format

/api/v1/models/subset

So in this instance it required modifying the adapter in some way to hit this URL which doesn’t adopt the standard format of /models/:model_id

Some talk about custom API endpoints

There appeared to be 3 main approaches to solving this. The first being to directly modify the buildURL method in your adapter to account for this specific request. This direct approach seems to have (perhaps unfairly) a reputation for having a high barrier to entry as outlined in this RFC.

Which leads to the second option, using the ember-data-url-templates addon specifically developed to address this RFC. I found a pair of great blog posts Ember Data: working with custom API endpoints and Ember Data: working with nested API resources which really explained how to use the addon and in my case the adapter code was very straightforward.

This worked perfectly and for this example I could have stopped there. In reality I also have some other non-standard endpoints, including with query parameters, which seems to present a problem in certain cases with the current implementation.

In the course of discovering more about working with custom endpoints I discovered Mike North’s addon for Ember API Actions which adopted an approach I found more intuitive for my needs so thought I’d give it a shot.

Instead of working with the adapter to create the URL the idea is that you define the request in the model, this demo illustrates the concept.

As my request was for a collection the code in my model looked like this

Although the network inspector showed that it was making the correct request to /models/subset now I found the syntax for the model slightly strange and unlike the previous two approaches it didn’t automatically add the records to the store.

How (not) to push JSON into the store

Sorry for the long introduction, but I thought it was worth explaining why I needed to push data into the store directly first and some other potential solutions.

Anyway, I’d used store.push() before in Ember Data pre 1.13 but the way I had done it previously e.g.

this.store.push(‘type’, data);

has been deprecated and now logs this warning

Store.push(type, data) has been deprecated. Please provide a JSON-API document object as the first and only argument to store.push

As my server response was JSON-API compliant (with only one small proviso, my server responded with dasherized keys, whereas my model attributes wer camel-case) so I thought I would follow the advice and try

this.store.push(response);

Unfortunately this gave me an ambiguous error message and didn’t work as expected. I’ve made a simple example on Ember Twiddle so you can see how using store.push directly in this way doesn’t work, whereas findAll does.

Thanks to the advice of @pangratz on the Ember community slack he explained that the subtle difference in syntax between my keys was the reason for the issue.

Ember Data recommends camel-case for model attributes, whereas JSON-API spec recommends dasherized which means by following both best practices it is unlikely you can just use store.push().

Finally, a working solution

Therefore I should use store.pushPayload() instead. Additionally, until this PR lands in Ember Data then without a feature flag enabled pushPayload doesn’t return the ‘pushed value’.

This meant that, for now, to push the data into the store and return with those items I needed to return those values manually. The final working code for the route therefore looked like this

I hope this may help anyone else facing a similar situation. Please let me know if you think I’ve missed anything useful or have explained something incorrectly.

Thanks!