What loopback hook should I use? - loopbackjs

I am trying to do the following thing:
I have a model, say myModel which has some method calculateSomething. I defined that function by writing something like this in the MyModel.js file:
MyModel.prototype.calculateSomething = function(cb){
...
return cb(null,result)
}
Now I want to include the result of calculateSomething in the json whenever an instance of MyModel is returned from the api.
How do I do this? I tried using the "loaded" hook, but I believe this hook gets executed before the MyModel instance is created, so I can't call the calculateSomehing method there.
EDIT: It turns out that I can just use the "loaded" hook. I can use the ctx.instance in the hook to get the object.
I was confused by the documentation :
"LoopBack invokes this hook after the connector fetches data, but before creating a model instance from that data". Is the documentation wrong or am I misunderstanding it?

How about using Remote Hooks (on mymodel.js):
// run before any static method eg. MyModel.find
MyModel.beforeRemote('*', function(ctx, myModel, next) {
myModel.calculateSomething(function(err, something) {
if (err) throw err
myModel.something = something
next()
})
});
OR
If you need to do it on object initialization phase (while operation hook loaded seems to be not working) maybe you can try the model hook afterInitialize assuming no async call invoked from calculateSomething:
MyModel.afterInitialize = function() {
this.something = this.calculateSomething(function(err, result) {
return result
})
}
OR
As discussed below, if you need do async call and/or want to have this logic on subclasses, I think you should consider implementing createSomething not as object/prototype method but as a mixins. I haven't tried this personally though but it looks quite suitable to your need.

Related

How to trigger didReceiveAttrs in Ember component

Using version 2.17. I have an Ember component inside an /edit route with a controller:
// edit.hbs
{{ingredient-table recipe=model ingredients=model.ingredients}}
Inside my component, I am using a didRecieveAttrs hook to loop through ingredients on render, create proxy objects based off of each, and then build an ingredient table using those proxy objects.
// ingredient-table.js
didReceiveAttrs() {
let uniqueIngredients = {};
this.get('ingredients').forEach((ingredient) => {
// do some stuff
})
this.set('recipeIngredients', Object.values(uniqueIngredients));
}
I also have a delete action, which I invoke when a user wishes to delete a row in the ingredient table. My delete action looks like this:
// ingredient-table.js
deleteIngredient(ingredient) {
ingredient.deleteRecord();
ingredient.save().then(() => {
// yay! deleted!
})
}
Everything mentioned above is working fine. The problem is that the deleted ingredient row remains in the table until the page refreshes. It should disappear immediately after the user deletes it, without page refresh. I need to trigger the didReceiveAttrs hook again. If I manually call that hook, all my problems are solved. But I don't think I should be manually calling it.
Based on the docs, it is my understanding that this hook will fire again on page load, and on re-renders (not initiated internally). I'm having some trouble figuring out what this means, I guess. Here's what I've tried:
1) calling ingredients.reload() in the promise handler of my save in ingredient-table.js (I also tried recipe.reload() here).
2) creating a controller function that calls model.ingredients.reload(), and passing that through to my component, then calling it in the promise handler. (I also tried model.reload() here).
Neither worked. Am I even using the right hook?
I suppose recipeIngredients is the items listed in the table. If that is the case; please remove the code within didReceiveAttrs hook and make recipeIngredients a computed property within the component. Let the code talk:
// ingredient-table.js
recipeIngredients: Ember.computed('ingredients.[]', function() {
let uniqueIngredients = {};
this.get('ingredients').forEach((ingredient) => {
// do some stuff
})
return Object.values(uniqueIngredients)
})
My guess is didReceiveAttrs hook is not triggered again; because the array ingredients passed to the component is not changed; so attrs are not changed. By the way; do your best to rely on Ember's computed properties whenever possible; they are in the hearth of Ember design.

How to update an ember-infinity infinityModel?

I am trying to implement searching with ember-infinity. But, I do not understand the interaction between the route model and infinityModel.
I have the following code:
model() {
...
return this.infinityModel("myModel", {...}, {...})
}
My search action looks like the following:
search(searchCriteria){
const controller = this.get('controller');
...
_this.infinityModel("myModel", {search:searchCriteria, ...}, {...}).then((myMod)=>{
...
controller.set('model', myModel);
});
}
So this works, but the my query gets fired twice when search is called.
The following only fires the query once.
search(searchCriteria){
const _this = this;
...
_this.infinityModel("myModel", {search:searchCriteria, ...}, {...});
}
But my model does not update. However infinityModelUpdated() function is fired. So I assume that means the infiniteModel was updated, which I assume is my model.
I am pretty sure I am missing something simple. But any help would be greatly appreciated.
Just calling the following:
_this.infinityModel("myModel", {search:searchCriteria, ...}, {...});
does not solve your problem; that is because that method call just returns fresh set of new objects retrieved; which is irrelevant with your original model that you had already returned from model hook. In other words; that method call makes the remote call but does not push the objects retrieved to the model that is already returned from the hook method. If you instead set the model of the controller; then of course the new data is updated to the screen; but I am not sure why a second remote call is being made. That might be related with existence of an infinity-loader already existing in your screen.
What I would suggest is to use updateInfinityModel provided instead of setting the model of the controller. Please take a look at the twiddle I have provided. It uses ember-cli-mirage to mock data returned by the server. Anyway, our point is looking at the makeInfinityRemoteCall action.
this.infinityModel("dummy", { perPage: 12, startingPage: 5}).then((myModel)=>this.updateInfinityModel(myModel));
Here a remote call is made upon button click and data is appended to the model already constructed in model hook. I hope this helps you clear things. Please do not hesitate to alter the twiddle yourself or ask further questions you have.
After your comment, I have updated the twiddle to change the model directly. The duplicate remote call that you have mentioned does not seem to be appearing. Are you sure an exact duplicate remote call is being made? Can it be just the case you are using infinity-loader at your template and a remote call for the next page is being made due to appearance within the view port?

