ObjectAt(0) doesn't work with Ember.Controller - ember.js

I'm trying to upgrade my Ember application to the latest Ember 1.13.2 and therefore am trying to replace all Ember.ArrayController with Ember.Controller.
I've got a problem with the category-controller of my blog. The data retrieved from my server looks like this:
"posts": [
{
"category": {
"id": "1",
"name": "tech"}
"title": "my title",
"body": "post-content",
"isPublished": true,
},
...
]
In my category-controller I need both, the category-id and the category-name.
With the Ember.ArrayController I used objectAt(0) to get both from the first post loaded in the model for that purpose but this doesn't work with Ember.Controller anymore.
How can I get these two attributes in my controller?
This code doesn't work anymore:
breadCrumb: function(){
return this.objectAt(0).get('category').get('name');
}.property('model.[]'),
categoryId: function(){
return this.objectAt(0).get('category').get('id');
}.property('model.[]'),

You just get it from the model directly instead of using the proxy behavior:
return this.get('model').objectAt(0).get('category').get('name');
Or, if you like shorthand:
return this.get('model.firstObject.category.name');
EDIT: While I'm at it, you could also declare these as aliases:
breadCrumb: Ember.computed.alias('model.firstObject.category.name'),
If you don't do that, you should probably change your dependent key. Your property won't update if the nested properties change. You need to use something like model.firstObject.category.name or model.#each.category.name.

Related

Why does a `hasMany` relationship create an OPTIONS and GET request for each item in the relationship?

