I just upgraded ember from 1.11.1 to 1.13.2, and ember-data to 1.13.4
Since then, I'm not able to set a property on a resolved promise:
var promise = store.filter('user', {filters: {"googleId":content.id}}, function() {
return promise;
}).then(function(response) {
var user = response.get('content.0');
console.log(user); //--> A user entity (Object { type: makeCtor/Class(), id: "38", store: Object, [...])
user.set('foo', "bar"); // CRASH HERE
}, function(){ [...] });
The error message from the console is no help, since I'm in a .then()
EDIT :
Context :
I'm inside a initializer called session.js. I'm trying to realise a Google authentication. This specific piece of code is just after retrieving the Google infos from the API. I'm trying to see if an user with such a google ID exists in my back-end, and if it's the case (promise resolved), I want to update the user with the up-to-date google informations and save them to my back-end (hence the .set())
Using the debugger, I figured an other way to access the data that works:
var promise = store.filter('user', {filters: {"googleId":content.id}}, function() {
return promise;
}).then(function(response) {
var user = arguments[0];
console.log(user); //--> A user entity
user.set('foo', "bar"); // OK NOW
}, function(){ [...] });
I am really surprised, as the arguments seems to come from nowhere. There is maybe a cleaner way to do it.
Related
I've seen other questions about this (like this one), and I believe this should be working
import Ember from 'ember';
import Session from 'simple-auth/session';
export default {
name: 'session-with-me',
before: 'simple-auth',
initialize: function() {
Session.reopen({
me: function() {
if (this.get('isAuthenticated')) {
return this.container.lookup('service:store').find('me', { singleton: true });
}
}.property('isAuthenticated')
});
}
};
the find('me', { singleton: true }) is a working patch of ember-jsonapi-resources. While debugging I can see the request being sent, and the payload comes through. I use the same find call elsewhere in the app, and can confirm a model gets instantiated fine.
On the inspector, under container > simple-auth-session I can see me as a session property, but it shows as { _id: 68, _label: undefined ...}
Has the way to set a session property changed? I may have seen a mention about this somewhere, but I can't find it anymore.
This is in the same domain of another question I asked earlier, but I'm giving up on that approach and trying simply to fetch the user independently of the authentication process.
Set up a custom session like that:
export default Session.extend({
me: function() {
var accessToken = this.get('secure.access_token');
if (!Ember.isEmpty(accessToken)) {
return DS.PromiseObject.create({
promise: this.container.lookup('service:me').find({});
});
}
}.property('secure.access_token')
});
// app/config/environment.js
ENV['simple-auth'] = {
session: 'session:me'
}
DS.PromiseObject is actually part of Ember Data which you're not using - I don't know whether there's an equivalent in the library you chose.
This is most likely an issue with ember-jsonapi-resources, not with Ember Simple Auth.
Instead of reopening the session though you should define your own one that extends the default one that Ember Simple Auth provides - see e.g. this answer: How to store the user in a session
We ended up making it work like this:
// app/sessions/me.js
export default Session.extend({
me: function() {
var accessToken = this.get('secure.access_token');
if (!Ember.isEmpty(accessToken)) {
let self = this;
return this.container.lookup('service:me').find({}).then((me) => {
self.set('me', me);
});
}
}.property('secure.access_token')
});
// app/config/environment.js
ENV['simple-auth'] = {
session: 'session:me'
}
Partly this was due to the way resource services are initialized in EJR (so #marcoow's hunch on this was correct), the other part was just bad coding on my part.
Interestingly we didn't have to explicitly register the session in the container
I'm not sure how to store json data into a model in the controller. Here is my code.
App.LoginController = Ember.ObjectController.extend({
actions: {
login: function() {
var data = this.getProperties("email", "password");
console.log(data);
return $.post('/', {
email: data.email,
password: data.password
}).then(function(data) {
if(data.isFail) {
return data;
} else {
}
});
}
}
});
after the line if(data.isFail) {.... I want to store the json data into a model. How do I do this?
UPDATE
I went with Josh's suggestion of putting the action into the Route
App.LoginRoute = Ember.Route.extend({
actions : {
login: function() {
var data = this.getProperties("email", "password");
console.log(data);
return $.post('/', {
email: data.email,
password: data.password
}).then(function(data) {
if(data.isFail) {
store.createRecord('login', data);
} else {
}
return data;
});
}
}
});
No I'm getting an error Uncaught TypeError: Cannot read property 'normalize' of undefined.
If you have defined a DS.Model and if the contents of your JSON are a subset of the properties defined in your DS.Model then you can just do this:
store.createRecord('my-model-name', data);
This assumes that you have a DS.Model defined called my-model-name.js and that your JSON is in the data var. If you have some properties in your JSON that are not defined in your DS.Model, I'm not sure how Ember Data reacts.
UPDATE: You asked about how to connect the route and the controller. You can use this idiom:
Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller, model);
controller.set('myProperty', 'hello');
controller.set('meta', this.store.metadataFor('org-user'));
}
})
Note that this doesn't really relate much to your original question.
UPDATE #2:
Ok, it looks like you want to attempt a login, and then if the POST request succeeds, but the login itself fails (as indicated by the presence of an isFail property in the json response), then you want to create a new record in your local store? Did I describe your intentions right?
My first question is, are you sure you want to create an Ember record here? An Ember record is basically a "facsimile" of a "real" object that comes from your backend / database. It makes sense to create a new local record if you want to eventually persist that somewhere (e.g. by calling myRecord.save). Maybe you would create a local record for cacheing purposes only, but I personally have not seen that in the wild yet (but don't let me disqualify your usage if you've thought it through).
With that out of the way, let's assume you DO want to create a local record. Then first we actually need a DS.Model that represents the record this will be. I'll define mine like this:
models/login.js
DS.Model.extend({
username: DS.attr('string'),
password: DS.attr('string')
});
Note that I made sure to copy your properties from this.getProperties() because the way you're initializing your model with createRecord('login', data) means that what's in data needs to be a subset of what I just defined above.
Now that I have a model definition, I an get instances of this model from a backend via JSON (or any other format as long as I have the right serializer; Ember by default uses `RESTSerializer, which expects JSON). I can also locally instantiate a new model, or in Ember speak, create a record. Again, my goal in creating a new record is probably that I eventually want to persist it to my backend. But since you're doing your own AJAX calls, I'll leave that part out.
Now onto your code, with slight revisions:
App.LoginRoute = Ember.Route.extend({
actions : {
login: function() {
var _this = this;
var data = this.getProperties("email", "password");
$.post('/', {
email: data.email,
password: data.password
}).then(function(data) {
if(data.isFail) {
var loginModel = _this.store.createRecord('login', data);
loginModel.save(); // not sure if you want to do this?
} else {
}
});
I took out the return statements because I'm guessing you don't need them (I may be wrong). I also decided I would do something with the model instance we just created, in this case save() it, which will trigger a POST request to your backend, as determined by your adapter (by default Ember uses RESTAdapter to determine this).
Note also that I needed access to the current route instance via this but this takes on different meanings as I descend down the code, so I define var _this = this; at the top so I can reference the "real" this when I need it.
Does this solve your issue?
I'm using Ember Data and I can't seem to get the model's 'errors' property to populate with the error messages from my REST API. I'm pretty much following the example at this guide:
http://emberjs.com/api/data/classes/DS.Errors.html
My app looks like this:
window.App = Ember.Application.create();
App.User = DS.Model.extend({
username: DS.attr('string'),
email: DS.attr('string')
});
App.ApplicationRoute = Ember.Route.extend({
model: function () {
return this.store.createRecord('user', {
username: 'mike',
email: 'invalidEmail'
});
},
actions: {
save: function () {
this.modelFor(this.routeName).save();
}
}
});
And my API returns this:
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Length: 125
{
"errors": {
"username": ["Username is taken!"],
"email": ["Email is invalid."]
}
}
After I call save() on the model, here is what I see on the user model:
user.get('isError') // true
user.get('errors.messages') // []
Even though the model is registering the isError property correctly, I can't seem to get the error messages to populate. How can I get this to work? I'm working on the latest beta build of Ember Data version 1.0.0-beta.8.2a68c63a
The docs are definitely lacking in this area, the errors aren't populated unless you're using the active model adapter.
Here's an example of it working, also check out Ember: error.messages does not show server errors on save where I say the same thing
http://jsbin.com/motuvaye/24/edit
You can fairly easily implement it on the RESTAdapter by overriding ajaxError and copying how the active model adapter does it.
App.ApplicationAdapter = DS.RESTAdapter.extend({
ajaxError: function(jqXHR) {
var error = this._super(jqXHR);
if (jqXHR && jqXHR.status === 422) {
var response = Ember.$.parseJSON(jqXHR.responseText),
errors = {};
if (response.errors !== undefined) {
var jsonErrors = response.errors;
Ember.EnumerableUtils.forEach(Ember.keys(jsonErrors), function(key) {
errors[Ember.String.camelize(key)] = jsonErrors[key];
});
}
return new DS.InvalidError(errors);
} else {
return error;
}
}
});
http://jsbin.com/motuvaye/27/edit
https://github.com/emberjs/data/blob/v1.0.0-beta.8/packages/activemodel-adapter/lib/system/active_model_adapter.js#L102
I've had a long and very frustrating experience with Ember Data's errors.messages property, so I thought I'd summarize all of my findings here in case anyone else tries to use this feature.
1) Documentation is out of date
As #kingpin2k mentioned in his answer, the documentation at http://emberjs.com/api/data/classes/DS.Errors.html is out of date. The example they provide on that page only works if you're using DS.ActiveModelAdapter. If you're using the default DS.RESTAdapter, then you need to do something like this. Note that I prefer this simpler approach instead of just copying ActiveModelAdapter's ajaxError implementation:
App.ApplicationAdapter = DS.RESTAdapter.extend({
ajaxError: function (jqXHR) {
this._super(jqXHR);
var response = Ember.$.parseJSON(jqXHR.responseText);
if (response.errors)
return new DS.InvalidError(response.errors);
else
return new DS.InvalidError({ summary: 'Error connecting to the server.' });
}
});
2) You must supply a reject callback
This is very strange, but when you call save() on your model, you need to provide a reject callback, otherwise, you'll get an uncaught 'backend rejected the commit' exception and JavaScript will stop executing. I have no idea why this is the case.
Example without reject callback. This will result in an exception:
user.save().then(function (model) {
// do something
});
Example with reject callback. Everything will work well:
user.save().then(function (model) {
// do something
}, function (error) {
// must supply reject callback, otherwise Ember will throw a 'backend rejected the commit' error.
});
3) By default, only the error properties that are part of the model will be registered in errors.messages. For example, if this is your model:
App.User = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string')
});
...and if this is your error payload:
{
"errors": {
"firstName":"is required",
"summary":"something went wrong"
}
}
Then summary will not appear in user.get('errors.messages'). The source of this problem can be found in the adapterDidInvalidate method of Ember Data. It uses this.eachAttribute and this.eachRelationship to restrict the registration of error messages to only those that are part of the model.
adapterDidInvalidate: function(errors) {
var recordErrors = get(this, 'errors');
function addError(name) {
if (errors[name]) {
recordErrors.add(name, errors[name]);
}
}
this.eachAttribute(addError);
this.eachRelationship(addError);
}
There's a discussion about this issue here: https://github.com/emberjs/data/issues/1877
Until the Ember team fixes this, you can work around this problem by creating a custom base model that overrides the default adapterDidInvalidate implementation, and all of your other models inherit from it:
Base model:
App.Model = DS.Model.extend({
adapterDidInvalidate: function (errors) {
var recordErrors = this.get('errors');
Ember.keys(errors).forEach(function (key) {
recordErrors.add(key, errors[key]);
});
}
});
User model:
App.User = App.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string')
});
4) If you return DS.InvalidError from the adapter's ajaxError (the one we overrode above), then your model will be stuck in 'isSaving' state and you won't be able to get out of it.
This problem is also the case if you're using DS.ActiveModelAdapter.
For example:
user.deleteRecord();
user.save().then(function (model) {
// do something
}, function (error) {
});
When the server responds with an error, the model's isSaving state is true and I can't figure out to reset this without reloading the page.
Update: 2014-10-30
For anyone who's struggling with DS.Errors, here's a great blog post that summarizes this well: http://alexspeller.com/server-side-validations-with-ember-data-and-ds-errors/
UPDATE: Ember Data 2.x
The above response are still somewhat relevant and generally pretty helpful but are now outdated for Ember Data 2.x(v2.5.1 at time of this writing). Here are a few things to note when working with newer versions of Ember Data:
DS.RESTAdapter no longer has an ajaxError function in 2.x. This is now handled by RESTAdapter.handleResponse(). You can override this method if any special handling or formatting of errors is required. RESTAdapter.handleResponse source code
The documentation for DS.Errors and DS.Model.errors(which is an instance of DS.Errors) is currently a little misleading. It ONLY works when errors in the response adhere to the JSON API error object specification. This means it will not be at all helpful or usable if your API error objects follow any other format. Unfortunately this behavior can't currently be overridden well like many other things in Ember Data as this behavior is handle in private APIs inside of Ember's InternalModel class within DS.Model.
DS.InvalidError will only be used if the response status code is 422 by default. If your API uses a different status code to represent errors for invalid requests you can override RESTAdapter.isInvalid() to customize which status codes(or other part of an error response) to check as representing an InvalidError.
As an alternative you can override isInvalid() to always return false so that Ember Data will always create a more generic DS.AdapterError instead. This error is then set on DS.Model.adapterError and can be leveraged as needed from there.
DS.AdapterError.errors contain whatever was returned on the errors key of the API response.
I'm trying to load the current user into the data store but am having some difficulty. The server uses PassportJS and visiting /api/users/me returns a JSON object similar to this:
{"user":{"_id":"53a4d9c4b18d19631d766980","email":"ashtonwar#gmail.com",
"last_name":"War","first_name":"Ashton","location":"Reading, England",
"birthday":"12/24/1993","gender":"male","fb_id":"615454635195582","__v":0}}
My store is just defined by App.store = DS.Store.create();
The controller to retrieve the current user is:
App.UsersCurrentController = Ember.ObjectController.extend({
content: null,
retrieveCurrentUser: function() {
var controller = this;
Ember.$.getJSON('api/users/me', function(data) {
App.store.createRecord('user', data.user);
var currentUser = App.store.find(data.user._id);
controller.set('content', currentUser);
});
}.call()
});
It is called by my application controller:
App.ApplicationController = Ember.Controller.extend({
needs: "UsersCurrent",
user: Ember.computed.alias("controllers.UsersCurrent")
});
I suspect the line App.store.createRecord('user', data.user); is causing issues but I don't have any idea how to fix it.
The console logs TypeError: this.container is undefined while the Ember debugger shows every promise is fulfilled and the users.current controller has no content. Thankyou for any help you can provide.
Are you defining the store on the App namespace, because Ember Data doesn't do that by default. Either way, you're failing to define the type you want to find after you create the record.
var currentUser = controller.store.find('user', data.user._id);
createRecord returns the record, so there is no point in finding it afterward
var currentUser = controller.store.createRecord('user', data.user);
Also in your example, you are trying to call the function immediately on the type, and not on the instance. You should add that as a method to run on init.
App.UsersCurrentController = Ember.ObjectController.extend({
retrieveCurrentUser: function() {
console.log('hello')
var controller = this;
Ember.$.getJSON('api/users/me', function(data) {
var user = controller.store.createRecord('user', data.user);
controller.set('model', user);
});
}.on('init')
});
http://emberjs.jsbin.com/OxIDiVU/693/edit
Sorry to ask such a simple question but I'm looking at migrating from jQuery to Ember and am trying to figure out calling / responding json without using ember-data. One question I have is how do people suggest having class methods. Say for example I have a post object like this:
Hex.Post = Ember.Object.extend({
id: null,
body: null
});
Would a reasonable findById look like this?
$(document).ready(function(){
Hex.Post.findById=function(id){
console.log("you are here");
$.getJSON("/arc/v1/api/post/" + id, function(data){
var post = Hex.Post.create();
post.set('id', data.id);
post.set('body',data.body);
return post;
});
};
});
Or is this just wrong for creating a findById class method?
When I run this from the chrome console, it comes back as undefined even though the JSON call works fine in a brower. What am I doing wrong?
thx
FROM CHROME CONSOLE:
You'd want to define it on the class, and return the ajax call, which is then a promise
Hex.Post = Ember.Object.extend({
id: null,
body: null
});
Hex.Post.reopenClass({
findById: function(id) {
return Ember.$.getJSON("/arc/v1/api/post/" + id).then(function(data){
var post = Hex.Post.create();
post.set('id', data.id);
post.set('body',data.body);
return post;
});
}
});
Using the promise
from a model hook, Ember will resolve the promise for you, example below
Hex.PostRoute = Em.Route.extend({
model: function(param){
return Hex.Post.findById(param.id);
}
});
as the promise
Hex.Post.findById(42).then(function(record){
console.log(record);
});
or
var promise = Hex.Post.findById(42);
promise.then(function(record){
console.log(record);
});
And here's a simple example:
http://emberjs.jsbin.com/OxIDiVU/21/edit