ArrayProxy expects an Array or Ember.ArrayProxy, but you passed object - ember.js

I am getting this error:
ArrayProxy expects an Array or Ember.ArrayProxy, but you passed object
I am getting my data from a rails application using active-model-serializers. The data is showing in mhy ember inspector but my template is not rendering properly with this error in the console.
Router.map(function() {
this.resource('brands', function() {
this.resource('brand', { path: '/:brand_id' });
});
this.resource('campaigns', function() {
this.resource('campaign', { path: '/:campaign_id' },
this.resource('index'), { path: 'brands/:brand_id' });
});
});
export default Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
brand: this.store.find('brand'),
campaign: this.store.find('campaign')
});
}
});
export default DS.Model.extend({
name: DS.attr('string'),
facebook_page_id: DS.attr('string'),
active: DS.attr('boolean'),
facebook_uid: DS.attr('string'),
facebook_token: DS.attr('string'),
facebook_token_expires: DS.attr('string'),
website_url: DS.attr('string'),
privacy_policy_link: DS.attr('string'),
terms_link: DS.attr('string'),
instagram_account: DS.attr('string'),
instagram_url: DS.attr('string'),
twitter_account: DS.attr('string'),
twitter_url: DS.attr('string'),
avatar_url: DS.attr('string'),
youtube_account: DS.attr('string'),
youtube_url: DS.attr('string'),
favicon_url: DS.attr('string'),
open_graph_url: DS.attr('string'),
campaigns: DS.hasMany('campaign', {async: true})
});
export default DS.Model.extend({
name: DS.attr('string'),
brand_id: DS.attr('string'),
brand: DS.belongsTo('brand', {async: true})
});
{{#each brand in controller}}
<a>
{{#link-to 'brand' this}}
{{brand.name}}
{{/link-to}}
</a>
{{else}}
<a>No brands found.</a>
{{/each}}
No errors in the server logs.

Embers default index controllers are ArrayControllers that expect their model to be an array.
In the model hook of - what appears to be - your BrandsIndexRoute you specified the model as
Ember.RSVP.hash({
brand: this.store.find('brand'),
campaign: this.store.find('campaign')
});
Which returns a single object, not an array of brands as is expected.
What you should do instead is something like:
//brands route
export default Ember.Route.extend({
model: function() {
return this.store.find('brand');
}
});
//campaigns route
export default Ember.Route.extend({
model: function() {
return this.store.find('campaign');
}
});

You're trying to iterate over the controller, but your controller is decorating a object with two properties on it, not an array. Your object looks like this:
{
brand: [...],
campaign: [...]
}
Additionally if you have a controller defined as an array controller it will throw this error (which is what I'm guessing is really happening). Because you're passing an object to your controller, instead of an array.

Related

Error for one hasMany but not another. Makes no sense

I have a model "Group" that has some hasMany relationships "members" and "links". I have a controller that adds a new group but i get the following error on save
"Assertion Failed: A App.Group record was pushed into the store with the value of links being '{}', but links is a hasMany relationship so the value must be an array. You should probably check your data payload or serializer."
I can fix this by returning an empty links array in the response json but i don't necessarily want this included and i don't understand why i get this error for links but not for members as that isn't included in the response either.
App.AddGroupController = Ember.ObjectController.extend({
needs: ['application'],
actions: {
submitForm: function(){
var self = this;
var model = this.get('model');
self.send('showLoading', 'Saving');
model.save().then(function(){
self.send('hideLoading');
//Go to the groups list
self.transitionToRoute('groups');
},function(error){
self.send('hideLoading');
console.log(error);
//Must supply reject callback, otherwise Ember will throw a 'backend rejected the commit' error.
});
}
}
});
App.AddGroupRoute = App.AuthenticatedRoute.extend({
model: function(){
return this.store.createRecord('group');
}
});
App.Group = DS.Model.extend({
title: DS.attr('string'),
description: DS.attr('string'),
members: DS.hasMany('groupMember'),
links: DS.hasMany('groupLink'),
});
App.GroupLink = DS.Model.extend({
title: DS.attr('string'),
link: DS.attr('string'),
});
App.GroupMember = DS.Model.extend({
first_name: DS.attr('string'),
last_name: DS.attr('string'),
email: DS.attr('string'),
profile_picture: DS.attr('string'),
});
Response from save:
{
"group":{
"id":26,
"title":"Test Group",
"description":"Dummy group description",
}
}
Im using:
Ember : 1.9.1
Ember Data : 1.13.14

ember model rendering strange behaviuor

While experimenting with ember and ember-localforage-adapter I noticed a strange behaviour.
ember.debug.js:4888DEBUG: Ember : 1.12.0
ember.debug.js:4888DEBUG: Ember Data : 1.0.0-beta.18
ember.debug.js:4888DEBUG: jQuery : 1.11.3
I have three models:
/app/models/ledger.js
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
purchases: DS.hasMany('purchase', {async: true}),
players: DS.hasMany('player', {async: true}),
});
/app/models/purchase.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
amount: DS.attr('number'),
ledger: DS.belongsTo('ledger', {async: true}),
player: DS.belongsTo('player', {async: true})
});
/app/models/player.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
balance: DS.attr('number'),
ledger: DS.belongsTo('ledger',{ async: true }),
purchases: DS.hasMany('purchase', {async: true}),
});
and a simple route:
/app/routes/purchase.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
return this.store.find('purchase', params.purchase_id);
}
});
My experimental template is
/app/templates/purchase.hbs
name: {{model.name}}
<br>
amount: {{model.amount}}
<br>
player: {{model.player.name}}
and it works showing me all invoked attributes. But if I add another line like
name: {{model.name}}
<br>
amount: {{model.amount}}
<br>
player: {{model.player.name}}
<br>
ledger: {{model.player.ledger.title}}
it shows the name of the player for just an instant and never shows the ledger title. How should load in the store the requested record to have them available in my template?
After reading this I added the property payer to the purchase model:
payer: function(){
return DS.PromiseObject.create({
promise: this.get( 'player' )
});
}.property('')
to be used in the template instead of player:
<h3>show purchase</h3>
model.name: <strong>{{model.name}}</strong>
<br>
model.amount: <strong>{{model.amount}}</strong>
<br>
<br>
model.payer.name: <strong>{{model.payer.name}}</strong>
<br>
model.payer.ledger.title: <strong>{{model.payer.ledger.title}}</strong>
<br>
<br>
model.player.name: <strong>{{model.player.name}}</strong>
<br>
model.player.ledger.title: <strong>{{model.player.ledger.title}}</strong>
This approach is very similar to the one suggested by Artych but, maybe because of my Rails background, I prefer to access an associated record from anywhere by the model and not by the controller associated to the route
The following gif (download it if don't see the animation) show the behaviour of both:
Can someone explain what happen? Why model.player.name disappear at the time when model.plyer.ledger.title appear? What's the difference from the template point of view between payer and player considering that both return a DS.PromiseObject?
You could try to resolve model.player.ledger in afterModel hook:
afterModel: function(model) {
var controller = this.controllerFor('purchase');
model.get('player').then(function(player) {
controller.set('player', player);
return player.get('ledger');
}).then(function(ledger){
controller.set('playerLedger', ledger);
}, function(error) {
console.log("promise is not resolved", error);
});
}
In template
{{player.name}} | {{playerLedger.title}}
I think you could see why model.player.ledger is not resolved or it could be empty.
You have typo in player model. Should be:
//app/models/player.js
import DS from 'ember-data';
export default DS.Model.extend({
// ...
ledger: DS.belongsTo('ledger', { async: true }),
//not ledger: DS.belongsTo({ async: true }),
});

Value is showing up as [object Object] ember.js

I'm trying to set up the attributes to my model but having trouble when nesting objects. Here is what I have
App.Posts = DS.Model.extend({
title: DS.attr('string'),
author: {
name: DS.attr('string')
},
date: DS.attr('date'),
excerpt: DS.attr('string'),
body: DS.attr('string')
});
How am I suppose to declare the author object?
In the ember inspector when I go under data and under App.post and select one of the rows. It has a property a property App.Post with an attribute author: { name: [Object] }
here is the JS Bin link http://jsbin.com/tesozepexaqi/2/
Ember works perfectly fine without Ember Data. Let's pretend we want to do it with Ember Data:
Ember Data's Records should be flat. This means all properties are at the top level. If you have a related data that exist deeper they generally live in a different record. If you're attempting to embed the record you'll need to look into the tricky world of embedded records (Ember-data embedded records current state?). At the very least these related records must have an id defined. So here's an example of what the data returned from server should look like.
{
posts:[
{
id: '1',
title: 'My name is Django.',
author: {
id:1,
name: 'D name'
},
date: new Date('08-15-2014'),
excerpt: 'The D is silent.',
body: 'The D is silent.'
},
{
id: '2',
title: 'White horse',
author: {
id:2,
name: 'horse name'
},
date: new Date('08-15-2014'),
excerpt: 'Is what I ride.',
body: 'My horse likes to dance.'
}
]
}
Code
App.ApplicationAdapter = DS.RESTAdapter.extend();
App.PostSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
author: {embedded: 'always'}
}
});
App.Post = DS.Model.extend({
title: DS.attr('string'),
author: DS.belongsTo('author'),
date: DS.attr('date'),
excerpt: DS.attr('string'),
body: DS.attr('string')
});
App.Author = DS.Model.extend({
name: DS.attr()
});
Example: http://jsbin.com/vazada/1/edit
One other small tip, you'll not want to use globals when working with the routes, you can use modelFor to get the model from a different route.
App.PostsRoute = Ember.Route.extend({
model: function() {
return this.store.find('post');
}
});
App.PostRoute = Ember.Route.extend({
model: function(params) {
var posts = this.modelFor('posts');
return posts.findBy('id', params.post_id);
}
});
Personally, I think Ember Data is overkill. Ember works perfectly well with POJOs. If you need caching and the ability to rollback then Ember Data might be a good solution for you.
Example: http://jsbin.com/vazada/2/edit
Adjusting this example from the Ember docs like the below should work:
App.Post = DS.Model.extend({
title: DS.attr('string'),
author: DS.belongsTo('author'),
date: DS.attr('date'),
excerpt: DS.attr('string'),
body: DS.attr('string')
});
App.Author = DS.Model.extend({
post: DS.belongsTo('post')
})

