Explicit reverses in Ember-Data - ember.js

My model looks like this:
App.Room = DS.Model.extend({
title : DS.attr('string'),
description: DS.attr('string'),
rooms : DS.hasMany('room', {
async : true,
inverse: 'parent'
}),
parent : DS.belongsTo('room')
});
Viewing existing records works, except creating new records.
I tried it like this:
var self = this,
parent = this.get('content'),
input = this.getProperties('title', 'description'),
newRoom = this.store.createRecord('room', {
title : input.title,
description: input.description,
parent : parent
});
parent.get('rooms').then(function (rooms) {
rooms.pushObject(newRoom);
newRoom.save();
parent.save();
self.transitionToRoute('rooms');
});
But get this error:
Assertion Failed: You defined the 'parent' relationship on (subclass of DS.Model),
but multiple possible inverse relationships of type (subclass of DS.Model) were
found on (subclass of DS.Model).

You're relating an object to itself, so it's likely that Ember-Data's inverse guessing algorithm is getting confused. Declare the inverse on both explicitly.
App.Room = DS.Model.extend({
rooms: DS.hasMany('room', {
async: true,
inverse: 'parent'
}),
parent: DS.belongsTo('room', {
inverse: 'rooms'
})
});
Because it's specifically mentioning the parent relationship, I'm assuming Ember-Data is thinking that it might be its own inverse.

Related

EmberJS: how to remove records with many-to-many relationship

