Override computed property in Ember 4 adapter - ember.js

In the "new" class style of Ember / JavaScript (at least new to me), I am having trouble adapting old tutorials to set a computed property in my JSONAPIAdapter.
export default class ApplicationAdapter extends JSONAPIAdapter {
#service session;
headers: computed('session.isAuthenticated', 'session.data.authenticated.token', function() {
if (this.session.isAuthenticated) {
return {
Authorization: `Bearer ${ this.session.data.authenticated.token }`,
Results in
$TMPDIR/embroider/b3d2a6/adapters/application.js/application.js: Unexpected token (8:9)
6 | #service session;
7 |
> 8 | headers: computed('session.isAuthenticated', 'session.data.authenticated.token', function() {
The docs ( https://api.emberjs.com/ember-data/4.3/classes/JSONAPIAdapter/properties/headers?anchor=headers ) imply that this should work, but I think the #service session line is messing things up.
What is the correct way to set the headers property an Ember 4 class in 2022?

Not sure exactly what error you're seeing, but you should be able to replace this with a getter
get headers() {
if (this.session.isAuthenticated) {
return {
Authorization: `Bearer ${ this.session.data.authenticated.token }
}
}
}
You can see a working example in our addapter

Related

JSONAPISerializer not returning underscore case to API

I am trying to retrieve a single record by returning
singleGroup: this.store.findRecord('individual_group', group_id)
I am then getting this error in the console:
GET http://localhost:4200/api/v1/individual-groups/349 404 (Not Found)
where it seems to be dasherizing individual_groups to individual-groups
Now in the API documentation, an individual group is found by doing a GET request to api/v1/individual_groups/:id
I'm using the JSONAPIAdapter and already have a serializer set up as so:
export default DS.JSONAPISerializer.extend({
keyForAttribute: function(attr) {
return Ember.String.underscore(attr);
},
keyForRelationship: function(attr) {
return Ember.String.underscore(attr);
}
});
but it does not seem to be affecting this particular case.
Is there a way to make sure that when I call this.store.findRecord('individual_group', group_id) it will make the request to the API using
GET http://localhost:4200/api/v1/individual_groups/349
instead of
GET http://localhost:4200/api/v1/individual-groups/349
You should redefine pathForType adapter method. The default implementation uses dasherize:
//as is
pathForType: function(modelName) {
var dasherized = Ember.String.dasherize(modelName);
return Ember.String.pluralize(dasherized);
}
//to be
pathForType: function(modelName) {
var underscored = Ember.String.underscore(modelName);
return Ember.String.pluralize(underscored);
}

Adding headers after RESTAdapter initialization

I am trying to add an Authorization header to my adapter's request after the adapter has been initialized and used. I can add headers in a static way at the time I create my ApplicationAdapter, but I can't seem to get it use the headers in subsequent REST calls. I am trying this:
var auth= "Basic " + hash;
App.ApplicationAdapter.reopen({
headers: {
Authorization: auth
}
});
I have debugged RESTAdapter in the ajax method, and the test for adapter.headers is always undefined.
The accepted answer doesn't address the fact that the recommended approach is not working in ember-data. I say recommended since:
https://github.com/emberjs/data/blob/master/packages/ember-data/lib/adapters/rest_adapter.js#L88
https://github.com/emberjs/data/blob/master/packages/ember-data/lib/adapters/rest_adapter.js#L162
and other places in that file.
Further, the issue the OP brings up with of undefined specifically happens here:
https://github.com/emberjs/data/blob/master/packages/ember-data/lib/adapters/rest_adapter.js#L619
So, the following simply does not work:
App.ApplicationAdapter.reopen({
headers: {token: 'reopen_token (NO WORK)' }
});
I've tried to point to this out as an issue but it got closed within an hour:
https://github.com/emberjs/data/issues/1820
Hopefully core will decide to either fix this or remove the comments. But, yes, for now it seems you have to hijack jQuery ajax setup, Ember.$.ajaxPrefilter, or override the ajax on the adapter yourself.
EDIT: So after getting some more feedback from Ember devs, it looks like the core of this issue is trying to reopen an instance already created. So using a computered property when it's defined (so it will update as desired) seems to be the advised approach. Hope that helps (there's a recently merged pull request that makes this more obvious in the comments of referenced file:https://github.com/emberjs/data/pull/1818/files#diff-1d7f5a5b77898df15de501c3c38d4829R108 )
EDIT 2: Got this working in my app so here's the code in case someone else gets stuck:
//app.js
App.ApplicationAdapter = DS.ActiveModelAdapter.extend({
namespace: 'api/v1',
headers: function() {
return {
token: this.get('App.authToken') || localStorage.getItem('token')
};
}.property("App.authToken")
});
//login-controller.js (only action shown..assume `data` has user/pass)
actions: {
login: function() {
$.post('/token/', data).done(function(user) {
App.set('authToken', user.token);
//Above will trigger adapters's header computed property to update
// Transition to previous attempted route
var attemptedTransition = self.get('attemptedTransition');
if(attemptedTransition) {
attemptedTransition.retry();
}
else {
self.transitionToRoute('yourapproute');
}
})
.fail(function(response) {
//fail handling omitted
});
The answers are already introduced in official API document.
http://emberjs.com/api/data/classes/DS.RESTAdapter.html#toc_headers-customization
Use computed property with session injection
or just use volatile computed property
You should be able to use $.ajaxPrefilter to add custom headers (or params).
See: http://api.jquery.com/jQuery.ajaxPrefilter/
Ember.$.ajaxPrefilter(function( options, oriOptions, jqXHR ) {
var auth= "Basic " + hash;
jqXHR.setRequestHeader("Authorization", auth);
});

How can I modify an adapter after login?

Heres my setup:
Ember: 1.1.0-beta.1
Ember-data: 1.0.0-beta.2
# ON STARTUP
APP.ApplicationAdapter = DS.RESTAdapter.extend(
headers: {
'X-API-TOKEN': localStorage.token
}
)
This works fine if they were already logged in when they refresh...
However...
#ON LOGIN FORM SUBMIT ACTION
$.post('/sessions', data).then( (response) =>
if response.token
localStorage.token = response.token
APP.ApplicationAdapter = DS.RESTAdapter.reopen(
headers: {
'X-API-TOKEN': localStorage.token
}
)
else
#set('error_message', response.error)
The calls are still unauthorized, and the adapter keep trying to pass the old token.
So basically the 'headers' property on the adapter is not updated by the 'reopen' method. Whats the proper way to change the adapter in ember-data?
Thanks guys
Once instantiated you should get your adapter from the container.
But since it's not very clear from where you are making the request, try something like this:
#ON LOGIN FORM SUBMIT ACTION
$.post('/sessions', data).then( (response) =>
if response.token
localStorage.token = response.token
adapter = APP.__container__.lookup 'adapter:Rest'
adapter.set('headers', { 'X-API-TOKEN': localStorage.token })
else
#set('error_message', response.error)
Note: using App.__container__ is not recomended for production code depending from where you are making the request it would be more appropriate to get the container in a more clean way.
Update in response to your last comment
In the case you need access to the container in a controller then:
APP.LoginController = Ember.ObjectController.extend
...
adapter = #get('container').lookup 'adapter:application'
adapter.set('headers', { 'X-API-TOKEN': localStorage.token })
...
should get you the adapter.
Hope it helps.
Headers can be used as a computed property to support dynamic headers.
You can use the volatile function to set the property into a non-cached mode causing the headers to be recomputed with every request.
APP.ApplicationAdapter = DS.RESTAdapter.extend(
headers: function() {
return {
'X-API-TOKEN': localStorage.token
};
}.property().volatile()
)
URL's:
toc_headers-customization
method_volatile

Ember Data 1.0.0: how to retrieve validation errors (422 response)

I am converting from Ember data 0.13 to 1.0.0 Beta 1. In 0.13, I was using the becameError and becameInvalid states to know whether there was a problem when saving a record.
In 1.0.0 there is no longer a transaction and you need to use the save promise to handle errors. See below:
save: function() {
this.get('model').save().then(function () {
alert("Record saved");
}, function () {
alert("Problem");
});
},
In the above, I want to make a distinction between validation errors and all the rest (just as it was before in 0.13 with becameError and becameInvalid).
Is there a way to access the error object and how to read the validation errors included in the json response ? Before this was via this.get('content.errors') ...
Hoep somebody can help
Marc
Three steps:
Return errors in a proper format. If it Rails application, then:
\# Rails controller, update function
format.json { render json: {errors: #post.errors.messages}, status: :unprocessable_entity }
Set errors in promise
// app.js
save: function() {
self = this;
this.get('model').save().then(function () {
alert("Record saved");
}, function (response) {
self.set('errors', response.responseJSON.errors);
});
}
Display errors in a handlebar template
<\!-- index.html -->
{{input type="text" value=title}}<span class="alert-error">{{errors.title}}</span>
Not sure if this helps you substitute the bacameInvalid and becameError since states are now being removed, but you could try this as a catchall workaround:
Ember.RSVP.configure('onerror', function(error) {
console.log(error.message);
console.log(error.stack);
});
Hope it helps.

How do I observe *all* property changes on a model object?

I have a model built from a JSON object.
// extend the json model to get all props
App.Model = Ember.Object.extend(window.jsonModel);
I want to automatically save the model when anything is updated. Is there any way I can add an observer to the whole model?
EDIT: // adding the solution I currently go
For now I do:
// XXX Can't be right
for (var prop in window.jsonModel) {
if (window.jsonModel.hasOwnProperty(prop)) {
App.model.addObserver(prop, scheduleSave);
}
}
This is a large form, which means I'm adding tons of observers – it seems so inefficient.
A firebug breakpoint at Ember.sendEvent() reveals that there are events called App.model.lastName:change being sent. I could hack in an intercept there, but was hoping for an official way.
You can bind to isDirty property of subclass of DS.Model. The isDirty changes from false to true when one of model properties changes. It will not serve well for all cases because it changes only once until reset or committed, but for your case -
I want to automatically save the model when anything is updated. Is there any way I can add an observer to the whole model?
it may work fine.
From the article:
autosave: function(){
this.save();
}.observes('attributes'),
save: function(){
var self = this,
url = this.get('isNew') ? '/todos.json' : '/todos/'+this.get('id')+'.json',
method = this.get('isNew') ? 'POST' : 'PUT';
$.ajax(url, {
type: 'POST',
// _method is used by Rails to spoof HTTP methods not supported by all browsers
data: { todo: this.get('attributes'), _method: method },
// Sometimes Rails returns an empty string that blows up as JSON
dataType: 'text',
success: function(data, response) {
data = $.trim(data);
if (data) { data = JSON.parse(data); }
if (self.get('isNew')) { self.set('id', data['todo']['id']); }
}
});
},
isNew: function(){
return !this.get('id');
}.property('id').cacheable(),
I had the same requirement, and not finding a suitable answer, I implemented one.
Try this: https://gist.github.com/4279559
Essentially, the object you want to observe all the properties of MUST be a mixed of Ember.Stalkable. You can observe the properties of that object as 'item.#properties' (or, if you bake observers directly on the Stalkable, '#properties' alone works. "#ownProperties", "#initProperties" and "#prototypeProperties" also work, and refer to (properties that are unique to an instance and not defined on any prototype), (properties that are defined as part of the create() invocation), and (properties that are defined as part of the class definition).
In your observers, if you want to know what properties changed and invoked the handler, the property "modifiedProperties", an array, will be available with the names of the changed properties.
I created a virtual property _anyProperty that can be used as a dependent key:
import Ember from 'ember';
Ember.Object.reopen({
// Virtual property for dependencies on any property changing
_anyPropertyName: '_anyProperty',
_anyProperty: null,
propertyWillChange(keyName) {
if (keyName !== this._anyPropertyName) {
this._super(this._anyPropertyName);
}
return this._super(keyName);
},
propertyDidChange(keyName) {
if (keyName !== this._anyPropertyName) {
this._super(this._anyPropertyName);
}
return this._super(keyName);
}
});