Ember Data grab already loaded objects from store - javascript

Using Ember 1.13.6 and Ember Data 1.13.7, I am wondering how I can get the already loaded objects from the store without doing a call to the server (where relationship is async).
Image following model:
//page model
export default DS.Model.extend({
parent: DS.belongsTo('page', { async: false, inverse: 'subpages'}),
subpages: DS.hasMany('page',{ async: true, inverse: 'parent' }),
});
If you just call model.get('subpages'), Ember Data returns a promise + make a call to the server. Normally this is fine behaviour, but now I got a special case I just want to grab the already loaded objects.
I can't find anything about such case in the docs. The only way I found at the moment is by using private properties:
model._internalModel._relationships.initializedRelationships.subpages.canonicalState
Of course I seek a normal way to achieve this, without touching the inner code of Ember Data. So does anyone know how to achieve this?

I don't think you can do it from relationships directly, but you can peek.
this.store.peekAll('page')
Wouldn't call the backend. You could probably use a filter, they don't hit the backend:
existingSubpages: function(){
return this.store.filter('page', item => {
return item.parent === this;
});
}.property('subpages.#each')

In addition to #Kit Sunde's answer, there maybe a situation where you want some objects to have an async relation and other objects to have a sync relation.
With the ready hook this can easily be implemented:
ready() {
if(this.get('relationIsSynced')) { //whater case you got
this.set('subpages.content.relationship.isAsync', false);
}
}

Related

then() returning a null value when it should be pending

