Ember not requesting models by id after first request - ember.js

SO,
I am working on an Ember app and experiencing a confusing problem. At the index route the app performs a find() and returns an array of dataset and links to a template to show further details about each dataset which are sideloaded when a resquest is made to find by id. (i.e. find(1), where 1 is the id.)
The first request with an id works fine, returning the dataset object and it's sideloaded data, however subsequent requests do not seem to do anything. The server does not see any request if I try to navigate to any other dataset after the first one's details have been loaded. However if I navigate from a specific dataset back to index and then back to any dataset it will send the request again (twice even, am not sure if this a related problem) and work. In other words:
/# works
/#/1 also works (or any other id as long as it is the first one visited)
/#/1 then /#/2 does not work, no request is sent
/#/1 followed by /# then /#/2 does work, maintaining the data at /#/1 & getting the new data for /#/2.
How do I get all of the specific dataset objects to return upon visiting them, without the hacky pitstop at index? Any advice would be greatly appreciated, thank you in advance!
The code:
-app.js
/**************************
* Application
**************************/
var App = Em.Application.create();
App.Router.map(function() {
this.resource('application', {path:'/'}, function() {
this.resource('dataset', {path: '/:dataset_id'}, function() {
});
});
});
App.ApplicationRoute = Em.Route.extend({
model: function() {
return App.Dataset.find();
}
});
App.DatasetRoute = Em.Route.extend({
activate: function() {
this.modelFor('dataset').reload();
}
});
/**************************
* Models
**************************/
App.Store = DS.Store.extend({
adapter: DS.RESTAdapter.create({
url: 'http://***.***.***.***:5000',
namespace: 'api',
serializer: DS.RESTSerializer.extend({
primaryKey: function(type) {
return '_id';
}
})
})
});
App.Dataset = DS.Model.extend({
dataset: DS.attr('string'),
title: DS.attr('string'),
points: DS.hasMany('App.Point')
});
App.Point = DS.Model.extend({
dataset: DS.attr('string'),
dataset_id: DS.attr('string'),
date: DS.attr('date'),
value: DS.attr('string')
});

