I'm building an an ember app for demo purposes (important to note, as some of what I'm doing here is not secure/best practice). I have no back end and am publishing it on github pages (the purpose is to show Ember, not Rails or whatever other backend I might write).
The data this app is displaying is mostly from a public api (the Riot api if it matters]). I'll get my data from there quite easily, but obviously I can't modify and save it or anything. To that end, I set up a RESTAdapter:
App.ApplicationAdapter = DS.RESTAdapter.extend({
host: 'https://prod.api.pvp.net',
namespace: 'api/lol/na/v1.3',
buildURL: function (x, y) {
var url = this._super(x, y);
return url + '?api_key=<my_api_key_here :)>';
}
});
I was somewhat surprised to see this error: No 'Access-Control-Allow-Origin' header is present on the requested resource.
It had built the url string correctly, but the request to their api failed. I googled around for quite awhile and discovered this is a common sense restriction by them to prevent cross-site abuse of the api or some such, and also to prevent javascript apps (such as mine!) from doing exactly what I was doing. Please note that I'm aware of why I shouldn't do this, and wouldn't if this were in any way a production app. Since it's just for my own ends, I figured I would be okay leaving my api key in the javascript and doing it this way, if only I could figure out a way around this error.
Why I was surprised it didn't work
I'm surprised because previous to using Ember data and the REST Adapter, I was loading models from this exact api with simple ajax promises, and it worked like a charm. Doing the following worked:
$.ajax({
url: 'https://prod.api.pvp.net/api/lol/na/v1.3/summoner/<some id>?api_key=<my_api_key>',
success: function (modelJson) {
store.createRecord('summoner', modelJson); //or somehing
}
});
Why should this work, while whatever the REST Adapter is trying to do isn't? Either way the request is coming from my framework right? How can I get Ember Data to request its data more closely to the above, which I know works?
Something else I tried
It's worth noting I tried the following initializer:
initialize: function () {
Ember.$.ajaxPrefilter(function (options) {
options.xhrFields = { withCredentials: true };
});
}
The error message changes to A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true.
Related
I have an ES6 class in Ember 3.1 which is being handed an ember data object called certifciate. I would like to be able to call .reload() on that certificate as follows:
#action
showCertificateInfo(this: DomainCard, certificate) {
this.setProperties({
isShowingCertificateModal: true,
selectedCert: certificate,
})
certificate
.reload()
.then(() => {
this.set('isShowingCertificateModal', true)
})
.catch(e => {
// TODO: handle this
})
}
However, if I do this, then Ember gives the following error/warning:
Assertion Failed: You attempted to access the 'reload' property
(of <DS.PRomiseObject:ember796>)... However in this case4 the object
in quetstion is a special kind of Ember object (a proxy). Therefore,
it is still necessary to use` .get(‘reload’)` in this case.
If I do as the code suggests and call .get('reload') instead, then I get an internal Ember error that this is not defined when calling this._internalModel. I get the same error when doing:
const reload = certificate.get('reload').bind(certificate)
reload().then()
...
What do I need to do to be able to reload this ember data object properly?
Actually, whe fundamental problem seems to be that the certificate model had an async relationship to the domain model, and by using {async: false}, we remove the need to get a proxyPromise object returned to us and remove the need to pull the content object out of the promise.
Pulling the content out of the proxy solved the problem:
const certContent = certificate.content || certificate
certContent.reload()
I'm still not sure why Ember 3.1 isn't able to work with the proxy properly, but this was the solution.
There have been questions about this topic in previous years, but Ember has changed a lot since then and most of those answers were fill ins until things were more 'together'. I am working on an app that is using ember-cli. On the api, if you request a resource from the api that does not exist, the api returns a 404. However, ember data seems to just throw an error upon receiving a 404.
I saw one approach that seemed promising, someone answered a similar question in 2014 and had this code sample:
return this.store.find('matter', params.matter_id).then(
(function (_this) {
return function(model){
resolve(model);
}
})(this),
(function (_this) {
return function(invalid){
_this.transitionTo('auth.denied');
}
})(this));
ember promises can take a resolve and reject as arguments. In the above code, he passed self instantiating functions as the resolve and reject arguments. The reject is working just as I would like it to. However, now the issue that I am running into is that when I am in the resolve, even though the 'model' variable comes back with an ember data object, i cannot seem to get this to resolve properly. Ember throws and error stating, "Expected an object as data in a call to push for matter , but was undefined".
I was hoping someone out there in the Ember community might have some insight in either how to get this to resolve properly, or perhaps a better way to approach this problem altogether.
this.store.find returns a promises.
Try doing something like
this.store.find(...).then(function(model) {
console.log(model);
resolve(model)
}, function(reason) {
alert('error');
});
Is the model json, if not it's probably the problem?
I have an adapter something like this:
var Adapter = DS.RESTAdapter.extend({
host: 'http://localhost:4200'
});
if (config.environment === 'production') {
Adapter.reopen({
host: 'http://foo.example.com'
});
}
This has been working for a while, but recently something breaks. My ember app interfaces with a number of different subdomains (eg foo.example.com and bar.example.com). My understanding is that reopen changes all instances, which is what I think happens. When I browse to '/bar' it uses the correct adapter in production, but browsing to '/foo' still uses the bar.example.com endpoint.
My question is two fold. Firstly, am I using the right approach here?
Secondly, I'd like to change the adapter based on a runtime setting. I need to work around IE8 lack of CORS support so am thinking that if I have an ie8 switch, the adapter should hit example.com/foo instead of foo.example.com. In my mind these two areas are conceptually related, but I'm happy to be wrong.
update
To simplify things, I essentially want to find the hostname of the request and pass that into the adapter. For example if I browse to www.example.com I want the adapter to go fetch a record from www.example.com/foo, or when browsing to www.example2.com I want the adapter to fetch the record from www.example2.com/foo. I hope that makes sense. Is this even possible?
I'm not 100% sure on this, but my guess is that the value is being cached. My recommendation is to make host a volatile computed property. This actually works out better for you, since that will allow you to better select the host based on runtime configuration. Try something like this:
var Adapter = DS.RESTAdapter.extend({
host: function() {
if (config.environment === 'production') {
return 'http://foo.example.com';
} else {
return 'http://localhost:4200';
}
}.property().volatile()
});
Because Ember-Data always uses Ember's get method to fetch properties, changing host from a normal property to a computed one should make no difference. And because of that, you're able to select the host at runtime, and make sure that it's computed every time. (Don't worry about the performance of not caching that value, I promise it won't make a noticeable difference.)
import ENV from 'ember-arp-roomviewer/config/environment';
export default DS.JSONAPIAdapter.extend({
namespace: 'arp/v1',
//host: 'http://localhost/arp/index.php/wp-json',
//host: 'http://www.acme.net/myroomplan/wp-json',
host: function() {
if (ENV.environment === 'production') {
return 'http://www.acme.net/myroomplan/wp-json';
} else {
return 'http://localhost/arp/index.php/wp-json';
}
}.property().volatile(),
headers: {
'Content-type': 'text/plain' // Stops OPTION headers being sent PITA
}
I've recently been porting a number of Ember apps I maintain to RC 8 and ran into this.
Before the router facelift landed I would sometimes manage control flow via promises returned by Ember Data find calls.
For example:
SomeRoute = Ember.Route.extend({
model: function(params) {
var resolve = function(model) { return model; };
var route = this;
var reject = function() { this.transitionTo('someOtherRoute'); };
return SomeModel.find(params.some_model_id).then(resolve, reject);
}
...
});
With the recent changes, it is now possible to handle errors created in model callbacks via the error action:
SomeRoute = Ember.Route.extend({
// note: model callback no longer needed--default suffices
actions: {
error: function(reason, transition) {
// check the reason object to determine how/if to handle this error
this.transitionTo('someOtherRoute');
}
}
...
});
I much prefer the latter approach as it makes the code easier to read and better separates concerns.
This works well in most cases but I encountered an issue in an app that uses nested routes. I've included a simplified example followed by a jsbin that demonstrates the issue.
Lets say we want to show Articles that belong to Authors and the URLs look like: /authors/:author_slug/articles/:article_slug. We want to redirect to a Not Found page when someone tries to view an article that doesn't exist.
When managing control flow in the model callback as above, you can browse to /authors/some_author/articles/some_invalid_slug and be redirected to /authors/some_author/articles/not_found as expected.
However, if the redirect to the Not Found page is instead managed via the error action, the parent context is lost at some point and you end up at /authors/undefined/articles/not_found.
You can see this in the following jsbins:
http://jsbin.com/eJOXifo/1#/authors/schneier/articles/12345
(redirects to http://jsbin.com/eJOXifo/1#/authors/schneier/articles/not_found)
http://jsbin.com/oNaWelo/1#/authors/schneier/articles/12345
(redirects to http://jsbin.com/oNaWelo/1#/authors/undefined/articles/not_found)
Does anyone know why this happens or how to avoid it?
Notes:
I know this doesn't have anything to do with Ember Data. However, implementing something equivalent without Ember Data just makes the example more complicated without adding anything
There are a few small hacks to make Ember Data work as expected in jsbin:
I'm preloading the parent model to avoid having to load it from anywhere.
I'm not doing anything special to provide data for the child model. The app just makes a request to http://jsbin.com/articles/12345. This actually returns a 200 but bombs anyway because the response is html. An API that correctly returns a 404 response gives the same behvaiour.
I remember a while ago reading about some service that could be used to build fake API responses for use with services like jsfiddle or jsbin. If anyone knows what it is please comment.
Your right that the parent context is getting lost. The trick is to extract that context from the transition and pass it as an argument when calling transitionTo. So:
App.ArticlesArticleRoute = Em.Route.extend({
actions: {
error: function(reason, transition) {
console.log('in error handler');
this.transitionTo('articles.notFound', transition.resolvedModels.authors);
}
}
});
With this change, visiting the url:
http://jsbin.com/iVOYEvA/2#/authors/schneier/articles/my-fake-article
will redirect to:
http://jsbin.com/iVOYEvA/2#/authors/schneier/articles/not_found
In my route I have a method that tries to request a list of models from the server
model: ->
App.MyModel.find
projectId: (#modelFor "project").id
Now obviously sometimes this might return a 404.
At the moment when this happens, Ember just stops doing anything. No view is rendered, no controller is setup.
So how can I properly handle the 404 (ie show an error view)?
Bad news: right now, ember-data doesn't do anything when it gets a 404 on find(). At all. The model sits in the 'loading' state forever.
There are no non-completely-stupid options, here, in my opinion. What I would probably do as a last resort is add a notFound attribute on my DS.Model, and instead of returning 404, return JSON with notFound set to true. It's painful, I know...
--- I had originally offered a solution of overriding find in a subclass of RESTAdapter. Then I noticed that find DOES NOT get passed the record instance it is supposedly loading. So, no go on handling 404s by putting the record into an error state.
[NOTE: ember-data has changed dramatically since March 2013, the information in this answer may no longer be operative]
Incidentally, the "new" BasicAdapter was just released now. The question for me was, will this make things easier to handle 404 errors.
Approach #1
My first approach - similar to what Christopher was suggesting - was to add an additional field containing the HTTP status.
status: DS.attr("number");
And then I used this AJAX call:
$.getJSON(url, data).then(null, function(xhr) {
return {
id: id,
statusCode: xhr.status
};
}).always(function(data) {
return process(data).load();
});
What this does is to transform the error response (xhr) to a hash containing the requested id and the status code. Finally, the successful result or the failed hash are passed to the store.
This kind of works, but isn't very practical: When you show a list of all model instances those "mock" instances have to be filtered out manually.
Approach #2
Another idea was to create a special error model.
App.Error = App.Model.extend({
status: DS.attr("number")
});
And the according query:
$.getJSON(url, data).then(null, function(xhr) {
return App.store.load(App.Error, {}, {
id: 0,
status: xhr.status
});
}).done(function(data) {
return process(data).load();
});
This will load and create a new instance of the error model and put it into the store.
The problem with this is that Ember wasn't really "accepting" this. The application just stopped routing, doing nothing anymore. So this seems like a dead end as well :(
I hit this issue as well today.
However, after looking at the source, it appears the model is actually setup to utilize Ember.Evented, and we can add our own handlers for these cases.
The two events that caught my eye were becameError and didLoad.
In my case I was able to do something like the following:
// Grab a user by id.
var user_rec = App.User.find( user.id );
// Oh no! Error!
user_rec.on('becameError', function() {
alert('I could not find you!');
});
// We should be good! Proceed!
user_rec.on('didLoad', function() {
alert('Your email: '+this.get('email'));
});
Here's the source on github: https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/model/model.js
Hopefully, if this is indeed the way we should be handling things, there will be some added documentation in the guides in the near future.