How do I make multiple requests with an Ember adapter? - javascript

I'm building an Ember app with Parse as my backend. Parse out of the box does something odd with relationships – they only work one way. :/ I've been down the road of trying to manually make relationships work both ways by saving both models in Ember, but that seems messy and can mean that data gets out of sync quite easily. Yuck. So now I'm looking for alternatives.
First, let me explain in a bit more detail.
Let's say I have a post and comments. Parse will store the relationship from comments -> post, but not the other way around. Or, I can manage a list of comments in a 'Pointer array', but not the one-to-one relationship from comments -> post.
I see two options:
Modify the way the API works using their Cloud Code platform. This might be the easier route, but I'd rather address this in Ember so I can open source my adapter/serializer for others who run into this issue.
Figure out a way to make multiple requests in my adapter. I imagine it would work something like this:
a. this.store.findRecord('post', id) is called in the app code.
b. Ember adapter sees that post has a hasMany relationship based on what's defined in the model.
c. The adapter generates a url that looks for comments where the post's id matches the one I'm trying to find (pretty easy to do with Parse). Yes, this means multiple requests, but only 1 more (per relationship). It would be similar to what Ember does when there is a links attribute in the Payload, but we don't have that since Parse doesn't recognize the hasMany relationship`.
Looking for 2 things. First, any thoughts on if I'm thinking about this correctly, or suggestions on other options. Second, and probably most important, some ideas on what hook I can latch onto in my adapter to fire off a second request. My initial thought was something like this:
findRecord: function(store, type, id, snapshot) {
snapshot.eachRelationship(relationship => {
// Do something to build a url for each hasMany relationship
});
return Ember.RSVP.promise(...);
}
Any help with this is much appreciated.

So, basically you're trying to override the default findRecord behaviour of ember-data.
Your current approach sounds about right. You can create an adapter and provide custom definition to methods such as findRecord, findAll, query and so on.
A typical code example can be :
findRecord(store, type, id, snapshot) {
let data = {};
const traditional = true;
return new Ember.RSVP.hash({
news: Ember.$.ajax({ url: wpUrl + 'post', dataType: "jsonp", type: 'GET' }),
events: Ember.$.ajax({ url: wpUrl + 'comments', dataType: "jsonp", type: 'GET' })
});
});
The code snippet is completely vague, but i hope you got my point..

Related

Right way to make AJAX GET and POST calls in EmberJS

I've started working on EmberJS and I absolutely love it. It does have a learning curve but I believe it has fundamentally very meaningful principles.
My questions is how to make GET and POST calls in Ember JS. I understand that there are models / store, but models (in my opinion) would be to only a particular entity's attributes.
Any thoughts on the following questions would be great.
1. USER A send friend request to USER B. Should there be a "Request"
model? And do I just make a POST request?
2. Some arbitrary data needs to be returned for the page. Not
particularly of a model. Should I still make a model for that?
For use a simple GET request?
3. User needs to update this profile photo. How can the file
to be uploaded, be set as a model attribute?
How should I go about making regular GET and POST calls (if I am to do them at all). Just use jQuery's $.ajax() or is there another way. I also found a service ember-ajax which has extended the $.ajax into a promises style.
Any thoughts would be much appreciated.
Long live EmberJS :)
First option: You can use ember-data. It has customizations such as serializers or adapters.
Second option: You can use addons like ember-ajax.
Our usage is just using jQuery's ajax(). We wrote a service that just wraps jquery.ajax() and use it everywhere in our code. We believe that it gives us a flexibility of writing different kind of queries. We don't have any model of ember-data.
Sample -pseudo- code:
export default Ember.Service.extend({
doPostCall(target, data, options=null){
//consider cloning options with Ember.$.extend
var requestOptions= options || {};
requestOptions.url=target;
requestOptions.type='POST';
requestOptions.data=JSON.stringify(data);
doRemoteCall(requestOptions);
},
doGetCall(target, data=null, options=null){
//consider cloning options with Ember.$.extend
var requestOptions=options || {};
requestOptions.url=target;
requestOptions.type='GET';
requestOptions.data=data;
doRemoteCall(requestOptions);
},
doRemoteCall(requestOptions){
//assign default values in here:
// such as contentType, dataType, withCredentials...
Ember.$.ajax(requestOptions)
.then(function(data) {
Ember.run(null, resolve, data);
}, function(jqXHR , textStatus, errorThrown) {
jqXHR.then = null;
Ember.run(null, reject, jqXHR, textStatus, errorThrown);
});
}
});
PS: By the way, if you need to run your app in server-side (with fastboot), you cannot use jQuery. So use ember-network.
If you are performing CRUD operations over models, ember-data is nice. In most apps, CRUD operations account for ~90% of requests.
There is occasions where an app needs to make requests that not ideal for ember-data, and that is where ember-ajax enters the game. It allows you to do requests like you'd do with just jQuery, with the nice addition that requests are done though a service that has some extension points to allow to customize things like headers that are used app-wide, which is more complex with just raw jquery.
If your app is going to run in fastboot, you should use ember-network, because it works both in the browser and in node, while jquery/ember-ajax does don't.
Current best practise would be ember-fetch. since ember-network is deprecated in favor of ember-fetch.
You can just install ember-fetch addon by running the below command,
ember install ember-fetch
and then just import fetch and you are good to use fetch method.
import fetch from 'fetch';
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return fetch('/my-cool-end-point.json').then(function(response) {
return response.json();
});
}
});

Mithril js , Is there any good method for m.request() loop and concat Array?

See below.
https://jsfiddle.net/z30y983n/1/
First, the script gets 4 items from Github API and renders a list. If you submit 'NEXT' button, the script re-draws the list after re-sends Get request.
GET request.
repo.Repo.getList = function (api) {
return m.request({
method: "GET",
url: api,
type: repo.Repo,
extract: repo.linkHeader.setLinkHeader,
initialValue: []
})
.then(function (data) {
// bad solution.
return repo.vm.list(repo.vm.list().concat(data));
});
};
Concat Array.
repo.vm.api(links['next'])
return m('button', {onclick: repo.vm.add}, 'NEXT');
It works just as expected. But, It is bad solution, aren't you? I think
this concat process should be completed in View-Model (repo.vm).
Is there any good method? Or is this all right?
It's not ideal to modify things in the view method. Consider the view a template, that should only display state, since redraws can happen quite rapidly. Instead you should let the controller do the request, and modify state when the request is finished. Then the view will be displayed.
Code-wise, I think you're a bit deep into "too much structure". There are viewmodels, a LinkHeader prototype, and the program flow jumps all over the place. The mental model is quite simple, so keep it that way instead of getting into patterns that will only make things abstract and complicated.
Here's my take on it: https://jsfiddle.net/ciscoheat/akwdqhpx/
The parser is the same, but after that I've tried to keep the code compressed and local, so you can look at a part of the code and understand it. Here's a very good article about locality and cohesion. I've also changed a few names to keep closer to the mental model.

What is the Backbone way to use a different URL for POSTing to create a model?

We have built a RESTful API with URLs like
/api/v1/cars/
/api/v1/cars/34
All these endpoints accept GET, PUT, POST and DELETE methods as usual.
To help you understand our models: these are cars whose engine/wheels/specifications/etc are typically modified as part of a process of tuning.
We therefore have the concept of a 'report' on a car: a collection of results of various tests that are carried out on a car and stored for later viewing (just like your mechanic might produce a report on your car for later comparison).
These reports are stored at URLs like
/api/v1/car_reports/
/api/v1/car_reports/76
We have chosen to not allow these end points to accept a POST method, you can only GET or DELETE them (there is no sense in a PUT because these reports should never be modified after creation).
We made the decision that you create a car report via the URL of the car itself:
you POST to an endpoint like /api/v1/cars/34/make_report, with a list of the tests you wish to run as the body data in this request,
the back end uses the car's details together with the list of tests to produce the report which it stores at a URL like /api/v1/car_reports/77.
the back end concludes by sending the location /api/v1/car_reports/77 in the response to the client (and we decided, as a formality/convenience, to actually include a copy of the report in the body of the response).
Our front end consumes this API with a Backbone.Model and Backbone.Collection like:
var Car = Backbone.Model.extend();
var CarCollection = Backbone.Collection.extend({
model: Car,
url: '/api/v1/cars'
});
var CarReport = Backbone.Model.extend();
car CarReportCollection = Backbone.Collection.extend({
model: CarReport,
url: '/api/v1/car_reports'
});
We can create a Car easily via the CarCollection:
var car_collection = new CarCollection();
var car = car_collection.create({
make: 'Ford',
engine_size: 1600,
tyres: 'Michelin'
});
// Suppose that car.id = 34
What is the best way to create a CarReport on that car?
At the moment I am doing an explicit ajax call like:
var tests = {
'maximum speed',
'miles per gallon'
};
$.ajax({
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(tests),
url: car_collection.url + '/' + car.id + '/make_report'
});
This feels like a hack.
How can I do this in a more Backbone-ish way?
Many thanks.
In REST, I feel that the following:
We made the decision that you create a car report via the URL of the
car itself:
you POST to an endpoint like /api/v1/cars/34/make_report, with a list
of the tests you wish to run as the body data in this request,
Is a no-no.
The endpoint is meant to represent a resource, the REST verbs (ie. GET, POST, PUT and DELETE) being the only actions to be performed on the resource.
Therefore, the make_request method you defined would rather be formulated as:
POST /api/v1/cars/34/reports
Here are my two cents on the rest of the problem:
When you define a URL as such:
/api/v1/car_reports/77
This is not wrong, but I feel that the following formulation is cleaner:
/api/v1/cars/reports/:id
It is more custom to build url's this way. If tomorrow you have motorcycles as well, you would have:
/api/v1/cars/reports/:id
/api/v1/motorcycles/reports/:id
Your question:
What is the best way to create a CarReport on that car? At the moment
I am doing an explicit ajax call like:
The example you shared uses jQuery ajax directly. Backbone also depends on this method to perform ajax calls to the server, but its philosophy is a bit different. The purpose for a Backbone model is to represent a single resource unit, whereas the Backbone Collection represents a collection of units. Therefore, if you want to POST a model to the server, you are better off calling save() directly on the model after having set its properties to be POSTed, if you want to follow the philosophy where models represent a resource unit.
However, in your example, the report isn't 'actually' directly bound to the car, as it is bound to tests that are executed on the car. So I would suggest having something like:
/api/v1/cars/77/tests/reports
The reason being, that tests are stored in a database table (many to many) where test data are linked to the cars. As such "tests" becomes the resource as a subset of a specific car where data can be sent to as a restful route (I imagine that it could theoretically be possible that you perform multiple tests on the same car, which would make the system more flexible).
Rather than defining make_report in the url, it could be a method that is invoked when the test data are posted or updated (generating reports with v1, v2, etc).
Thinking further, if you would assume that you might have other vehicles such as motorcycles tomorrow, you might make tests a resource as well with the vehicle as a subset, which would also make sense:
/api/v1/tests/cars
/api/v1/tests/motorcycles
In the end, all of these options are possible. Therefore, REST isn't really defined as an exact science rather than an architectural style; if you implement it correctly in the backend, it will work nevertheless. So I guess that what you opt for depends on what makes the most sense to you and your specific case.
My two cents, I hope it has given some insights on further options.
In your place I would probably hold a separate Model (sort of a factory?) for creating the reports, and another for GET and DELETE. That would give the most Backbone-ish feel IMHO.
Here is an example of how to make sure no mistakes are done.

Optimal URL structure for Backbone.js and Backbone implementation

I'm developing a RESTful API for a Quiz app, which is going to be built with Backbone.js and Marionette. I'm quite new to backbone and was wondering what de best URL structure would be. I have the following resources:
Answer,
Question which contains Answers,
Question Group which contains Questions,
Quiz which contains Question Groups.
Two possible URL structures come to mind:
GET /quizzes/:id
GET /quizzes/:id/questiongroups
GET /quizzes/:id/questiongroups/:id
GET /quizzes/:id/questiongroups/:id/questions
GET /quizzes/:id/questiongroups/:id/questions/:id
GET /quizzes/:id/questiongroups/:id/questions/:id/answers
or:
GET /quizzes/:id
GET /quizzes/:id/questiongroups
GET /questiongroups/:id
GET /questiongroups/:id/questions
...
Now, I have been trying to use both of these options. With the first one, I can't figure out how to define the collections as a property of the parent models in Backbone so that I can use fetch() on them. The problem with the second option is a bit different: as I understand it, Backbone derives the url for a model from its collection, but the collection is a child of another resource, whereas the url for getting a single resource uses another collection, namely the global set of resources.
I'm pretty sure I'd have to override url() in both cases. I tried some things but didn't come up with anything useable at all. Also, I'd rather not override every single url()-model in the app, changing the API structure to suit the preferences of Backbone seems like a better option to me.
Any pointers as to what seems the right way to do it with Backbone would be great!
Thanks
If questiongroups can only appear in a single quiz, then the first option (the hierarchical one) is an obvious choice. To comply with RESTful conventions, you might want to consider using singular nouns instead: /quiz/:id/questiongroups/:id/question/:id/answer/:id
To solve your fetching problem, I would recommend using nested backbone models as per this answer: https://stackoverflow.com/a/9904874/1941552. I've also added a cheeky little parentModel attribute.
For example, your QuizModel could look something like this:
var Quiz = Backbone.Model.extend({
urlRoot: '/quiz/', // backbone appends the id automatically :)
defaults: {
title: 'My Quiz'
description: 'A quiz containing some question groups.'
},
model: {
questionGroups: QuestionGroups,
},
parse: function(response){
for(var key in this.model){
var embeddedClass = this.model[key];
var embeddedData = response[key];
response[key] = new embeddedClass(embeddedData, {
parse:true,
parentModel:this
});
}
return response;
}
});
Then, your QuestionGroups model could have the following url() function:
var QuestionGroups = Backbone.Model.extend({
// store metadata and each individual question group
url: function() {
return this.parentModel.url()+'/questiongroup/'+this.id;
}
});
Alternatively, if you don't need to store any metadata, you could use a Backbone.Collection:
var QuestionGroups = Backbone.Collection.extend({
model: QuestionGroup,
url: function() {
return this.parentModel.url()+'/questiongroup/'+this.id;
}
});
I'm afraid I haven't tested any of this, but I hope it can be useful anyway!

Any recommendations for deep data structures with Backbone?

I've run into a headache with Backbone. I have a collection of specified records, which have subrecords, for example: surgeons have scheduled procedures, procedures have equipment, some equipment has consumable needs (gasses, liquids, etc). If I have a Backbone collection surgeons, then each surgeon has a model-- but his procedures and equipment and consumables will all be plain ol' Javascript arrays and objects after being unpacked from JSON.
I suppose I could, in the SurgeonsCollection, use the parse() to make new ProcedureCollections, and in turn make new EquipmentCollections, but after a while this is turning into a hairball. To make it sensible server-side there's a single point of contact that takes one surgeon and his stuff as a POST-- so propagating the 'set' on a ConsumableModel automagically to trigger a 'save' down the hierarchy also makes the whole hierarchical approach fuzzy.
Has anyone else encountered a problem like this? How did you solve it?
This can be helpful in you case: https://github.com/PaulUithol/Backbone-relational
You specify the relations 1:1, 1:n, n:n and it will parse the JSON accordingly. It also create a global store to keep track of all records.
So, one way I solved this problem is by doing the following:
Have all models inherit from a custom BaseModel and put the following function in BaseModel:
convertToModel: function(dataType, modelType) {
if (this.get(dataType)) {
var map = { };
map[dataType] = new modelType(this.get(dataType));
this.set(map);
}
}
Override Backbone.sync and at first let the Model serialize as it normally would:
model.set(response, { silent: true });
Then check to see if the model has an onUpdate function:
if (model.onUpdate) {
model.onUpdate();
}
Then, whenever you have a model that you want to generate submodels and subcollections, implement onUpdate in the model with something like this:
onUpdate: function() {
this.convertToModel('nameOfAttribute1', SomeCustomModel1);
this.convertToModel('nameOfAttribute2', SomeCustomModel2);
}
I would separate out the different surgeons, procedures, equipment, etc. as different resources in your web service. If you only need to update the equipment for a particular procedure, you can update that one procedure.
Also, if you didn't always need all the information, I would also lazy-load data as needed, but send down fully-populated objects where needed to increase performance.

Categories