How to get actual array from ember model? - ember.js

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')
});

Related

How can I load associated records the first time they are accessed in Ember?

I have a computed property, which fetches an associated record and tries to print it. The first time I fetch the record, it's null. All subsequent accesses work correctly. It is set as 'async: true', but setting it as false doesn't change this behavior.
MyApp.ThingsController = Ember.ArrayController.extend({
myProperty: function() {
var content = this.get('content');
return content.filter(function(thing) {
console.log(thing.get('title')); // Since this is a direct attribute on the model, it prints fine.
var associatedThing = thing.get('associatedThing'), otherThings = [];
console.log(associatedThing.get('content')); // This is a hasMany attribute on the model, and is null the *first* time, but fine on subsequent accesses.
otherThings = associatedThing.get('content'); // Obviously doesn't work the first time either.
return thing.get('title') + otherThings[0].get('name'); // Or similar.
});
}.property('content.#each') // adding observers for content.associatedThing.#each does not seem to make any difference.
});
Models are like:
MyApp.Thing = DS.Model.extend({
title: DS.attr('string'),
associatedThings: DS.hasMany('associatedThing', { async: true })
});
MyApp.AssociatedThing = DS.Model.extend({
name: DS.attr('string')
});
Obviously, I cannot use promises here since I need to return a value from the function, so I cannot use a callback (since we're in a computed property.) How can I make this work the first time this associated record is accessed?
Edit: myProperty is a computed property on an ArrayController, and is used for showing or hiding Things
Actually, you can use a promise, just not in the way you're thinking. For hasMany relationships, Ember-Data returns a PromiseArray. That means that it returns a promise that will resolve to an array. But in the meantime, the proxy will actually respond to get requests that you make with undefined. Then, when the promise resolves, any observers are fired. So, if you have your property depend on the associatedThings property, it will update when the promise resolves. In other words, this will work as expected:
MyApp.Thing = DS.Model.extend({
title: DS.attr('string'),
associatedThings: DS.hasMany('associatedThing', { async: true }),
sum: function() {
var things = this.get('associatedThings');
return things.filter(function(thing) {
return shouldFilterThing(thing);
});
}.property('associatedThings.#each.size')
});
Also, please don't be bugged by the fact that this doesn't happen synchronously. Trying to change it from asynchronous to synchronous will just make your code that much more fragile. Let Ember do its job and handle all of the properties and bindings for you.
My solution to this was simply to access the associated data in the ArrayController's init method:
init: function() {
var content = this.get('content');
content.forEach(thing) {
// Prime the data.
var associatedThings = get('associatedThings');
});
}
This makes everything work as expected.

Computed property with `#each` won't update

I've created a computed property that relies on all records in the store.
I've tried making the property update on adding/removing records with .property('todos.#each.id'), .property('model.#each.id'), .property('#each.id'), .property('#each') and other combinations, no luck so far. :( When i create new records, existing recrods' property would not update.
Here's a fiddle: http://jsbin.com/UDoPajA/211/edit?output
The property is otherTodos on the Todo controller. This property is used by the <select> dropdown list on the page (via {{view Ember.Select}}).
You're out of scope of the collection. You'll need to get access to the todos controller in order to have a computed property based off of its model. needs will handle this use case. http://emberjs.com/guides/controllers/dependencies-between-controllers/
Additionally to make an easy to access alias to the todos controller's model we use computed.alias. http://emberjs.com/api/#method_computed_alias
Todos.TodoController = Ember.ObjectController.extend({
needs:['todos'],
todos: Ember.computed.alias('controllers.todos.model'),
....
foo: function(){
}.property('todos.#each.id')
});
PS note of caution, in your code you are creating multiple instances of Ember Data filter, filter collections are meant to be live collections that are long living and update as records are added/removed from the store. You might just want to grab the model from todos and filter over it instead of creating a new store filter (which then avoids the async code as well, not that that is an issue).
Here's an implementation that would avoid that (no point in using it as a setter, you are only getting from it):
otherTodos: function() {
var model = this.get('model'),
thisId = model.get('id');
var todos = this.get('todos').filter(function (todo) {
return todo.get('id') !== thisId;
});
var selectContent = todos.map( function(todo){
var selectContent = {
title: todo.get('title'),
id: todo.get('id')
};
return selectContent;
});
return selectContent;
}.property('todos.#each.id'),
Here's an updated jsbin of your code: http://jsbin.com/UDoPajA/216/edit

How to find ember store

I am trying to retrieve record from model using store in Ember. I am doing like this
var rec = this.store.find(App.Recipient);
console.log(rec);
When I am outputting this I am getting this result
Class {toString: function, __ember1397122062151_meta: Object, __ember1397122062151: "ember497", _super: undefined, constructor: function…}
My purpose is to get all records so that I can send them to the server. Also any views on how to iterate over them in the controller?
store.find return a promise so the way to get the records is this:
this.store.find('recipient').then(function(recipients){
recipients.forEach(function(recipient) {
var zip = recipient.get('zip');
})
});

Saving nested models

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');
});

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.