A route's activate hook is only called when the route is first transitioned to. It is not called again if the route's model changes. So when you transition into App.DatasetRoute either by entering the url directly or by clicking link on index page, the activate hook runs and your dataset is reloaded. When you switch from #/1 to #/2, the route remains active and no hook is called.
If I am understanding your question correctly, you want to reload the dataset whenever a user visits its url. In that case instead of the route's activate hook what you probably want to do is observe changes to the dataset controller's content. Something like this should work:
App.DatasetController = Ember.ObjectController.extend({
refreshOnChange: function() {
var dataset = this.get('content');
if (dataset) {
console.log('reloading dataset ', dataset.toString());
dataset.reload();
}
}.observes('content')
}

Related

Ember Data won't fetch from API

Even though i have set the RESPAdapter to take care of everything, it doesn't reach out to my server to get the data. My code is:
var App = Ember.Application.create({
LOG_TRANSITIONS: true
});
App.ApplicationAdapter = DS.RESTAdapter.extend({
host: '../api/'
});
App.Me = DS.Model.extend({
email: DS.attr('string'),
firstname: DS.attr('string'),
lastname: DS.attr('string')
});
App.WelcomemessageController = Ember.Controller.extend({
firstname:"I get rendered as i should",
model: function() {
return this.store.find('me');
}
});
And yeah, the property "firstname" gets rendered out just fine. And when inspecting with Chrome Devtools, no requests are being made.
In your case you just want to use a computed property, and not the model function. you could call it model, but it'd be slightly confusing since generally a controller decorates a model, and in this case it'd just be a property called model on the controller (in order to decorate a model, the controller needs to be an ObjectController or ArrayController)
App.WelcomemessageController = Ember.Controller.extend({
firstname:"I get rendered as i should",
user: function() {
return this.store.find('me');
}.property()
});

Emberjs display data after promise gets fulfilled?

I am a bit conceptually confused about how this works.
The user enters data on the form, presses next. The controller 's nextStep action fires and I ask the model to get data from the server. When the data arrives, I transition to the results route. At that point, I have the route store the previous model on a variable on the controller. Then I want to iterate through the data on the template.
The problem is that nothing shows up.
This is what I have (in the appropriate files):
//Step 1 Controller (partial - nextStep action)
var businessmatch = this.store.find('businessmatch', {businessname: businessname, phonenumber: phonenumber, zipcode: zipcode})
.then(function (result) {
// The model has data at this point
controller.transitionToRoute('step2');
});
// step2 Router
import Ember from 'ember';
export default Ember.Route.extend({
setupController: function(controller, model) {
controller.set('businessmatches', this.store.find('businessmatch'));
}
});
// Step2 template (partial)
{{#each businessmatches}}
{{businessname}}
{{/each}}
// Model
import DS from 'ember-data';
var Businessmatch = DS.Model.extend({
businessname: DS.attr('string'),
phonenumber: DS.attr('string'),
address: DS.attr('string'),
pageurl: DS.attr('string'),
thubmurl: DS.attr('string')
});
export default Businessmatch;
// Sample response from server
{
"businessmatches":[
{
"businessname":"El Farolito",
"pageurl":"/biz/el-farolito-san-francisco-2",
"thumburl":"/bphoto/ohpxQWg-hB9Sb27HkVg-yQ/90s.jpg",
"address":"780 El Camino RealMillbrae, CA 94030",
"phonenumber":"(650) 583-0487",
"id":1
},
{
"businessname":"El Farolito",
"pageurl":"/biz/el-farolito-san-francisco-4",
"thumburl":"/photo/AW76YTovuu9YsO69_BcLKQ/30s.jpg",
"address":"2779 Mission StSan Francisco, CA 94110",
"phonenumber":"(415) 824-7877",
"id":2
},
{
"businessname":"El Farolito",
"pageurl":"/biz/el-farolito-san-francisco",
"thumburl":"/bphoto/LgTOTIicRY6XArigmPhBpw/90s.jpg",
"address":"2950 24th StSan Francisco, CA 94110",
"phonenumber":"(415) 641-0758",
"id":3
}
]
}
#tstirrat is correct, the store.all() method immediately returns an array of cached records, while the store.find() method returns a promise of fetched records.
Is there a specific reason you aren't using the model hooks in your route? They are promise aware, and will wait for your store.find() to resolve then pass the model on to your setupController. You can then reference the previous models with modelFor('step1') or just use store.all() again.
Found it, if I change
controller.set('businessmatches', this.store.find('businessmatch'));
to
controller.set('businessmatches', this.store.all('businessmatch'));
it works as expected.
Would be interested to know why though, if anyone can explain.

Duplicate null-id records in ember-data

I'm using ember 1.0 and ember-data 1.0.0 beta 1. I have the following routes and controller to create and save simple notes ('AuthenticatedRoute' is just a custom made route for logged-in users):
App.Note = DS.Model.extend({
title: DS.attr(),
author: DS.attr(),
body: DS.attr(),
createdAt: DS.attr()
});
App.NotesRoute = App.AuthenticatedRoute.extend({
model: function() { return this.store.find('note'); },
});
App.NotesNewRoute = App.AuthenticatedRoute.extend({
model: function() {
return this.store.createRecord('note');
}
});
App.NotesNewController = Ember.ObjectController.extend({
actions: {
save: function() {
var self = this, model = this.get('model');
model.set('author', localStorage.username);
model.set('createdAt', new Date());
model.save().then(function() {
self.get('target.router').transitionTo('notes.index');
});
}
}
});
When I save a new note everything works as expected. But when I navigate away from the notes route and then back into it, the notes list is populated with a duplicate entry. One entry has an id and can be edited, deleted etc, the other has all the data of the first entry except the id attribute is null. It seems to me ember-data keeps the newly created record (that hasn't been committed to the database and thus has no id yet) alive even when the record becomes committed but I am uncertain as to why. When I reload the page, the list is correctly displayed, no duplicates appear. What am I doing wrong?
For the record, I am using mongodb so I use a custom serializer to convert '_id' attributes to ember-data friendly 'id's, essentially copied from here:
App.NoteSerializer = DS.RESTSerializer.extend({
normalize: function(type, hash, property) {
// normalize the '_id'
var json = { id: hash._id };
delete hash._id;
// normalize the underscored properties
for (var prop in hash) {
json[prop.camelize()] = hash[prop];
}
// delegate to any type-specific normalizations
return this._super(type, json, property);
}
});
I should also mention that this problem existed in ember-data 0.13 as well.
It was a stupid mistake in my RESTful server. I was responding to POST requests with a 204 (empty) response instead of what ember-data expected, that is a 201 ("created") response with the newly created record as the payload. This post made me realize it.
It would be nice though to include this information in the official REST adapter documentation.
That is certainly strange behaviour indeed. Unfortunately I'm not able to explain why you're experiencing this, however:
You can use the willTransition callback in the actions object in your Route to ensure that when it is transitioned away from, if NotesNewController's content property is dirty (i.e. has not been persisted yet), it will have its transaction rolled back.
App.NotesNewRoute = App.AuthenticatedRoute.extend({
model: function() {
return this.store.createRecord('note');
},
actions: {
willTransition: function (transition) {
var model = this.controllerFor('notesNew').get('content');
if (model.get('isDirty') === true) {
model.get('transaction').rollback();
}
return this._super(transition);
}
}
});

Serialize is reached before server response is complete

In my ember app, I have a router with nested resources, like so:
App.Router.map(function () {
this.resource('explore', function() {
this.resource('building', { path: 'building/:slug' });
this.resource('country', { path: ':slug' }, function() {
this.resource('state', {path: ':slug' });
});
});
});
App.CountryRoute = Ember.Route.extend(App.SlugRouter, {
setupController: function(controller, country) {
controller.set('title', 'country detail');
controller.set('model', country);
}
});
App.SlugRouter = Ember.Mixin.create({
serialize: function(model, params) {
var name, object;
object = {};
name = params[0];
object[name] = model.get('slug');
return object;
}
});
App.Building = DS.Model.extend({
country: DS.belongsTo('App.Country'),
name: DS.attr('string'),
slug: DS.attr('string')
});
App.Country = DS.Model.extend({
name: DS.attr('string'),
slug: DS.attr('string'),
buildings: DS.hasMany('App.Building'),
states: DS.hasMany('App.State')
});
Loading the explore route shows a list of buildings received from the server (a django-rest-framework app), each building has a relationship to a country with a belongsTo attribute.
In the explore.index route, I display the list of the buildings, with links to the country route for each building, using {{linkTo this.country}}. The href, however, is loaded as #/explore/undefined, instead of #/explore/<country-name>.
The part that is confusing me is that this only happens the first time that I load the list. If I go to another route, then come back to #/explore, the links render correctly.
In the debugger, putting a breakpoint in the serialize method, I see that the first time that I load the page, the model object is empty (_data.attributes is an empty object). Going to the network tab in the debugger, I see that the a request has been made to the server to get the country data, but the response has not been received yet:
The response is eventually received, since {{this.country.name}} renders correctly, but after it's too late.
Thanks in advance for any responses/tips.
I am using:
Ember: 1.0.0-rc.5,
Handlebars: 1.0.0-rc.4,
jQuery: 1.8.3,
ember-data: 0.13,
ember-data-django-rest-adapter: 0.13
Firstly you are mixing in App.SlugRouter before it's definition. You should be seeing an error like Assertion failed: Expected hash or Mixin instance, got [object Undefined] in the console.
After that you need to load the model for a country by the slug. I don't see this in your Route either. You need something like this in CountryRoute depending on your persistence library.
model: function(params) {
return App.Country.find({slug: params.slug});
}
I suspect the part that is working right now is because your index route is loading the model and passing it in to setupController with the linkTo. Direct loading of the nested page requires configuring that route's model hook.

Ember Data sideloaded properties being dropped on a model

I'm working with
Ember RC3
Ember Data Revision 12
Handlebars RC3
I have Ember Data sideloading relationships on many of my models, so that I can template the sideloaded relationships like so:
// Models
App.Client = DS.Model.extend({
company: DS.attr('string'),
accountNumber: DS.attr('string'),
startDate: DS.attr('mysqlDate'),
// Relationships
campaigns: DS.hasMany('App.Campaign'),
users: DS.hasMany('App.User'),
phones: DS.hasMany('App.Phone'),
addresses: DS.hasMany('App.Address')
});
App.User = DS.Model.extend({
email: DS.attr('string'),
password: DS.attr('string'),
// Relationships
userType: DS.belongsTo('App.UserType'),
role: DS.belongsTo('App.Role'),
clients: DS.hasMany('App.Client'),
phones: DS.hasMany('App.Phone'),
addresses: DS.hasMany('App.Address')
});
<!-- template -->
<script type="text/x-handlebars" data-template-name="user/index">
<h2>{{email}}</h2>
<h5>Clients</h5>
<ul>
{{#each client in model.clients}}
<li>{{client.company}}</li>
{{/each}}
</ul>
</script>
This works wonderfully...except for every 1 in 10 reloads or so. Every once in a while the sideloaded relationship (in this case the hasMany relationship model.clients) DOES NOT render to the template while all other model properties (not relationships) DO render to the template. What's weird is that it only does this every once in a while.
I'm not quite sure yet how I can set up a js fiddle for this problem, so I wanted to ask:
Where in the call stack could I set a break point to see what properties will actually get rendered?
I'm using {{debugger}} in the template in question, I'm just not sure where the best place would be to inspect the application state in the call stack.
So, my problem was two-fold. First Problem: Here's my router map and routes:
App.Router.map(function() {
this.resource('users', function() {
this.route('create');
this.resource('user', { path: ':user_id' }, function() {
this.route('edit');
this.route('delete');
});
});
});
App.UsersRoute = Ember.Route.extend({
model: function() {
return App.User.find();
}
});
// Default for this route
App.UserRoute = Ember.Route.extend({
model: function(params) {
return App.User.find(params.user_id);
}
});
Therefore, when navigating to the route 'clients/3' the DS.JSONSerializer would do an extract() for the UserRoute and an extractMany() for the UsersRoute. However, interestingly enough, most of the time extractMany() (for getting a JSON return of all of the users) would occur before extract() for the single user and its sideloaded properties. When this happened the sideloaded properties would indeed render to the template. However, every once in a while extract() would come before extractMany() (it asynchronosly "beat" the extract many), the sideloaded properties would not render. I think this is because if the extract() occured first that model would then be reset when the extractMany() then occurred for all of the models, which when extracting many do not have sideloaded properties.
I fixed this first problem by doing the following:
App.Router.map(function() {
this.resource('users', function() {
this.route('create');
});
this.resource('user', { path: 'user/:user_id' }, function() {
this.route('edit');
this.route('delete');
});
});
This prevented both models from being extracted in the same route, but the following might have solved both problems.
Second Problem: When navigating away from client/3 to clients and then back to client/3 again, the model would get reset just like the first problem—-sideloaded properties would get dropped.
The way to fix this was to use the UserRoute's activate hook to reload the model.
App.UserRoute = Ember.Route.extend({
activate: function() {
this.modelFor('user').reload();
}
});
This will force the model to be reloaded with the sideloaded properties every time this route 'activates', which is needed of this particular app we're building anyway.
Hope this helps somebody!
You may want to have a custom property that observes your association and print its content in the console.
printRelationship: function() {
console.log(model.clients.get('length'), model.clients);
}.computed(model.clients.#each);