I stumbled across an issue when I tried to remove a record whose model is in many-to-many relationship to another.
I have Book:
App.Book = DS.Model.extend({
title: DS.attr('string'),
isbn: DS.attr('string'),
category: DS.attr('string'),
publishDate: DS.attr('date'),
authors: DS.hasMany('author', {inverse: 'books'})
});
as well as Author:
App.Author = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
birthDate: DS.attr('date'),
books: DS.hasMany('book', {async: true, inverse: 'authors'})
});
I am removing books like this:
actions: {
delete: function (book) {
var authors = book.get('authors')
authors.forEach(function(author) {
var books = author.get('books')
books.forEach(function(book){
console.log(book.toJSON());
})
books.removeObject(book);
//books.save doesn't work
})
book.destroyRecord();
this.transitionToRoute('books.index');
},
and it correctly removes this book, with DELETE request to backing REST server, but there are no PUT requests for all those authors which had this book in their 'books' collection. When I change view to authors and go back to books one dummy book is created with id of the one I previously removed and 'authors' set to old author as well, other properties are undefined.
How do I correctly remove books so that authors are updated as well?
You have to remember that DS.hasMany returns a promise, so it has to be resolved before you can use it:
actions: {
delete: function (book) {
if (!book.get('isDeleted')) {
book.destroyRecord()
.catch(function(error) {
console.log(error);
book.rollback();
})
.then(function(book) {
book.get('authors').then(function (authors) {
authors.mapBy('books').forEach(function(books) {
if (typeof books.then === "function") {
books.then(function(books) {
books.removeObject(book);
});
} else {
books.removeObject(book);
}
});
});
});
}
this.transitionToRoute('books.index');
}
},
Looks like I've solved it.
First issue was that on the server-side of my project I incorrectly populated data - the ids were wrong and didn't match those from their relation.
Another thing was saving incorrect object. I was trying to save only the array with books, had to save whole author instead (actually that makes sense). Working action is this:
console.log('Removing book: ' + book.get('id') + book.get('title'));
var authors = book.get('authors')
var id = book.get('id');
authors.forEach(function (author) {
var books = author.get('books').removeObject(book);
author.save();
})
book.destroyRecord();
this.transitionToRoute('books.index');
My only doubt was that the delete action is defined in BookController, whose model is the book I am trying to delete, so I shouldn't put the book in a parameter but use controller's model instead. And I've just found that it's easily achievable by just removing the parameter and declaring book as this.get('model'), that's why the final, working solution is this:
delete: function () {
var book = this.get('model')
console.log('Removing book: ' + book.get('id') + book.get('title'));
var authors = book.get('authors')
var id = book.get('id');
authors.forEach(function (author) {
var books = author.get('books').removeObject(book);
author.save();
})
book.destroyRecord();
this.transitionToRoute('books.index');
}

How do I set a dynamic model attribute in the fixture?

As the title describes, I am running into trouble making a dynamic attribute on the Fixture layer.
Here is an example model:
App.Pokeball = DS.Model.extend({
name: DS.attr('string'),
ballRate: DS.attr('number'),
battleAttributes: DS.belongsTo('battleAttributes')
});
And my Fixture:
App.Pokeball.reopenClass({
FIXTURES : [
{
id: 1,
name: 'PokeBall',
ballRate: 1
},
{
id: 23,
name: 'Dusk Ball',
ballRate: function() {
// Some logic that applies only model #23
return 2;
}.property('battleAttributes')
}
]
});
I scoured online trying to find out the right way to do this, but have instead ran into a dead end. :(
This is an invalid use of fixtures. They’re meant to represent JSON (or whatever) on the server that is passed to your application and turned into Ember Data models. JSON cannot represent the concept of a computed property, it’s for pure data.
I don’t understand your use case so I could be way off, it seems like you should use a computed property on the model instead:
App.Pokeball = DS.Model.extend({
name: DS.attr('string'),
ballRate: DS.attr('number'),
battleAttributes: DS.belongsTo('battleAttributes'),
adjustedBallRate: function() {
if (this.get('battleAttributes.whateverPropertyCausesThisToChange') == 'special value') {
return 2;
}
else {
return this.get('ballRate');
}
}.property('battleAttributes.whateverPropertyCausesThisToChange')
});

EmberJS - Computed property referencing firstObject do not update

I have a model like this:
App.Conversation = DS.Model.extend({
body : DS.attr('string'),
created_at : DS.attr('date'),
entry : DS.hasMany('Entry', {async: true}),
user : DS.belongsTo('User'),
allEntriesLoaded : DS.attr('boolean'),
entryProxyBody : function() {
return this.get('entry.firstObject.body');
}.property('entry.firstObject.body')
});
As you can see it references its Entry hasMany relationsship in the function entryProxyBody. This reference works great, as calling entryProxyBody do indeed return the body-attribute from the first object in Entry.
However my problem is, that the computed property is not updated, when a new value is added to the Entry-store.
I add a new record like this:
App.NewController = Em.ObjectController.extend({
actions: {
save: function() {
var entry = this.store.createRecord('entry', {body: 'Test', created_at: new Date() });
this.store.find('conversation', this.parentController.get('id')).then(function(conversation) {
conversation.get('entry').pushObject(entry);
entry.save();
});
}
},
});
However.. If I update the first object in Entry directly using Ember Inspector in Chrome, then the computed property changes as it should.
What am I missing? Thank you for your help!
I think you might want to observe for changes for each entry in the entry array.
entryProxyBody:function() {
return this.get('entry.firstObject.body');
}.property('entry.#each.body')

Execute observer once model loaded

i am trying to have an observer execute when a model is loaded
sortAttachments:function(){
console.log('sorting')
var attachments = this.get('model').get('attachments');
for(var i = 0;i<attachments.length;i++){
var a = attachments[i];
if(a.type=="Link"){
links.push(a)
}
}
}.observes('models.attachments.#each.type'),
the method is currently being called twice, if i change the observes statement to
observes('blablabla'),
it also gets called twice.
the method must only execute when the attachments property of the model updates
the model code :
App.Card = DS.Model.extend({
title: DS.attr('string'),
description: DS.attr('string'),
left: DS.attr('number'),
top: DS.attr('number'),
user: DS.belongsTo('user', { async: true }),
attachments: DS.hasMany('attachment',{async:true}),
tags:DS.hasMany('tag',{async:true})
});
Additionally you can observe the state of the model using the current state
App.ApplicationController = Em.ObjectController.extend({
timesFired: 0,
watcher: function(){
if(this.get('model.currentState.stateName') == 'root.loaded.saved'){
this.incrementProperty('timesFired');
}
}.observes('model.currentState.stateName')
});
http://jsbin.com/aYIkAcUk/9/edit
I think that your observer is called multiple times because for each item loaded in attachments relationship, the model.attachments.#each.type is triggered. You can use Ember.run.once to collapse all these calls in a single one:
sortAttachments:function(){
console.log('sorting')
var attachments = this.get('model').get('attachments');
for(var i = 0;i<attachments.length;i++){
var a = attachments[i];
if(a.type=="Link"){
links.push(a)
}
}
},
sortAttachmentsOnce: function() {
Ember.run.once(this, this.sortAttachments);
}.observes('model.attachments.#each.type'),
I hope it helps

Acces parent record from an embedded object

Is there a way to access to the parent object of an embedded model object ? For example :
App.Person = DS.Model.extend({
name : DS.attr('string'),
emails : DS.hasMany('App.Email', { embedded: true })
});
App.Email = DS.Model.extend({
label : DS.attr('string'),
email : DS.attr('string'),
setParentUpdated: function() {
if(this.get('isDirty') == true)
// this.get('parent').get('stateManager').goToState('updated');
// I would like to do something like this.get('parent')
// to access 'App.Person' instance object
}.observes('isDirty')
});
Why not simply setup a belongsTo relation?
App.Email = DS.Model.extend({
person: DS.belongsTo('App.Person')
//...
});
Then you will be able to use the person property of the email.