Failed to get child records without embedded property (JSBIN inside)

I would get the records of my field children. Here the code:
App.User = DS.Model.extend({
name: DS.attr('string'),
online: DS.attr('boolean')
});
App.List = DS.Model.extend({
name: DS.attr('string'),
children: DS.hasMany('App.User'),
online: function() {
var users = this.get("children");
return users.reduce(0, function(previousValue, user){ // no record founds
return previousValue + user.get("online");
});
}.property("children.#each.online")
});
But App.List.find(1).get('online') returns no record. (For some reason I cannot specify that App.List.children contains many records, of type App.Users, as embedded records).
Here is the fiddle: JSBIN and it's output
How I can solve my issue?
Define the embedded Model on your Adapter map:
App.List = DS.Model.extend({
name: DS.attr('string'),
users: DS.hasMany('App.User'), //use "users" as the property name to mantain ember's naming conventions
...
});
App.Adapter = DS.RESTAdapter.extend();
App.Adapter.map('App.List', {
users: {embedded: 'always'} //you can use `always` or `load` which is lazy loading.
});
App.Store = DS.Store.extend({
revision: 12,
adapter: App.Adapter.create()
});
Hope it helps

How to achieve delegation or composition using Ember-data

Is there a way to delegate all calls to App.Item to App.Book, so that i can do App.Book.name and it returns the value of App.Item.name and thesame for App.Book.position. Simply put a way to delegate all the attributes of App.Item to App.Book, imagine a scenario where App.Item has 10 - 12 attributes.
App.Item = DS.Model.extend({
name: DS.attr('string', { defaultValue: "foo" })
position: DS.attr('string')
});
App.Book = DS.Model.extend({
category: DS.attr('string')
});