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?
Related
So Ember Data Model has a deleteRecord() that performs a destroyRecord() without submitting it to the backend.
How do I do save() without submitting it to the backend?
The reason I need it is that I'm using a custom service to batch-save multiple records of different types (models) in one request. I'm successfully sending the requests, and records are persisted on the backend.
But as the request does not go through the Ember Data pipeline, the response from server will be discarded unless I handle it manually.
Basically, I have this in a service:
// Accepts an array of records of mixed types,
// both existing and new
batchSave (records) {
this
.customAjax(records) // The records are persisted
.then(payload => { // Response from the backend with updated records
store.pushPayload(payload); // Now all records have been updated with their current state
// Now all the records are in their current state.
// But they are still dirty!
// How do I mark them clean and saved
});
I've seen this but it seems to discard dirty attributes, while I want dirty attributes to become clean.
I've also tried store.didSaveRecord() but after it records are still dirty.
This is an extension of #Tom Netzband's proposal with a big more sugar.
First, a mixin for adapters:
// mixins/prevent-save-adapter.js
export default Ember.Mixin.create({
preventSave: false,
updateRecord(store, type, snapshot) {
if (!this.get('preventSave'))
return this._super(store, type, snapshot);
this.set('preventSave', false);
return true;
}
});
Then one for models:
// mixins/prevent-save-model.js
export default Ember.Mixin.create({
saveWithoutSave() {
var modelName = this.constructor.modelName;
var adapter = this.adapterFor(modelName);
adapter . set('preventSave', true);
return this.save();
}
});
The post adapter:
// adapters/post.js
export default ApplicationAdapter.extend(PreventSaveAdapter);
And the post model:
// models/post.js
export default DS.Model.extend(PreventSaveModel, {
...
);
Using this:
// controllers/some-controller.js
export default Ember.Controller.extend({
actions: {
someAction () {
(...)
post.saveWithoutSave();
}
}
});
Untested.
Disclaimer: This isn't an ideal solution and I hope someone can point us both in a better direction.
Edit: torazaburo's solution on this thread seems like the best way to go.
I've run into the same situation and haven't found a great solution. I ended up writing a custom adapter and added a service to just return true in updateRecord if the service had a flag of preventRequest: true.
Example:
// services/prevent-request.js
export default Ember.Service.extend({
prevent: false // default
});
// adapters/post.js
export default ApplicationAdapter.extend({
preventSave: Ember.inject.service(),
updateRecord (store, type, snapshot) {
if (this.get('preventSave.prevent')) {
this.set('preventSave.prevent', false);
return true;
}
this._super(store, type, snapshot);
}
});
// controllers/some-controller.js
export default Ember.Controller.extend({
preventSave: Ember.inject.service(),
actions: {
someAction () {
(...)
this.get('preventSave').set('prevent', true);
post.save();
}
}
});
According to Ember Guides using store.createRecord() will created the record and add it to the store but it won't make a request to the backend.
Example:
store.createRecord('post', {
title: 'Rails is Omakase',
body: 'Lorem ipsum'
});
The store object is available in controllers and routes using this.store.
Then if you want to persist it, just call save().
Example:
post.save(); // => POST to '/posts'
isDirty means that the record has local changes that have not yet been saved by the adapter. This includes records that have been created (but not yet saved) or deleted.
Dirty states have three child states:
uncommitted: the store has not yet handed off the record to be saved.
inFlight: the store has handed off the record to be saved, but the adapter has not yet acknowledged success.
invalid: the record has invalid information and cannot be send to the adapter yet.
If you want to make a record clean, try something like this (untested by me):
record.get('stateManager').send('becameClean');
I have this action. Data comes from a form after a createRecord, and it is saved perfectly in the database.
App.ShowController = Ember.Controller.extend({
actions: {
createShow: function() {
var self = this;
var onSuccess = function(res) {
alert('CREATED OK ' + res.get('alias'));
};
var onFail = function() {
alert('err ' + res);
};
self.get('model').save().then(onSuccess, onFail);
}
}
});
The id is generated in the database (Postgres), and I return it in a perfectly formatted json response from the app (made in Mojolicious) {"serverResponses":{"last_id":"500"}} along with a '200' status.
I can see the network response in the console, with the json data.
But how can I access the last_id value in the callback function onSuccess????
In "res" I have the original data I sent to the server, but, obviously its "id" attribute is undefined.
My idea es to "set" the returned id from the database in the "id" of the model.
I have seen a lot of questions about the returning format of the son, serialize problems, and so on, but what I really want to know is WHERE, in WHICH variable or object is the returned data?
Obviously, in case of Fail, I have the same problema. I return a perfectly formatted json with root for ember, but can't find it in the callback function onFail.
Can someone point me in the right direction?
Regards
When you create the record on the server you can return the json of the record, including ID (the same way you would return the json for a GET request). Ember data will then automatically use that response to update the model in its store. Then the argument passed to onSuccess will contain the updated model with the generated ID.
If it's not possible to change the REST api, you'll have to look into extending RESTSerializer to extract the id from the payload.
You need to configure properly your model, for example if you have an x model inside your controller create show action
//create your model locally
//acquisition is for the name of your model
//product_id is just whatever attributes you declare in your model spec
var newAcquisition = this.store.createRecord('acquisition', {
'product_id': this.get('id'),
});
//with save it calls to the server and creates the new model and retrieves a response
newAcquisition.save().then(function(response){
console.log('success - json '+response);
}, function(response){
console.log('fail - err '+response);
});
After this, you don't need to catch response to put and id, if your json response is correct, Ember will handle that response and update your newly created object with that new ID
You can save data in route to controller in setupController function
App.ShowRoute = Ember.Route.extend({
setupController: function(controller, model){
//setup form data
controller.set('formData', data);
},
actions:{
}
});
I am wondering about the best practice for creating a new record in Ember with createRecord() and then persisting it to the API? Specifically, should Ember's POST request generally be a single JSON that embeds all the model's relationships, or is it customary to POST each relationship individually?
In my code, I'm not able to get a single JSON, so I'm wondering if I'm missing the "Ember Way" or (more likely) I have a mistake in my code?
DETAILS:
Here are the details of my setup. I have two models:
/models/OrgUser.js:
DS.Model.extend({
...
orgPerson: DS.belongsTo('org-person', { inverse: 'org-user', async: true, embedded: 'always' }),
});
/models/OrgPerson.js:
DS.Model.extend({
...
orgUser: DS.belongsTo('org-user'),
})
I'm attempting to create a new user on the "Create New User" page. The route for that page is below. Is this the best place to call createRecord() for my new models?
/routes/org-users/add.js:
Ember.Route.extend({
model: function() {
var orgPerson = this.store.createRecord('org-person');
var orgUser = this.store.createRecord('org-user' );
orgUser.set('orgPerson', orgPerson);
return orgUser;
},
...
}
Using Chrome console to look at the orgUser object after I call set shows no evidence at all that I have added anything to orgUser. The "Ember" tab of Chrome Debug Tools does reflect the relationship, though.
On my "Create New User" page, my input fields all correspond to both OrgUser properties and OrgUser.OrgPerson properties. Here's an example:
/templates/org-users/add.hbs
...
{{input value=username}} // a property of OrgUser
{{input value=orgPerson.firstName}} // a property of OrgUser.orgPerson
...
In my route, when I go to save() Ember Data POSTs only the orgUser JSON with a null value for orgPerson. I'd like it to embed the orgPerson serialized object in the orgPerson property.
/routes/org-users/add.js:
Ember.Route.extend({
...
actions: {
submitForm: function() {
...
this.currentModel.save().then( onSuccess ).catch( onFailure );
...
}
}
});
This results in a POST request with the following body:
{
"orgUser":{
"username":"MyUsername",
"orgPerson":null,
"id":null
}
Note that orgPerson is null. Thanks in advance for any assistance!
UPDATE: Once again, I think I will need to take a fresh look at my serializer. Here's how it's currently defined.
/serializers/application.js:
DS.RESTSerializer.extend({
// Use the default approach to serializing, but add the id property
serialize: function(record, options) {
var json = this._super.apply(this, arguments);
json.id = record.id;
return json;
},
serializeBelongsTo: function(record, json, relationship) {
var key = relationship.key;
key = this.keyForRelationship ? this.keyForRelationship(key, 'belongsTo') : key;
var data = record.get('data');
if (relationship.options.embedded && relationship.options.embedded === 'always') {
json[key] = data[relationship.key] ? data[relationship.key].serialize( { includeId: true } ) : null;
}
else {
json[key] = data[relationship.key] ? data[relationship.key].get('id') : null;
}
if (relationship.options.polymorphic) {
this.serializePolymorphicType(record, json, relationship);
}
}
});
Per #Kingpin2k's comment, there appears to be some ambiguity (and bugs!) on how best to handle serialize() for a belongsTo relationship. My serializer customization above works great for records that are obtained through this.store.find(), but now I need to enable them for createRecord(). Additional suggestions, pointers are welcome!
It's a bug. https://github.com/emberjs/data/issues/1542#issuecomment-49443496
A workaround is to get the async belongsTo record before attempting to save (It tricks Ember Data into initializing it). In your case you could do it in the model hook.
model: function() {
var orgPerson = this.store.createRecord('org-person');
var orgUser = this.store.createRecord('org-user');
orgUser.set('orgPerson', orgPerson);
return orgUser.get('orgPerson').then(function(){
return orgUser;
});
},
So, I finally figured this out. With the release of Ember-Data-1.0.0-Beta.9, http://emberjs.com/blog/2014/08/18/ember-data-1-0-beta-9-released.html, the EmbeddedRecordsMixin has been introduced. This pretty much solves all my issues!
So, I wound up doing the following:
Upgraded to Ember-Data-1.0.0-Beta.9
Deleted my serializeBelongsTo customization from my serializer
I now define a custom serializer for each model using the EmbeddedRecordsMixin as documented at http://emberjs.com/api/data/classes/DS.EmbeddedRecordsMixin.html.
This wound up working perfectly, because I get full declarative control over how and when my records are embedded.
Special thanks to #Kingpin2k for helping me realize my serializer was the problem and for the discussion to help me understand the options.
This may be abusing Ember, but I want to create a computed property for the number of items in the store.
I'm trying to prototype a UI that exists entirely client-side. I'm using fixture data with the local storage adapter. That way, I can start off with canned data, but I can also add data and have it persist across reloads.
As I'm currently working on the data layer, I've built a settings route that gives me a UI to reset various models. I would like to add a Handlebars expression like {{modelCount}} so I can see how many records there are in the store. That's quicker than using the Ember Data Chrome extension, which resets to the routes tab on every page reload.
The following will show me the number of records once, but does not change when the number of records changes:
modelCount: function() {
var self = this;
this.store.find("my_model").then(function(records) {
self.set("modelCount", records.get("length"));
});
}.property()
I get that the store is supposed to proxy an API in the real world, and find returns a promise, but that's about the limit of my knowledge. I don't know how tell Ember to that I want to know how many records there are, or if this is even a valid question.
Load the result of store.find into an Ember.ArrayController's content and then bind the length of content to modelCount. An example:
App.SomeRoute = Ember.Route.extend({
model: function(){
return this.store.find('my_model');
}
});
App.SomeController = Ember.ArrayController.extend({
modelCount: Ember.computed.alias('content.length')
});
See a working example in http://jsbin.com/iCuzIJE/1/edit.
I found a workable solution by combining the answer from #panagiotis, and a similar question, How to load multiple models sequentially in Ember JS route.
In my router, I sequentially load my models:
model: function() {
var self = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
self.store.find("model1").then(function(model1) {
self.store.find("model2").then(function(model2) {
self.store.find("model3").then(function(model3) {
resolve({
model1: model1,
model2: model2,
model3: model3
});
});
});
});
});
},
Then in my controller, I define simple computed properties:
model1Count: function() {
return this.get("model1.length");
}.property("model1.length"),
...
I'm working with a set of data that can potentially have duplicate values. When I initially add the data I'm using what little information I have available on the client (static info stored on the model in memory).
But because I need to fetch the latest each time the handlebars template is shown I also fire off a "findAll" in the computed property to get any new data that might have hit server side since the initial ember app was launched.
During this process I use the "addObjects" method on the ember-data model but when the server side is returned I see duplicate records in the array (assuming it's because they don't have the same clientId)
App.Day = DS.Model.extend({
appointments: function() {
//this will hit a backend server so it's slow
return App.Appointment.find();
}.property(),
slots: function() {
//no need to hit a backend server here so it's fast
return App.Slot.all();
}.property(),
combined: function() {
var apts = this.get('apppointments'),
slots = this.get('slots');
for(var i = 0; i < slots.get('length'); i++) {
var slot = slots.objectAt(i);
var tempApt = App.Appointment.createRecord({start: slot.get('start'), end: slot.get('end')});
apts.addObjects(tempApt);
}
return apts;
}.property()
});
Is it possible to tell an ember-data model what makes it unique so that when the promise is resolved it will know "this already exists in the AdapterPopulatedRecordArray so I'll just update it's value instead of showing it twice"
You can use
DS.RESTAdapter.map('App.Slot', {
primaryKey: 'name-of-attribute'
});
DS.RESTAdapter.map('App.Appointment', {
primaryKey: 'name-of-attribute'
});
But I think it is still impossible because App.Slot and App.Appointment are different model classes, so if they have same ids it won't help. You need to use the same model for both slots and appointments for this to work.
Edit
After examinig the source of ember-data, i think that you can define the primaryKey when you define your classes, like:
App.Slot = DS.Model.extend({
primaryKey: 'myId',
otherField: DS.attr('number')
});
I didn't tested it though..
Edit 2
After further reading seems that the previous edit is no longer supported. You need to use map as i wrote earlier.