I currently have a response like so from my API:
{
"data": [{
"attributes": {
"customer_name": "The Name",
},
"id": 1,
"relationships": {
"tasks": {
"data": [{
"attributes": {
"name": "The task",
},
]
}
},
"type": "customer"
}
]
}
And Ember loads the model correctly. However, when I try and consume the computed property the relationship creates, Ember initiates an OPTIONS and subsequent GET request for each item in the relationship.
I haven't worked with these kinds of relationships before so this may be the expected behaviour but I'm a bit confused.
If it is going to do a request for each one, why bother sending the items as part of the original model via a relationship at all? Why not just do a normal model retrieval which would probably be much quicker.
It seems really wasteful so I suspect I'm not handling or understanding something correctly, I'd appreciate any insight.
This is expected behavior for what you are doing (accessing a hasMany relationship). In your example, the tasks do not exist in Ember Data's store so when trying to access them Ember Data is smart enough to fetch them for you from your API. In doing so, it sends out the OPTIONS and GET requests to retrieve them. The OPTIONS request is simply a preflight request.
If you wanted to work with this particular model and its tasks (or other hasMany relationship models) without making so many network requests, you could fetch both simultaneously by requesting them with an include assuming you're using an adapter that allows it (i.e., supports the JSON API spec) and your API does too:
// for example, fetching a post with its comments (hasMany relationship)
this.store.findRecord('post', params.post_id, { include: 'comments' } );
Doing so should return the model and its tasks in one network request. For further reading, I'd checkout Ember's guide on Relationships.

Ember-models-table addon throws ember warn error while trying to use 'routeName' property

Am using ember-models-table to display table in my applicaiton. The table is great for sorting , pagination etc, but am trying to route from specific row to different page based on the id. it has mentioned in its example to use 'routeName'
But when I use it throws the following error:
"Assertion Failed: When calling warn you must provide an options hash as the third parameter. options should include an id property."
My .js coding :
columns:[
{
"propertyName": "firstName",
"title":"First Name",
"routeName":"/#/profile/_id"
},
and so on
Thanks for your help.
update: the error is gone after updating ember to ember 3.1.2
but there is a warning and its not functioning properly as expected , where am i going wrong?
my code :
columns:[
{
"propertyName": "firstName",
"title":"First Name",
"routeName":"profile"
},
If you look at the example app for this addon, you'll see the following syntax for the route:
{
propertyName: 'id',
routeName: 'users.user'
},
This roughly corresponds to a route like users/1. So, if this is your router:
Router.map(function() {
this.route('users', function() {
this.route('user', { path: '/users/:user_id' });
});
});
And here are the columns:
columns: [
{
"propertyName": "something",
"routeName": "users.user"
},
{
"propertyName": "id",
"routeName": "users.user"
}
]
Here's the template:
{{models-table
data=model
columns=columns
...
}}
routeName should not include the id segment.
Hover over the anchor tag created to see where it leads to, and make adjustments until it matches where it should go. It will only render when there's a valid route for the link. The path seems like it might be relative, so if you're already on the users route, you may only need to specify user for the routeName.
I figured this out by searching the addon codebase for routeName and then trying it out with the same kind of format that {{link-to}} helper uses in Ember.
P.S. this is some missing info in the documentation, so if this addon is helping you out, consider making a PR to help others.

How to Model.fetch(<object>) when the returned data is a single object

I want to make an API call for searching that looks like this:
https://myapi.com/search/<query>/<token>
where query is the search term and token (optional) is an alphanumeric set of characters which identifies the position of my latest batch of results, which is used for infinite scrolling.
This call returns the following JSON response:
{
"meta": { ... },
"results" {
"token": "125fwegg3t32",
"content": [
{
"id": "125125122778",
"text": "Lorem ipsum...",
...
},
{
"id": "125125122778",
"text": "Dolor sit amet...",
...
},
...
]
}
}
content is an array of (embedded) items that I'm displaying as search results. My models look like this:
App.Content = Em.Model.extend({
id: Em.attr(),
text: Em.attr(),
...
});
App.Results = Em.Model.extend({
token: Em.attr(),
content: Em.hasMany('App.Content', {
key: 'content',
embedded: true
})
});
In order to make that API call, I figured I have to do something like this:
App.Results.reopenClass({
adapter: Em.RESTAdapter.create({
findQuery: function(klass, records, params) {
var self = this,
url = this.buildURL(klass) + '/' + params.query;
if (params.token) {
url += '/' + params.token;
}
return this.ajax(url).then(function(data) {
self.didFindQuery(klass, records, params, data);
return records;
});
}
}),
url: 'https://myapi.com/search',
});
then somewhere in my routes do this:
App.Results.fetch({query: 'query', token: '12kgkj398512j'}).then(function(data) {
// do something
return data;
})
but because the API returns a single object and Em.RESTAdapter.findQuery expects an array, an error occurs when Ember Model tries to materialize the data. So how do I do this properly? I'm using the latest build of Ember Model.
By the way, I'm aware that it would be much more convenient if the API was designed in a way so I can just call App.Content.fetch(<object>), which would return a similar JSON response, but I would then be able to set the collectionKey option to content and my data would be properly materialized.
You simply need to override your models load() method to adjust the payload hash to what Ember.Model wants. There are no serializers in Ember.Model. There is both a class level load for handling collections and an instance level load for loading the JSON specific to a single model. You want to override the instance level load method to wrap the content key value in an array if its not one already.
I have been using Ember.Mode quite heavily and enhanced it for a number of my use cases and submitted PR's for both fixes and enhancements. Those PRs have been sitting there for a while with no response from the maintainers. I have now moved to Ember.Data which has been 'rebooted' so to speak and having a lot better result with it now.
I would strongly suggest walking away from Ember.Model as it appears dead with the new pragmatic direction Ember Data has taken and because the project maintainer doesn't appear to have any interest in it anymore.

How to access complex nested json in ember.js

after thorough searching stackoverflow and reading through all of the documetation on emberjs.com I'm finding myself stuck. I have a complex json object that I'm trying to model and output in my ember project.
I don't have control over the JSON, otherwise I'd change it's format to be easier digested. That said, here is my problem.
I have the following json
[
{
"id":1,
"catId": "10051",
"catUrl": "path/to/location",
"childCount": "4",
"description": [{
"text": "Description Text"
}],
"identifier": "UNQ123456",
"partialResults": "false"
}
]
What I'm trying to get at is the text value in description. I've tried creating the hasMany and belongsTo nested model construct described on emberjs.com, as well as many other patterns that were described as answers here on stack overflow, yet none of them seem to work or match the data construct I have to work with.
I've even tried the anonymous function in the first block of code on this page. http://emberjs.com/guides/models/defining-models/ trying to traverse this to the text that I want.
Regardless, any help would be much appreciated.
You could define a custom data transform to handle your special JSON field. This can be done by using the DS.RESTAdapter.registerTransform function. Something like this should work for your use case:
DS.RESTAdapter.registerTransform('descriptionText', {
serialize: function(data) {
var text = data[0].text;
return text;
},
deserialize: function(text) {
var data = [Ember.create({text: text})];
return data;
}
});
And then use it as a custom attribute for your model:
App.MyModel = DS.Model.extend({
...
description: DS.attr('descriptionText')
});
Note that the name of the transform could be something else as descriptionText as long you use the same name for DS.attr(...).
Hope it helps.

Ember data sideloaded models ignored

I'm new to Ember, and am having a problem I'm not seeing duplicated anywhere else so I'm sure I'm doing something silly. I have these models:
App.User = DS.Model.extend({
username: DS.attr("string"),
userId: DS.attr("number"),
modules: DS.hasMany("App.Module")
});
App.Module = DS.Model.extend({
moduleId: DS.attr("number"),
name: DS.attr("string")
});
Note that my Module model is simply a container that a User can have a few of, and many users might share the same modules - they're actually represented by an Enum on the server. As such, the backend does not have a mapping of Module > Users, so I've left the DS.ownedBy or DS.hasMany out of App.Module. I have, however, tried my test with a "users: DS.hasMany('App.User')" in there as well, with the same result. If it turns out to be necessary, I could maintain such a mapping, but I have no other reason for doing so right now, so it would be a bit unfortunate to be forced to add such a table to my database.
I'm using Ember.Auth for authentication, and when my app loads up and I log in, the server requests (as expected):
users/nathan?authToken=<token>
The result is also as I think it should be, according to the ember data docs:
{
"user": {
"username": "nathan",
"user_id": 1,
"module_ids": [1,2]
},
"modules": [
{"module_id": 1, "name": "Account Settings"},
{"module_id": 2, "name": "Manage Websites"}
]
}
I'm then testing in the Chrome console with:
App.Auth.get("user").get("modules");
or
App.User.find("nathan").get("modules");
and in both cases, Ember makes a request to my server to get the data for Modules 1 and 2. If I repeat the same request, it doesn't go back to the server again, so it is storing the result properly that time, it's simply the sideload that it's ignoring.
I'm using ember-1.0.0-rc4 with ember-data-0.13.
In your sideload response, module_id should be id.
(or you can configure ember-data to use module_id, but formatting the server response should be the easier way?)
--- edit for explanation ---
I'm not sure the original REST call is "working perfectly". Without the id. ember-data does see the two modules that your originally sideloaded, but it sees no id, so it does not know that they are modules 1 and 2 respectively. By default (changeable), ember-data expects the id to be called id; your module_id (and user_id) are just treated as regular properties.
On your next API call (to /modules?ids[]=1&ids[]=2 presumably), ember-data silently assumes that, since your requested two modules, and two modules came back, they should be the two that you requested. Try sending back this
{
"modules": [
{"module_id": 3, "name": "Foo module"},
{"module_id": 4, "name": "Bar module"}
]
}
for the request /modules?ids[]=1&ids[]=2, you will still get your "successful" observation.
Try App.Module.find(1).get('name') with the module_id-response - you will get undefined; now try it with the id-response, and you will get Account settings as expected.
Have you configured your RestAdapter to sideload modules?
Like this:
DS.RESTAdapter.configure('App.Module', {
sideloadsAs: 'modules'
});