Ember 2.17
I am calling an helper from my template :
{{#each invoice.invoiceLines as |line| }}
{{pricings/full-pricing line.pricing}}
{{/each}}
invoice,invoiceLine, as well as pricing are ember models.
Here is how invoice is created in model () :
model(params) {
let invoice= this.store.findRecord('invoice',params.invoice_id)
return Ember.RSVP.hash({
invoice: invoice,
allShares: invoice.then((i)=>{return i.allShares()}),
detailShares: invoice.then((i)=>{return i.detailShares()})
});
}
The goal of the helper is to take pricing, extract numbers (everything is in the model, no more relations) and return a string formatting the initial price and the subscription price.
The helper is as following :
import { helper } from '#ember/component/helper';
export function pricingsFullPricing([pricing]) {
return pricing.then(
p=>{
debugger
},p=>{
}
)
}
export default helper(pricingsFullPricing);
When I run the page, debugger is called twice (the template loop run once).
First time p is null, the second time it is a pricing.
Isn't then supposed to prevent that? Why does it behave like that?
Your route is wrong, routes are promise aware (that's what hash is for), it should be:
model(params) {
return Ember.RSVP.hash({
invoice: this.store.findRecord('invoice',params.invoice_id)
//allShares: invoice.then((i)=>{return i.allShares()}),
//detailShares: invoice.then((i)=>{return i.detailShares()})
});
}
Then your handlebars is just:
{{#each model.invoice.invoiceLines as |line| }}
{{line}}
{{/each}}
You also shouldn't call methods like you are on a model. It's not really clear what allShares(), etc does but these should (probably) be computed in the controller. Something along the lines of:
import { computed } from '#ember/object';
export default Controller.extend({
allShares:computed('model.invoice', function(){
return this.get('model.invoice').allShares();
});
});
Though this doesn't seem ideal. Like I said, it's hard to be explicit as it's not clear what your trying to do here. It'd probably make more sense if your extracted these methods into a service.
You then don't need the helper at all. This appears to be just trying to work around promises.
It makes life a lot easier if you try and load all server side data in the route before load.
First rule of helpers
Each time the input to a helper changes, the compute function will be called again.
Second, there's nothing about helpers that will make this block subsequent calls because you are returning a promise.
export function pricingsFullPricing([pricing]) {
return pricing.then(
p=>{
debugger
},p=>{
}
)
}
You've created a simple helper here that will use the promise itself as the value. Look at ember-promise-helpers/await to see how a class based helper is used to manually set the value that's displayed in the template.
Now, if you're wondering why the recomputation is happening, I'm going to have to speculate based off the knowledge I have of Ember data just from being part of the Ember community (I've never actually used Ember Data). You know line.pricing is a promise? I can then assume your using some sort of relationship, which will most likely have to be loaded via an ajax call (hence the promise). But these relationships in Ember data, iirc, use this PromiseProxyMixin that allow them to behave simultaneously like a promise or like an object (depending on whether the data is in the store already or not). This is what allows you to reference the promise in your template without then
See this article for a better understanding of what I mean

Searching Ember data async hasMany ids without loading all the id records

I am trying to search through several thousand records for a particular record. I have loaded a User model and it has several thousand Shipments that are async. In other words I have the serializer send down shipment_ids with the user model.
App.User = DS.Model.extend({
shipments: DS.hasMany('shipment', { async: true })
});
I want to search through all those ids to see if a particular record's id is among them. However I don't want to go to the api and load each and every single record. I just want to be able to search through the shipment_ids.
I have a jsbin that showcases it loading all the records. How do I change this so it doesn't make any api calls for a shipment and still finds the specific record?
Instead of doing
this.get('shipments').any(function(shipment){
return shipment.get('id') === "10124";
there has to be a way to go through only the ids, no?
Thanks!
The solution described in "Get belongsTo ID without fetching record"
seems to work as of Ember Data 1.0.0-beta.10. Briefly, you can do the following to access the underlying
modelInstance.get('data.belongsToRelation.id');
Presumably you can also do this (though I have not tested it):
modelInstance.get('data.hasManyRelation');
Original Answer (DO NOT USE):
I have accomplished this on a belongsTo by adding a second model field for the id. My example:
App.User = DS.Model.extend({
group: DS.belongsTo('group', { async: true }),
groupId: DS.attr('number')
});
So maybe you can do this?
App.User = DS.Model.extend({
shipments: DS.hasMany('shipment', { async: true }),
shipmentIds: DS.attr('array')
});
You will need to add an array transform in app/transforms/array.js (assuming you use ember-cli)
import DS from 'ember-data';
var ArrayTransform = DS.Transform.extend({
deserialize: function(serialized) {
return serialized;
},
serialize: function(deserialized) {
return deserialized;
}
});
export default ArrayTransform;
Let me know if it works as I probably will need to something similar in my app soon.
This hack is very smelly but it's the only way I have found. And it is to search with _data
this.get('content._data.shipments').any(function(shipment){
return shipment.get('id') === "10124";
});
It won't make any api calls. But there has to be a more acceptable method that won't be prone to breakage when updating Ember.
Here is an updated jsbin to show this. Does anyone have a better solution?
Thanks!
Since Ember Data 2.3 you can use this code:
// get all ids without triggering a request
var commentIds = post.hasMany('comments').ids();
See http://emberjs.com/blog/2016/01/12/ember-data-2-3-released.html for details.

How to deal with async properties in Ember when I need blocking calls?

We were using a model with a hasMany with embedded children. This was fine so that whenever I called model.get('children') everything just worked.
We've now changed that children property to async:true, and I can't seem to find proper documentation on how you should handle this.
Let me give you an example. I'll use simplified json to represent my Ember setup, just for the sake of simplicity.
Say I have a Model like this:
model:{
hasMany: {children: {async: true} },
isActive: boolean
}
Say I have a Template like this:
{{#if lastChildIsActive}}
<p>I'm the last one!</p>
{{/if}}
And I have a Controller:
controller:{
lastChildIsActive: function(){
return this.get('model').get('children').get('lastObject').get('isActive')
}
}
Ok, so with this setup when async: false was being used, everything just worked.
But now, with async true, that call in the controller for .get('children') SOMETIMES just doesn't return anything, because it's async I guess.
Now I could use promises, and refactor my controller to this:
controller:{
lastChildIsActive: function(){
this.get('model').get('children').then(function(children){
return children.get('lastObject').get('isActive');
});
}
}
The problem with the second refactor is, I'm not longer returning the isActive value, I'm now returning the promise object.
But the template doesn't want a promise, it needs the returning value.
SO, how can I make sure the async has loaded, while being able to return the actual result of the call instead of the promise?
By using a view and didInsertElement, you can get the children of the parent and then update the attribute on the controller, which will appear in your template.
App.IndexView = Ember.View.extend({
didInsertElement: function() {
controller = this.get('controller');
controller.get('model').get('children').then(function(children){
active = children.get('lastObject').get('active');
controller.set('lastChildIsActive', active);
});
}
});
See this fiddle: http://jsfiddle.net/f9DAc/2/
You have to use observer to listen for the changes in children. Refer the change I have made in lastChildIsActive method below. As children is an array type, I am listening for "children.#each". Whenever, there is a change in "childern", lastChildIsActive will be updated automatically.
controller:{
lastChildIsActive: function(){
return this.get('model').get('children').get('lastObject').get('isActive')
}.property('children.#each')
}

Ember.js model- and setupController-hook with nested routes

I'm currently evaluating Ember.js and therefore I am building a small sample app. Currently everything went quite smooth so far, but now I don't seem to be able to fix my last little problem.
When I access the app normaly via the route films, everything works as expected. The list of films is displayed. Now when I click onto a film, the details of the film are loaded via setupController hook just below the list of films. That's all just fine.
Here comes my problem: I would like to be able to access the film details directly via url, but somehow in this case another request is fired to grab the film details, with the value of undefinded. As far as I understand that is the model hook.
I can only guess, but I think it is the model hook which is beeing executed.
Can someone point me to the probably obvious mistake I'm making? And on the other hand, is the code i wrote so far "correct"? Or is there a better way of doing this?
(I am aware of the bad way I use to render the film details. I will remove the {{#each}} tag, and change the way I asign the response to the film variable.
Here the link to the sample app: http://jsbin.com/ewiN/1#/films
UPDATE
Ok, now I am getting really confused. I almost have it working, hopefully someone can point it out to me, because it's such a simple task, but it nearly seems impossible to do without knowing ember really well...
When accessing the app via url, it only works when I remove the setupController hook. But I need that hook, to load the FilmDetails on clicking onto the links to properly load the FilmDetails.
http://jsbin.com/ewiN/16#/films/tt0100669
Many thanks for the feedback!
Regards
Reto
Instead of use jQuery.getJSON, use Ember.RSVP.Promise, because internally ember use this promise api instead of jquery. I think that using both, can make inconsistencies.
return new Ember.RSVP.Promise(function(resolve, reject) {
var films = [];
jQuery.getJSON("http://www.omdbapi.com/?s=" + searchTerm, function (response) {
$.each(response.Search, function (index, value) {
films.pushObject(Kitag.Films.create({
title: value.Title,
id: value.imdbID
}));
});
}).fail(reject);
resolve(films);
});
Because we are returning a promise instead of an object, we need to use the model hook, because it is wait until the promise is resolve to render templates.
Kitag.FilmsRoute = Ember.Route.extend({
model: function() {
return Kitag.Films.getMovies('spiderman');
}
});
I have removed the Kitag.FilmRoute, because the expected:
Kitag.FilmRoute = Ember.Route.extend({
model: function(params) {
return Kitag.Film.find(params.id)
},
serialize: function (model) {
return { film_id: model.get("id") };
}
});
is the default.
This is the final result http://jsbin.com/ewiN/15/edit

Ember.js createRecord possibly not firing

I have an application that saves a user's search criteria in localStorage, where each saved search is represented as an instance of an Ember.js model:
Checklist.SavedSearch = DS.Model.extend({
id: DS.attr('string'),
filters: DS.attr('string')
});
When the "save" button is pressed, the controller creates a model instanced and creates a record for it:
Checklist.savedSearchController = Ember.ArrayController.create({
[..]
save: function(view) {
var saved_seach = Checklist.SavedSearch.createRecord({
id: 'abcd',
filters: '<json>'
});
Checklist.local_store.commit();
}
});
Checklist.local_store is an adapter I created (this is unsurprisingly where the problem probably begins) that has a basic interface that maps createRecord, updateRecord, etc. to a bunch of get/set methods that work with localStorage (loosely based on a github fork of ember-data). The adapter appears to work fine for some basic tests, particularly as findAll has no issues and returns values added manually to localStorage.
Here is the relevant method within Checklist.local_store:
createRecord: function(store, type, model) {
model.set('id', this.storage.generateId);
var item = model.toJSON({associations: true});
this.storage.setById(this.storage_method, type, id, item);
store.didCreateRecord(model, item);
}
The problem is that when createRecord is called by the controller, absolutely nothing occurs. Running it through the debugger, and logging to console, seems to show that the method isn't called at all. I imagine this is a misunderstanding on my part as to how Ember.js is supposed to work. I'd appreciate help on why this is happening.
I come from a ruby and php background, and have perhaps foolishly dived straight in to a JS framework, so any other comments on code style, structure and anything in general are welcome.
Ember Data doesn't change createRecord on the controller so it shouldn't behave any differently. It's possible that there was something related to this in the past, but it's certainly not the case anymore.

Categories

Resources