getting data from non-standard endpoints using ember-data - ember.js

I have an endpoint /activities which behaves in a typical fashion for a RESTful API using JSON. I can of course do things like the following:
model: function() {
return this.store.find('activity');
}
to return a list of activities. Or I can:
model: function(params) {
return this.store.find('activity', params.id);
}
get a specific Activity record. This works fine. But the API also allows certain filtering to be done using URL parameters. So for instance,
GET /activities/type/exercise
Unlike a full list it would only bring back activities which are associated with "exercise". What is the best way for one to pull back data using ember-data? At a bare minimum I want the data to come back, be associated to the correct model, and be iterable via a `DS.RecordArray or similar object.
Ideally I would also like to avoid making network requests every time I hit this route although possibly this is something that I can't get "out of the box". Still interested if there are any best practices.

Ember can already query for data, by default using query params i.e.
store.find(activities, {type: 'exercise'}); // /activities?type=exercise
you could override findQuery on your activities adapter so that if conforms to your api:
// /adapters/activity.js
import DS form 'ember-data';
export default DS.RESTAdapter.extend({
findQuery: function(store, type, query) {
for(var key in query) break; // get first key in the example 'type'
var path = ['', key, query[key]].join('/');
return this.ajax(this.buildURL(type.typeKey) + path, 'GET');
},
});
I haven't tested this but it should work.

Related

Can I POST multiple models using Ember Data?

So let's say you have a User model which is set up to a fairly standard API. On the front end you have an Ember project which also has that User model. The normal creation call would be something like the following:
store.createRecord('user', {email: 'test#gmail.com'}).save();
This would send off a POST request to something like /api/users. However, something fairly extensive API's support is the creation of multiple models at once. So for instance, instead of the POST call just sending a single object under user: {email: 'test#gmail.com'} it would send an array of objects like users: [{email: 'test#gmail.com'}, {email: 'test2#gmail.com'}, ...].
How I have seen this handled in ember is to just do multiple creation calls at runtime. However, this is terribly inefficient and I am wondering if Ember supports saving multiple models at the same time? How would you achieve this in Ember?
You cannot save an array of models in a single POST request Ember Data as you describe it, however there is a way.
You can save a parent model which hasMany 'user' with the EmbeddedRecordsMixin, which will include either relationship ids or full records. Your serializer would look like -
import DS from 'ember-data';
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
users: { embedded: 'always' },
}
});
Depending on your use case it may make sense to create a parent model only for this purpose which hasMany 'user'. If you want to use an existing model and don't always want to embed its user records there is an answer here.
If you do decide to save the models individually, you would want to do users.invoke('save'), which will trigger a POST for each model.
If you're asking specifically about Ember Data, I don't know of any way of doing that (I don't think it's possible to use any equivalent of save() on a collection/array). There could be alternative Data libraries that may work (for instance you could check Orbit.JS - which is something I haven't done yet)
The way I've done it it to have a custom endpoint on my backend that receives a certain JSON payload and creates the resources. You do it by issuing a regular ajax call, see this example (from a project of mine).
let content = //get content that you want to post
let accessToken = this.get('session.session.authenticated.token');
Ember.$.ajax({
data: JSON.stringify(content),
dataType: 'json',
method: 'POST',
url: 'path/to/my/custom/end/point',
headers: {
'Content-Type': 'application/json',
'Authorization': `Beader ${accessToken}`
}
}).then((result) => {
// Code for success
}, (jqXHR) => {
// Code for error
}).always(() => {
// Code for always/finally
});
As you can see this is all custom code, not leveraging Ember Data store or models. So far I haven't found a better answer.
EDIT: After seeing andorov's answer. I forgot to mention something. I'm using Ember Data 2.0 (JSONAPI by default) and EmbeddedRecordsMixin does not work property with JSON API

Ember Data is always fetching records for route

I just switched my application over to Ember CLI and Ember-Data (previously using Ember Model). When I transition to my employees route ember data does a GET request on the api's user route with a query as intended. However, whenever I leave this route and return it continues to perform a GET request on the api. Shouldn't these results be cached? I had a filter running on the model, but I removed it and still ran into the same issue.
Route w/ Filter:
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
// This queries the server every time I visit the route
return this.store.filter('user', {type: 'employee'}, function(user) {
if(! Ember.isEmpty(user.get('roles'))) {
return user.get('roles').contains('employee');
}
});
}
});
Route w/out Filter:
import Ember from 'ember';
// This still queries the server every time I visit the route
export default Ember.Route.extend({
model: function() {
return this.store.find('user');
}
});
Passing a second parameter into the filter function, {type: 'employee'}, turns it into a findQuery + filter, and find will always execute a query request. If you want to only call a particular resource once per SPA lifetime in a particular route you can add logic to keep track of it. The basic concept goes like this:
Check if you've fetched before
If you haven't fetch the records
Save the fetched records
return the saved fetched records
Example
export default Ember.Route.extend({
model: function() {
//resultPromise will return undefined the first time... cause it isn't defined
var resultPromise = this.get('resultPromise') || this.store.find('user');
this.set('resultPromise', resultPromise);
return resultPromise;
}
});
Additionally if you've already called find you can also just use store.all('type') to get all of the records for that type in the store client side without making a call to the server.

Route for one resource returns two records insted of one

In my application I want route to resource "day" to looke like this "sampledomain.com/day/1-3-2014".
I defined it like this:
this.resource('day', { path: '/day/:day_date' });
My model hook for that route looks like this:
model: function(params) {
return this.store.find('day', params.day_date);
},
And my API response looks like this:
{"day":{"id":"3","dayDate":"2014-03-01","openTime":null,"closeTime":null}}
For some reason I'm getting two records into the store. One is correct and one with id set like the dynamic part from URL (1-3-2014) and the rest of this model data as empty.
I have no idea what I'm doing wrong.
You should use a findQuery and return the first record inside the promise resolve handler:
App.DayRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('day', { date: params.day_date }).then(function(days) {
return days.toArray()[0];
});
}
});
You're pretending the date is the id, so Ember Data is building up a record based on the id 1-3-2014 then your server is returning a record with an id of 3. So that begs the question, which is the id?
If dayDate can be the id, the primaryKey for the record should change, if it can't, then really you should be using findQuery (or find with an object).
return this.store.find('day', {dayDate: params.day_date});
This will change your result set to a collection, since you aren't going by id, it's no longer guaranteed to be a single instance.
Additionally the get request will be different

Using primary keys with Ember Data

I've been struggling for the past few days with primary keys and the last version of Ember Data.
I first read how to do it on the Breaking Changes file on GitHub, but it's apparently outdated. I tried several other ways (with the help of Peter Wagenet on IRC), but none of them seem to work.
I would like to make slug my primary key on my model, and also since I'm working with MongoDB, I would like to use _id instead of id.
Has anyone figured out how to do this? My underlying problem is that model records get loaded twice when I do several App.MyModel.find() on the model.
As of Ember Data 1.0 beta you define primaryKey on the respective serializer.
For the entire app
App.ApplicationSerializer = DS.RESTSerializer.extend({
primaryKey: '_id'
});
For a single type
App.FooSerializer = DS.RESTSerializer.extend({
primaryKey: '_id'
});
You will still refer to it as id on the model, but Ember Data will serialize/deserialize it to _id during transfer.
Example: http://emberjs.jsbin.com/OxIDiVU/635/edit
Read More about it here: http://emberjs.com/api/data/classes/DS.RESTSerializer.html#property_primaryKey
I would like to make slug my primary key on my model, and also since I'm working with MongoDB, I would like to use _id instead of id.
Use the adapter's map API to specify the attribute that should be used as primary key:
App.Adapter.map('App.Person', {
primaryKey: '_id'
});
See serializer.js api docs for detail. If you need to further customize how the records id is serialized, use the addId hook described here.
Since ember-data is still under active development, documentation on this feature is somewhat limited and may change before 1.0 release. In the meantime refer to the ember-data test suite to see examples of this in action. For example:
mapped primary keys are respected when serializing a record to JSON demonstrates how an _id attribute will be included in json when a record is saved
mapped primary keys are respected when materializing a record from JSON shows how JSON with _id attribute will be transformed into a record with the correct id
In case the solution suggested by Nikita doesn't work (didn't for me using revision 11 of ember-data), here is how I changed the primary key when working with the RESTAdapter:
App.Adapter = DS.RESTAdapter.extend({
serializer: "App.MySerializer"
});
// notice we extend the RESTSerializer, not Serializer!
App.MySerializer = DS.RESTSerializer.extend({
primaryKey: function(type) {
return '_id'; // you get it...
}
});
I'm aware that this probably won't help the OP anmore but I still post it as it may be helpful for some of you.
Try to extend your adapter like this:
App.RESTSerializer = DS.RESTSerializer.extend({
init: function() {
this._super();
this.map('App.MyModel', {
primaryKey: "_id"
});
}
});
I use MongoDB and Ember-Data 1.0.0-beta.6 in my application and the _id posed a problem in Ember 1.4.0 for me too. Here's what I've done to solve the problem, assuming the returned JSON array is nested in the root property "people":
App.ApplicationSerializer = DS.RESTSerializer.extend({
normalizeHash: {
people: function(hash) {
hash.id = hash._id;
delete hash._id;
return hash;
}
}
});
This is, of course, an application-wide serializer but you can limit it to a specific path with something like App.MyPathSerializer = DS.RESTSerializer.extend({ ... });

Ember: unsaved models in dynamic URLs

I'm currently using the FixtureAdapter in my Ember app, but when I switch to the RESTAdapter, my URLs no longer work.
The app is a scorekeeping type thing, and I want users to be able to log all the scores without having to be connected to the Web. After the game is finished they can optionally save all the data to the server.
Now, when Ember wants to route to say, matches/:match_id, the ID isn't there because I didn't commit anything to the server/store, so my models don't yet have an ID and I get URLs like: /match/null/games/null
Is this expected behaviour? And if so, is there a workaround? I thought about using model.clientId and then overriding the model hook for each route to try and fetch the Model from the store using the id when present and falling back to clientId. Any other ideas?
UPDATE March 10, 2013:
The following seems to fit my needs and allows to (for now) forget about moving back and forth between local storage and the REST adapter:
App.Store = DS.Store.extend({
revision: 11,
adapter: DS.RESTAdapter.extend({
namespace: 'api/v1',
bulkCommit: true,
generateIdForRecord: function(store, record) {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
}
})
});
UUID function taken from: Create GUID / UUID in JavaScript?
If a record hasn't been committed, then it shouldn't have an id yet. Furthermore, a url that serializes the application state of viewing that record doesn't make any sense because the record won't exist in another browser until it is committed. You couldn't just paste the url elsewhere and have it load where you left off.
I think what you really want to do is serialize the application state differently (ie. generate a less specific url) when the record is uncommitted. You can achieve this by overriding the serialize method in your route.
For example,
App.PostRoute = Ember.Route.extend({
serialize: function(model, params) {
if (model && model.get('isNew')) {
// corresponds to path '/post/:post_id'
return { post_id: 'new'}
}
return this._super(model, params);
}
});
Now if you call transitionToRoute('post', post) from your controller, where post is a newly created but uncommitted record, the application state will be serialized to the path /post/new. If you pass it a committed record with an id, it will be serialized as usual.