Saving nested models - ember.js

I have two models like this:
App.Build = DS.Model.extend({
allegiance: DS.attr('string'),
profession: DS.attr('string'),
skills: DS.hasMany('skill')
});
App.Skill = DS.Model.extend({
name:DS.attr('string'),
value:DS.attr('number')
});
In my app, I have controls to set the allegiance, profession, and values of each skill (there's up to 55).
Then in the actions hash of my application controller, I have an action to save the build model to the server.
save:function(){
var store = this.get('store');
var skills = this.get('controllers.skills').get('model');
console.log(skills);
var build = store.createRecord('build',{
profession:1,
allegiance:1,
skills:skills
});
build.set('skills',skills);
build.save();
console.log('Saved!');
}
But when the build model is sent to the server the skills property is an empty array:
{"build":{"allegiance":"1","profession":"1","skills":[]}}
I'm sure I'm doing something wrong, but I can't figure out what and can't find any good documentation about it. An additional note, all I care about submitting is the skill id and value.
Any help will be greatly appreciated!
UPDATE:
Following Daniel's suggestion, I've edited the save function to use pushObjects to put the skills into the Build model, then save it. It's working better now. The generated post data is like this now:
{"build":{
"allegiance":1,
"profession":1,
"skills":["1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","32","33","34","35","36","37","38","39","40","41","42","43","44","45","46","47","48","49","50","51","52","53","54","55"]}}
That being a list of the skill ids. None of the other attributes are submitted in the post. I've tried iterating over skills, creating a new object, and just pushing in the id and value, which are the only parts I need, but that gives me an error. Something like, can not use undefined, must be type skill.
This seems like something Ember data should handle natively. Is there something I'm missing to get it to send the other skill attributes in the request?
Thanks!!

If anyone else is interested, I solved the issue by overriding the serlizer with a custom serliazer for the Build model like this:
App.BuildSerializer = DS.RESTSerializer.extend({
serializeHasMany: function(record, json, relationship) {
if(relationship.key === 'skills') {
var skills = record.get('skills');
var block = [];
skills.forEach(function(skill, index) {
var current = {};
current.id = skill.get('id');
current.value = skill.get('value')
block[index] = current;
});
json['skills'] = block;
} else {
return this._super(record,json,relationship);
}
}
});
UPDATE:
There's a much easier way to do this now using the DS.EmbeddedRecordsMixin like this:
App.BuildSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin,{
attrs: {
skills: 'records'
}
});

Is the skills model a RecordArray? That's the underlying model Ember data uses. You might try creating the record then using pushObjects after the fact.
var build = store.createRecord('build',{
profession:1,
allegiance:1
});
build.get('skills').pushObjects(skills);
additionally, save returns a promise, so in order to properly handle the successful save versus failure you can handle it like this.
build.save().then(
function(){
console.log('Saved!');
},
function(){
console.log('Failed to save');
});

Related

How to get actual array from ember model?

I have a model with a hasMany relationship:
var Bus = DS.Model.extend({
model: DS.attr('string'),
passengers: DS.hasMany('passenger', {async: true})
});
This seems to work, in that I am able to iterate through the passengers in my template.
Now I want to have an action in my controller that will do something with the passengers. It involves some business logic, and I'll need to iterate through the list of passengers.
My problem is that when I get the passengers from the model in the controller, it is not an array, it is some sort of object. How do I get an array from this object?
Here's what I have tried:
export default Ember.ObjectController.extend({
actions: {
start: function() {
var bus = this.get('model');
var passengers = model.get('passengers');
passengers.then(function(passengerArray) {
var stuff = passengerArray.get('content');
console.log('The thing that I wish were an array of passengers',passengerArray);
console.log('The type of that thing',typeof(passengerArray));
});
}
}
});
It gives me the type object, and it is clearly something wrapped in emberness in a way that is inscrutable to me.
what's the output in the console of a console.log(passengerArray) if you use a decent browser you will get more than just object and actually beeing able to get informations on your object.
Some how when you use model.get("passengers") you get a PromiseArray of your "passengers" . using then is the correct way to get the datas, you will get an object which implements Ember.Array as the parameter of the function called in the then.
If you want a " raw js Array" you can get it by using passengerArray.toArray() function, if your goal is to iterate or get the lenght or what ever you can use the methods provided by emberArray => http://emberjs.com/api/classes/Ember.Array.html
And as you can see in the documentation above content property is at least not public or may even not exist :) (you can also try a passengerArray.get("[]")) to retrieve the "content"
Apart from if it is required/desirable. This would do:
var bus = this.get('model');
var passengersPromise = bus.get('passengers');
passengersPromise.then(function(passengers) {
console.log('RecordArray', passengers);
var passengerArrayWithRecords = passengers.toArray();
console.log('array with records', passengerArrayWithRecords);
var passengerArray = passengers.map(function(record) {
return record.toJSON();
});
console.log('plain array with javascript objects', passengerArray);
});
See it in action here: http://emberjs.jsbin.com/mapiyafaxa/2/edit?html,js,output
Reference:
Ember.Map: http://emberjs.com/guides/enumerables/#toc_map
DS.RecordArray: http://emberjs.com/api/data/classes/DS.RecordArray.html
What you are getting is a promise, since its an async relation. to access the actual array just do:
bus.get('passengers').then(function(passengers) {
// passengers is the actual array
});
When I tried it out using fixtures (so data is already in the store), I was able to get the array using
var bus = this.get('model');
var passengers = model.get('passengers').get('currentState');
I was then able to use passengers[0].get('content') (which is what I assume you want(?)). My guess is that for fetching the data from the server, you will want to use
var bus = this.get('model');
var passengers = model.get('passengers');
passengers.then(function(passengerArray) {
var passengerContent = passengerArray.get('currentState')[0].get('content')
});

how to store json response into model ember.js

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?

Best Practice for Creating New Record with belongsTo Relationship

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.

Adding hasMany items to a post ember not working

I'm trying to create a new post and I want to add some tags to that post when I create it.
So let's say I have a list of tag objects in an array.
The hasMany tags relationship is an async one by the way.
Here is my code
var post = store.createRecord('post', {
name: this.get('name'),
slug: this.get('slug'),
description: this.get('description'),
});
post.get('tags').then(function(post) {
post.pushObjects(this.get('selectedTags'));
});
post.save().then(...);
But in the POST request the data that's being sent has tags as a blank array like tags:[]
The tags already exist in the selectedTags variable in the controller. These tags are coming straight from the server.
So I have no clue why this is not working.
If anyone has an idea, that would be great.
the post.get('tags') is an async call, so it will happen after the save in the stated scenario and should be changed to something like this:
var self = this;
post.get('tags').then(function(tags) {
tags.pushObjects(self.get('selectedTags'));
post.save().then(...);
});

How to remove duplicate records in ember-data during addObjects

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.