I don't understand how ember data updates the model, if you do this
model: function() {
return this.store.find('something');
}
You're not actually giving it any reference to the model, so how does it update or how does it know what to update when it gets the result back from the server?
That particular example is actually requesting a collection (all) of something.
If you are looking for a particular record you would do this.store.find('something', id) where id is some unique identifier.
this.store.find('something', 3)
this.store.find('something', "cool_post")
App.Something = DS.Model.extend({
someAttr = DS.attr()
});
var promise = this.store.find('something', 2);
promise.then(function(record){
//promise is resolved and the record is ready here
console.log(record.get('someAttr'));
}
find will always return a promise. Ember Data will then asynchronously search it's cache or make a call to the server (or fixture data depending on your adapter). Once the data has return it will then create an instance of something (defined above). It will then use the serializer associated with the adapter used and apply the results to the instance of something.
The format your json should come in is like so:
{
"something": {
"id": 1
"someAttr": "Rails is omakase"
}
}
The model hook is a special hook in that if you return a promise to it it will wait and resolve that promise and use the result of the promise instead of the promise for the model of the controller.
See the ember data transition document for additional information: https://github.com/emberjs/data/blob/master/TRANSITION.md
Related
I have this action. Data comes from a form after a createRecord, and it is saved perfectly in the database.
App.ShowController = Ember.Controller.extend({
actions: {
createShow: function() {
var self = this;
var onSuccess = function(res) {
alert('CREATED OK ' + res.get('alias'));
};
var onFail = function() {
alert('err ' + res);
};
self.get('model').save().then(onSuccess, onFail);
}
}
});
The id is generated in the database (Postgres), and I return it in a perfectly formatted json response from the app (made in Mojolicious) {"serverResponses":{"last_id":"500"}} along with a '200' status.
I can see the network response in the console, with the json data.
But how can I access the last_id value in the callback function onSuccess????
In "res" I have the original data I sent to the server, but, obviously its "id" attribute is undefined.
My idea es to "set" the returned id from the database in the "id" of the model.
I have seen a lot of questions about the returning format of the son, serialize problems, and so on, but what I really want to know is WHERE, in WHICH variable or object is the returned data?
Obviously, in case of Fail, I have the same problema. I return a perfectly formatted json with root for ember, but can't find it in the callback function onFail.
Can someone point me in the right direction?
Regards
When you create the record on the server you can return the json of the record, including ID (the same way you would return the json for a GET request). Ember data will then automatically use that response to update the model in its store. Then the argument passed to onSuccess will contain the updated model with the generated ID.
If it's not possible to change the REST api, you'll have to look into extending RESTSerializer to extract the id from the payload.
You need to configure properly your model, for example if you have an x model inside your controller create show action
//create your model locally
//acquisition is for the name of your model
//product_id is just whatever attributes you declare in your model spec
var newAcquisition = this.store.createRecord('acquisition', {
'product_id': this.get('id'),
});
//with save it calls to the server and creates the new model and retrieves a response
newAcquisition.save().then(function(response){
console.log('success - json '+response);
}, function(response){
console.log('fail - err '+response);
});
After this, you don't need to catch response to put and id, if your json response is correct, Ember will handle that response and update your newly created object with that new ID
You can save data in route to controller in setupController function
App.ShowRoute = Ember.Route.extend({
setupController: function(controller, model){
//setup form data
controller.set('formData', data);
},
actions:{
}
});
I am attempting to save an Ember Data DS.Model after it's been updated, but when I call myModel.save(), I'm finding that Ember Data is sending the original, non-updated model instead of the updated one. I'm trying to understand why this is happening and what I need to do differently.
Here are some details. First, I have two models:
/models/OrgUser.js:
DS.Model.extend({
...
orgPerson: DS.belongsTo('org-person', { inverse: 'org-user', async: true, embedded: 'always' }),
});
Note that I am using a customized RESTSerializer (see below), so the only use of embedded: 'always' is how my custom RESTSerializer handles it.
/models/OrgPerson.js:
DS.Model.extend({
...
orgUser: DS.belongsTo('org-user'),
})
To persist these models, I'm using the RESTAdapter. In an attempt to generate a single JSON request to my API that contains both models above, I've made a single customization to the adapter. I don't think this is affecting anything, but just in case I'm missing something, here it is:
/serializers/application.js:
DS.RESTSerializer.extend({
serializeBelongsTo: function(record, json, relationship) {
var key = relationship.key;
key = this.keyForRelationship ? this.keyForRelationship(key, 'belongsTo') : key;
var data = record.get('data');
if (relationship.options.embedded && relationship.options.embedded === 'always') {
json[key] = data[relationship.key] ? data[relationship.key].get('data') : null;
}
else {
json[key] = data[relationship.key] ? data[relationship.key].get('id') : null;
}
if (relationship.options.polymorphic) {
this.serializePolymorphicType(record, json, relationship);
}
}
})
With that setup, I have a template where I update the orgPerson properties. I can confirm these are bound properties because updating their input updates their display on another part of the template in real-time. I then call an action on my controller, and within that action do the following:
/controllers/my-page.js:
export default Ember.ObjectController.extend( FormMixin, {
actions: {
submitForm: function() {
...
this.get('model') // Chrome console shows that _data.orgPerson._data.firstName has the (incorrect) old property
this.get('model').serialize() // returns (incorrect) old firstName
this.get('orgPerson.firstName') // returns (correct) updated firstName
this.get('orgPerson').get('firstName') // returns (correct) updated firstName
...
}
}
});
Any idea why I am getting two different versions of the same model? How can I serialize the correctly updated model? Thanks for any input!
SOLUTION:
Thanks (again!) to #kingpin2k, I have resolved this issue. Here are the steps I took:
My serializer was in fact the problem, and using Ember's old preserved data. I replaced the line data[relationship.key].get('data') with the line data[relationship.key].serialize() and this was fixed.
I then ran into another issue, which was that if I edited my record, did NOT save it, and then went back to my list of records, the list still showed the edit. My first thought was that I needed to update my list page's array model to show only the latest content, but there didn't appear to be any Ember facilities for this.
So I ultimately solved this by using the following code in my route. Note that because orgPerson is async: true I had to wrap my model in a promise. Note also that I had to directly call model.orgPerson versus just model.
Updated route:
actions: {
willTransition: function( transition ) {
this.controller.get('model.orgPerson').then( function( value ) {
if ( value.get('isDirty') ) {
value.rollback();
}
});
}
}
Going forward, I just want to call this.controller.get('model').rollback(), so I'm going to write a util function that traverses eachRelationship and then individually calls rollback() on any of the objects. Whew, a lot of subtlety to get this working right.
Ember Data stores the original values in the data obj. It stores modified values in _attributes obj. During a save it moves _attributes obj to inFlightAttributes obj, then after the save is complete it merges them from inFlightAttributes to data. All of this is so you can rollback your record.
When you define a property as attr it hooks up the magical get where it first checks _attributes, then inFlightAttributes, then data and returns that property's result.
function getValue(record, key) {
if (record._attributes.hasOwnProperty(key)) {
return record._attributes[key];
} else if (record._inFlightAttributes.hasOwnProperty(key)) {
return record._inFlightAttributes[key];
} else {
return record._data[key];
}
}
https://github.com/emberjs/data/blob/v1.0.0-beta.8/packages/ember-data/lib/system/model/attributes.js#L267
In your case, Ember Data doesn't know you are saving that record, and you are manually grabbing the old properties from the data obj. You'd either need to manually merge _attributes to data or trick Ember Data into thinking you'd saved it.
I'm learning Ember.js, and am writing an application that I want to perform the following tasks ...
load some data from local storage
check 3rd party API for new additional data
append any additional and save entire thing back to local storage
display a table of this data
My application is just a single route. I'm using the Ember.Route's model hook to load data from local storage. Where is a good spot to check that 3rd party API for any new data, though? Should I also do this in the model hook? I'd like to be able to display some sort of loading icon during the query to the 3rd party API and I'm not sure if that model hook will allow me to do this?
Right now, my Route only contains the following code ...
App.HistoryRoute = Ember.Route.extend({
model: function (params) {
// initialize model
var model = { summoner: params, history: [] };
if (typeof(localStorage.history) == 'undefined')
return model;
// fetch the data from local storage
var history = JSON.parse(localStorage.history);
// check for existing data
if (!history.hasOwnKey(params.region) || !history[params.region].hasOwnKey(params.name))
return model;
// use the data from local storage
return history[params.region][params.name];
}
});
The data in local storage is namespaced using a region and name. It looks something like this ...
{
"NorthAmerica": {
"ryan": {
"summoner": { "region": "NorthAmerica", "name": "ryan" },
"history": [ ... ]
}
}
}
So, the Route's model method loads that data so that it can be used as the model. Where should I hit the 3rd party API to get new data, though? I'd like to check it for new data each time the page is refreshed. Thanks!
The model hook is certainly the typical place that Ember expects you to place code like that. I wanted to create a lazy loading/infinite scroll mechanism and the best place to put the code to retrieve additional content was on the controller. For organization sake, I ended up moving the call to load my initial data into the controller as well. Using Ember.run.scheduleOnce, I was able to ensure that the load happened after the render queue:
init: function() {
//make sure the nested views have rendered before fetching initial data
Ember.run.scheduleOnce('afterRender', this, this.fetchInitialData);
},
//fetch initial data based on pageSize
fetchInitialData: function() {
//enable loading animation
this.set('isLoading', true);
//get the first page of users
var self = this;
$.post("/user/search", {limit: 15})
.then(function(response) {
self.set('isLoading', false);
self.set('total', response.total);
self.set('model', response.users);
});
}
Hope that helps! :)
The model hook is indeed the place to do this. Ember will wait until a promise returned in the model hook is resolved before completing the transition. If you want to display something while the page is loading, the HistoryLoadingRoute (or the corresponding history/loading template) will do exactly that. http://emberjs.com/guides/routing/loading-and-error-substates/
To make sure Ember waits for your API call to complete before moving out of the loading state, you'll need to return a promise in the model hook. You can use return $.ajax(... for this, or I prefer return ic_ajax.request(... from https://github.com/instructure/ic-ajax because it works better with Ember testing.
I'm currently using the FixtureAdapter in my Ember app, but when I switch to the RESTAdapter, my URLs no longer work.
The app is a scorekeeping type thing, and I want users to be able to log all the scores without having to be connected to the Web. After the game is finished they can optionally save all the data to the server.
Now, when Ember wants to route to say, matches/:match_id, the ID isn't there because I didn't commit anything to the server/store, so my models don't yet have an ID and I get URLs like: /match/null/games/null
Is this expected behaviour? And if so, is there a workaround? I thought about using model.clientId and then overriding the model hook for each route to try and fetch the Model from the store using the id when present and falling back to clientId. Any other ideas?
UPDATE March 10, 2013:
The following seems to fit my needs and allows to (for now) forget about moving back and forth between local storage and the REST adapter:
App.Store = DS.Store.extend({
revision: 11,
adapter: DS.RESTAdapter.extend({
namespace: 'api/v1',
bulkCommit: true,
generateIdForRecord: function(store, record) {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
}
})
});
UUID function taken from: Create GUID / UUID in JavaScript?
If a record hasn't been committed, then it shouldn't have an id yet. Furthermore, a url that serializes the application state of viewing that record doesn't make any sense because the record won't exist in another browser until it is committed. You couldn't just paste the url elsewhere and have it load where you left off.
I think what you really want to do is serialize the application state differently (ie. generate a less specific url) when the record is uncommitted. You can achieve this by overriding the serialize method in your route.
For example,
App.PostRoute = Ember.Route.extend({
serialize: function(model, params) {
if (model && model.get('isNew')) {
// corresponds to path '/post/:post_id'
return { post_id: 'new'}
}
return this._super(model, params);
}
});
Now if you call transitionToRoute('post', post) from your controller, where post is a newly created but uncommitted record, the application state will be serialized to the path /post/new. If you pass it a committed record with an id, it will be serialized as usual.
I'm working with a set of data that can potentially have duplicate values. When I initially add the data I'm using what little information I have available on the client (static info stored on the model in memory).
But because I need to fetch the latest each time the handlebars template is shown I also fire off a "findAll" in the computed property to get any new data that might have hit server side since the initial ember app was launched.
During this process I use the "addObjects" method on the ember-data model but when the server side is returned I see duplicate records in the array (assuming it's because they don't have the same clientId)
App.Day = DS.Model.extend({
appointments: function() {
//this will hit a backend server so it's slow
return App.Appointment.find();
}.property(),
slots: function() {
//no need to hit a backend server here so it's fast
return App.Slot.all();
}.property(),
combined: function() {
var apts = this.get('apppointments'),
slots = this.get('slots');
for(var i = 0; i < slots.get('length'); i++) {
var slot = slots.objectAt(i);
var tempApt = App.Appointment.createRecord({start: slot.get('start'), end: slot.get('end')});
apts.addObjects(tempApt);
}
return apts;
}.property()
});
Is it possible to tell an ember-data model what makes it unique so that when the promise is resolved it will know "this already exists in the AdapterPopulatedRecordArray so I'll just update it's value instead of showing it twice"
You can use
DS.RESTAdapter.map('App.Slot', {
primaryKey: 'name-of-attribute'
});
DS.RESTAdapter.map('App.Appointment', {
primaryKey: 'name-of-attribute'
});
But I think it is still impossible because App.Slot and App.Appointment are different model classes, so if they have same ids it won't help. You need to use the same model for both slots and appointments for this to work.
Edit
After examinig the source of ember-data, i think that you can define the primaryKey when you define your classes, like:
App.Slot = DS.Model.extend({
primaryKey: 'myId',
otherField: DS.attr('number')
});
I didn't tested it though..
Edit 2
After further reading seems that the previous edit is no longer supported. You need to use map as i wrote earlier.