ember / ember-data belongsTo and hasMany not updating as expected - ember.js

I have two models:
App.Focusarea = DS.Model.extend
definition: DS.attr('string')
theme: DS.belongsTo('theme',
async: true
)
App.Theme = DS.Model.extend
definition: DS.attr('string')
focusareas: DS.hasMany('focusarea',
async: true
)
when creating one of each, I would like to associate the focus area to that theme
theme = store.createRecord("theme",
id: 3
definition: 'theme definition'
)
focusarea = store.createRecord("focusarea",
id: 4
definition: 'focusarea definition'
)
theme.get("focusareas").then (focusareas) ->
focusareas.pushObject focusarea
theme.save()
but when I run my tests
theme.get('focusareas').then (focusareas)->
expect(focusareas.toArray.length).to.equal(1)
it fails - the focusareas.toArray.length equals 0, in other words, the association failed.
What am I doing wrong or what am I missing? Any help will be greatly appreciated.
Update:
Figured it out, theme.get('focusareas') returns an unresolved promise, which is 0, that when resolved will return 1 eg:
focusareas = theme.get('focusareas')
focusareas.then ->
console.log focusareas.get('length') #=1
or
store.find('theme', 4).then (theme)->
theme.get('focusareas').then ->
expect(theme.get('focusareas').get('length')).to.equal(1)
in other words
store.find('theme', 4).then (theme)->
theme.get('focusareas').then ->
theme.get('focusareas').forEach (item) ->
console.log(item.get('definition')) #'focusarea definition'
item.get('theme').then ->
console.log(item.get('theme').get('definition')) #'theme definition'
I guess I should just RTFM!

During object init you need to assign focusareas to some sort of collection that the getters and setters can work with . An ArrayProxy would work nicely in this case. How about even one you can sort items automagically with?
theme = store.createRecord("theme",
id: 3
definition: 'theme definition',
focusareas: Ember.ArrayProxy.createWithMixins Ember.SortableMixin,
content: [],
sortProperties: ['id'], // Set this to any object property
sortAscending: true
)
Hope that helped!

Related

EmberJS Formatting hasMany relation

I have the following model setup:
deliveryMethods: DS.hasMany("delivery-method", { async: true })
With this computed property:
### COMPUTED PROPERTIES ###
formattedDeliveryOptions: (->
#get('deliveryMethods').map((dm) ->
console.log dm.toJSON()
return { key: dm.get('name').underscore, value: dm.get('name') }
)
).property("deliveryMethods.#each")
And I am trying to access this property in a controller like so:
deliveryMethodsArray: (->
org = #get('controllers.application.currentOrganization')
console.log org.get('formattedDeliveryOptions')
return org.get('formattedDeliveryOptions')
).property()
But when the console.log dm.toJSON() runs, the organization property is set but nothing else. Not sure what I am doing wrong
console.log output:
Because the async is true, i believe you need to do a then() on the get promise.
org.get('formattedDeliveryOptions').then((fmtOpts) => { /* do something*/ });
Please excuse my syntax, you use a slightly different shorthand version than i do. The concept should be the same though.

Confusing behavior from computed properties with async Ember models