Can loopback.io operation hooks be registered only once per model?

I'm trying out operation hooks http://docs.strongloop.com/display/public/LB/Operation+hooks
Here is what I did in app code:
mymodel.observe('before save', doSomething);
//after some time elapses or based on an event we want to change the behaviour
mymodel.observe('before save', doSomethingElse);
var doSomething = function (ctx, next) {
//do something
next();
};
var doSomethingElse = function (ctx, next) {
//do something else
next();
};
When I test this code I find that always doSomething is executed which makes me wonder if the observer function can be registered only once per model or is it a bug?
If it is as per design, could you please tell the reason behind it?
Disclaimer: I am a core developer of LoopBack and the author of Operation hooks.
after some time elapses or based on an event we want to change the behaviour
The Operation hooks do not support unregistering of handler functions yet. Each call of observe() adds the handler to the list of methods invoked when a hook is triggered.
When I test this code I find that always doSomething is executed which makes me wonder if the observer function can be registered only once per model or is it a bug?
You can register multiple observers. Once you have registered doSomething, it will be always called. When you register doSomethingElse, it will be called too, after doSomething returns via next().
You can now unregister all observers with the clearObservers method and remove a single observer with the removeObserver method. See the ObserverMixin documentation.
According to the changelog, this feature was added in version 2.23.0 of the datasource juggler.

How can I directly invoke an event handler on a route in Emberjs?

I'm hoping to stub/spy on a route's event handler in ember.js using my testing framework of choice, jasmine. Usually this involves overwriting the function of interest with a spy, which requires access to the object on which the method is defined:
spy = spyOn(someObject, "methodOnThatObject")
But in Ember, my event handlers for my routes are defined as follows:
App.ActivityRoute = Ember.Route.extend({
events: {
show: function(context) {
}
}
});
I would like to stub the function show, but I don't know how to get the object on which it is eventually defined? Or is it ever defined on an object? Perhaps it's invoked with #call or #apply? If so, how does one stub this?
I've tried digging around the source, but didn't manage to figure out how this is handled. Any pointers to where I should look in the source would also be helpful.
Cheers,
Kevin
You can use send('eventName', [optional record]):
If you're calling from a controller under the same route do:
this.get('target').send('show', this.get('content'))
Silly me. I can just do the following:
route = App.__container__.lookup('route:myRoute')
spy = spyOn(route.get('events'), 'show')
controller.send('show')
expect(spy).toHaveBeenCalled()
And that works.

init is not called on object when using find

so I am working on an XMPP client with Ember.js. Since my data is coming from XMPP I wanted to create my own models and found this nice tutorial: http://eviltrout.com/2013/03/23/ember-without-data.html and the small example application emberreddit.
The setup should be pretty simple. I just extend Ember.Object and implement a find function which either creates or returns the object:
App.Conversation = Ember.Object.extend({
messages: [],
talkingPartner: null,
init: function(){
this._super();
console.log("Init called for App.Conversation");
//Binding for XMPP client event
$.subscribe('message.client.im', _.bind(this._onMessage, this));
},
//Private Callbacks
_onMessage: function(event, message){
console.log("Received message");
this.find(message.jid).messages.pushObject(message);
}
});
App.Conversation = Ember.Object.reopenClass({
store: {},
find: function(id){
if(!this.store[id]){
this.store[id] = App.Conversation.create();
}
return this.store[id];
}
});
This follows roughly the code from here. It works okay but init is never called. If I create the object not using find it works. So I am a little confused.
As far as I know store should be the same for all instances of
App.Conversation. Is that correct? Also, if that is true I have to
move messages and talkingPartner to init and set them via
this.set('message'), don't I.
Why is ìnit not called when App.Conversateion.create() is called in App.Conversation.find(id). Can anyone explain why? I found that Ember.js sometimes behaves a little bit different than one expects at first.
You need to change this:
App.Conversation = Ember.Object.reopenClass({
To this:
App.Conversation.reopenClass({
You code is reopening Ember.Object itself, and the completely overwriting the definition of App.Conversation.
Here's a working jsFiddle based on your code.