How do I set a dynamic model attribute in the fixture? - ember.js

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

Related

How Do I Construct my Ember Models and API Data to represent data on the relationship?

I'm new to jsonapi and how the structure works and trying to get a relationship to load properly. I'm expecting ember-data to follow provided url's in the relationship.links of my objects to fetch the required information but I'm getting unexpected results.
I have Users, Territories, and a User/Territory relationship defined like this:
// User Model
const UserModel = DS.Model.extend({
username: DS.attr('string'),
territories: DS.hasMany('user-territories')
}
// Territory Model
const TerritoryModel = DS.Model.extend({
name: DS.attr('string')
}
// User-Territory Model
const UserTerritoryModel = DS.Model.extend({
notes: DS.attr('string'),
startDate: DS.attr('date'),
user: DS.belongsTo('user'),
territory: DS.belongsTo('territory')
}
I then have mock data (using http-mock) that looks like this:
// api/users/
data: {
type: "users",
id: 1,
attributes: {
username: "thisIsMyUsername"
}
},
relationships: {
territories: {
links: {
self: "http://localhost:4200/api/users/1/territories"
}
}
}
// api/users/1/territories
data: {
type: "user-territories",
id: 1,
attributes: {
notes: "This is a note",
startDate: "2017-01-01"
}
},
relationships: {
user: {
link: {
self: "http://localhost:4200/api/users/1"
}
},
territory: {
link: {
self: "http://localhost:4200/api/territories/1"
}
}
}
// api/territories/1
data: {
type: "territories",
id: 1,
attributes: {
name: "Territory #1"
}
}
In my User route, I want to request the UserModel and have access to the UserTerritory Relationship data and the Territory itself. The api calls are not what I expect though:
this.get('store').findRecord('user', 1, { include: "territories" });
EXPECTED:
api/users/1
api/users/1/territories
ACTUAL:
api/users/1
api/users/1?include=territories
If I call the user-territories model I get this:
EXPECTED:
api/users/1/territories
ACTUAL:
api/user-territories/1
If you use included, ember-data basically thinks you want to tell the server to side-load data. If you return a links, just resolve the relationship. However the relationships have to be inside the data. Also the self link is for the relationship itself, to return the data use related.
So first you do something like user = store.findRecord('user', '1'), this will fetch to api/users/. Then you should return something like this:
// api/users/
{
data: {
type: "users",
id: 1,
attributes: {
username: "thisIsMyUsername"
}
relationships: {
territories: {
links: {
related: "http://localhost:4200/api/users/1/territories"
}
}
}
}
}
Next you do user.get('territories'). This will return a promise, and fetch http://localhost:4200/api/users/1/territories, or whatever was inside that related link. However know, what ember-data will expect you to return user-territories here, because thats what you specified with territories: DS.hasMany('user-territories'). You should know what you directly can model an many-to-many relationship in ember-data without a third table.

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

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

Explicit reverses in Ember-Data

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.

Trouble accessing data in relationships in Ember

I'm having trouble saving data in this model relationship. My models are as follows:
App.Flow = DS.Model.extend({
title: DS.attr('string'),
content: DS.attr('string'),
isCustom: DS.attr('boolean'),
params: DS.hasMany('parameter', {async: true})
});
App.Parameter = DS.Model.extend({
flow: DS.belongsTo('flow'),
param: DS.attr('string'),
param_tooltip: DS.attr('string'),
param_value: DS.attr('string')
});
As you can see, I want Flows to have multiple Parameters. I have a rudimentary setup using Flow and Parameter fixtures, which behave as expected in the templates. However, when I try to create new ones in the controller, I have trouble setting the flow and parameter values correctly.
var p = this.store.createRecord('parameter', {
param: "foo",
param_tooltip: "world",
param_value: "hello"
});
var f = this.store.createRecord('flow', {
title: 'job',
content: title,
isCustom: true,
params: [p] // doesn't seem to work
});
f.set('params', [p]); // doesn't seem to work
p.set('flow', f); // also doesn't seem to work
// Save the new model
p.save();
f.save();
I've tried a lot of solutions after staring at this and StackOverflow for a while (not just the ones listed). I'm not really sure what to try next. One thing that I noticed in the Ember inspector was that the ids of these created elements were not integers (they were something like the string 'fixture_0'), but I'm not really sure why that would be, whether its related, or how to fix it.
Thanks!
Edit
After dealing with promises ala kingpin's solution, the parameter seems like it is being set in the createFlow function I have when I check with print statements; however, I'm still having trouble accessing the parameters in both my template and in the removeFlow function.
createJob: function() {
// ...
f.get('params').then(function(params) {
params.pushObject(p);
});
f.get('params').then(function(params) {
console.log(params.toArray()[0].get('param')); // prints 'foo'
});
// ...
},
removeJob: function(flow) {
console.log(flow.get('title')); // prints 'job'
flow.get('params').then(function(params) {
var arr = params.toArray();
console.log(arr); // prints '[]'
for (var i=0; i < arr.length; i++) {
console.log('hi'); // never gets printed
arr[i].deleteRecord();
arr[i].save();
}
flow.deleteRecord();
flow.save();
});
},
params being async needs to be waited on before you can set a property or model on it (this is something that is being hardened out before 1.0 finally hits, but it's finicky right now in 1.0 beta). The fixture_ids are applied if you're using the fixture adapter.
var store = this.store;
var p = store.createRecord('parameter', {
param: "foo",
param_tooltip: "world",
param_value: "hello"
});
var f = this.store.createRecord('flow', {
title: 'job',
content: 'title',
isCustom: true,
});
f.get('params').then(function(params){
params.pushObject(p);
});
p.set('flow', f);
http://emberjs.jsbin.com/OxIDiVU/588/edit
Additionally saving using the fixture adapter seems to remove hasMany relationships when their is a manyToOne connection (aka both records have a relationship to each other, one of them being a hasMany). Getting rid of one of the relationships fixes the issue (or using one of the real adapters)
http://emberjs.jsbin.com/OxIDiVU/590/edit