I ran into some strange behavior trying to build computed properties into my Ember Data model. Here's what my .coffee file looks like:
LineItemModel = DS.Model.extend
quantity: attr 'number'
length: attr 'number'
product: DS.belongsTo 'product', async: true
priceAdjust: attr 'number', defaultValue: 0
weight: (->
return this.get('product.weight') * length
).property('product')
# Get MLF price
price: (->
# Adjust if percentage is specified
adjust = (mlf, adjustment) ->
return mlf unless adjustment
mlf += Math.ceil(mlf * adjustment / 100)
prices = this.get('product').get 'prices'
level = this.get('quote').get 'level'
price = prices.filterBy 'level', level
return if price[0] then adjust( price[0], this.get('priceAdjust') ) else 0
).property('product', 'product.#each.prices', 'quote.level', 'priceAdjust')
My problem is that in my weight method this.get('product.weight') always returns undefined. I've done some testing and anything other than this.get('product.id') returns undefined. To make thing even more confusing, this works
Ember.computed.alias('product.weight')
and this from with the price method
# Get MLF price
price: (->
console.log this.get('product.weight')
...
This is the product model for reference:
ProductModel = DS.Model.extend
...
weight: attr 'number'
prices: DS.hasMany 'price', async: true
I'm not sure if I did something wrong or if this is a bug. This is a cli app v0.1.15
So as far as I can tell, the problem is related to promises as whether or not they're resolved. Here's what I came up with so far:
weight: Ember.computed.alias 'product.weight'
Computed property easily takes care of handling the promise relationship business
# Lbs / Ft
lbs: (->
return this.get('weight') * this.get('length')
).property('weight', 'length')
Now I know I have some value for weight and I can do my calculation with it. I'm still not sure why I didn't seem to need this in the price method but this seems like the "Ember" way to do things. I'd love some feedback if this can be improved.

How to create a multi-use partial "template" in AngularJS?

I have a large list of items. Each item has it's own details.
In my main view/partial, I simply display a large list list of the item names.
When the user clicks on an item, I want the page to go to a partial which works as a "template", displaying information based on which list item is clicked, and hence possibly what the URL looks like. E.g. /listItem1/
This diagram below hopefully sums up what I want to achieve pretty clearly.
How can I do this?
Right now, I have a pretty standard set up in which I have all the information for each list item in an array of object literals, which is contained in a controller injected into the main app module. Like so:
var app = angular.module('app', [/*nodependencies*/]);
var controllers = {};
app.controller(controllers);
controllers.listController = function ($scope){
$scope.list = [
{name: 'List Item 1 Name', detail1: 'blahblah1', detail2: 'blahblah2'},
{name: 'List Item 2 Name', detail1: 'blahblah1', detail2: 'blahblah2'},
{name: 'List Item 3 Name', detail1: 'blahblah1', detail2: 'blahblah2'}
..... and so on
I know how to create basic views/partials as well. But what would be my next steps?
You can do what you want, using the built-in router which ships with AngularJS.
var app = angular.module('app', [/*nodependencies*/])
.config(function($routeProvider) {
$routeProvider
.when('/:itemId', {
templateUrl: '/path/to/partial',
controller : function($scope, $routeParams) {
$scope.item = $routeParams.itemId;
}
})
});
Basically, what the above means, is that if you browse to pdf/item/1
Then you will have access in your controller to $routeParams.itemId which will be equal to 1. You can then do whatever logic is necessary with this information on your partial to show the information you want.
Hope this helps.
Update
Please look at the controller, this is how you would get the param you passed via the URL, you would then do whatever it is you need to do with that param in the controller, and pass the data back to the view.
You can create a small directive that will use the multi-use partial to display each item on the list
Take a look at this working example (http://plnkr.co/edit/0jNVxRg6g3p8uxpustzz?p=preview)
var myApp = angular.module('myApp', []);
myApp.controller('listController', ['$scope', function ($scope) {
$scope.list = [
{
name: 'List Item 1 Name',
url: 'pdfs/item1.pdf',
detail: 'blahblah'
},
{
name: 'List Item 2 Name',
url: 'pdfs/item2.pdf',
detail: 'blahblah'
},
{
name: 'List Item 3 Name',
url: 'pdfs/item3.pdf',
detail: 'blahblah'
}
];
$scope.selectItem = function(item){
$scope.selected = item;
}
}]);
myApp.directive('listItem', [function () {
return {
restrict: 'A',
scope: {
item: '='
},
templateUrl: 'multiple-partial.html',
link: function (scope, element, iAttrs) {
}
};
}])

Ember.js / Konacha / Mocha.js test in promise

I've got some static data that I'm initializing into the ember app like this
App.initializer
name:"Foo Data"
initialize: ->
store = DS.get("defaultStore")
obj =
[
id: 1
name: "whatever"
,
id: 2
name: "whenever"
]
type = App.Foo
store.loadMany(type,obj)
The following test seems to pass regardless of what 'whatever' is
it 'should be preloaded into store', ->
Ember.run ->
App.Foo.find(1).then((foo)->
foo.get('name').should.equal('whatever')
)
Is this a bug? What's the correct way to do this?
I guess I forget to check the obvious...
This
it 'should be preload into store', ->
Ember.run => App.Foo.find(1).get('name').should.equal('whatever')
Works fine

Testing Ember-data w/Jasmine and DS.FixtureAdapter

I'm trying to do a Jasmine test of ember-data (using the current master) using the DS.FixtureAdapter. I've tried dozens of variations on the below code (with and without trying to create an Application namespace). I've also gone into the ember-data source to try and see what's going on, as well as referenced the tests in ember-data itself as an example.
I've also tried variations of Person.find(1), using Ember.run blocks and Jasmine wait()'s.
Whatever I try, store.find(Person, 'test') returns a result but attempting to get one of the attributes results in null (test assertion fails). What is it I'm not seeing? Thanks for any help!
describe "a test", ->
store = null
Person = null
beforeEach ->
store = DS.Store.create
revision: 11
adapter: 'DS.FixtureAdapter'
Person = DS.Model.extend
firstName: DS.attr('string')
lastName: DS.attr('string')
age: DS.attr('number')
it "works or does it", ->
Person.FIXTURES = [{
id: 'test'
firstName: 'Kyle'
lastName: 'Stevens'
age: 30
}]
kyle = store.find(Person, 'test')
expect(Em.get(kyle, 'firstName')).toEqual('Kyle')
Whatever I try, store.find(Person, 'test') returns a result but attempting to get one of the attributes results in null (test assertion fails). What is it I'm not seeing? Thanks for any help!
This is a timing issue. When you call store.find() it runs query asynchronously and returns a model promise. That means the query is still running (or scheduled to run) when control returns to your test, resulting in a failed expectation.
This is what we love about ember, it means your app can treat kyle as if the data were present and trust that values will be updated automagically via bindings when the data becomes available.
Of course all this magic is not so great when it is preventing your test from passing. Here are some alternative approaches:
1) Register a didLoad callback
kyle = store.find(Person, 'test');
kyle.on('didLoad', function() {
console.log('should = kyle: ', Em.get(kyle, 'firstName'));
});
2) Instead of didLoad could use more blackbox testing approach and just verify that the name is set propertly within 100 ms of having called find - of course this can lead to brittle tests
Ember.run.later(this, function() {
console.log('should = kyle: ', Em.get(kyle, 'firstName'));
console.log('should = kim: ', Em.get(App.kim, 'firstName'));
}, 100);
I believe that in a jasmine test you could wrap your setup code in a runs() method and use waitsFor to verify that the value has been set as expected:
waitsFor(function() {
return Em.get(kyle, 'firstName') == 'Kyle';
}, "x to be set to 5", 100);
See this JSBIN for working (non-jasmine) example:
http://jsbin.com/apurac/4/edit
See this post for tips on async testing with jasmine: http://blog.caplin.com/2012/01/17/testing-asynchronous-javascript-with-jasmine/
Also, be sure to set Ember.testing = true for all of your tests. See this SO post for detail: Is it recommended to set Ember.testing = true for unit tests?