Baqend onUpdate Handler - baqend

Will doing partialupdate() cause code in a data class' onUpdate Handler to run?
I have this setup in the data class:
exports.onUpdate = function(db, obj) {
DB.log.info(obj.ShiftID);
db.Shifts.load(obj.ShiftID)
.then((Shift) => {
DB.log.info(Shift);
if (Shift.User == db.User.me) {
Shift.User = null;
Shift.status = 0;
return Shift.update();
}
})
};
(yes, role 2 for node has permissions to query and update the Shifts data class)
But I am getting zero logs when I make a partialupdate(). Do I need to do a real update query...load the object, modify the data, update()?
Also it seems that this code causes the partialupdate() to not run at all, but when I delete the handler, it starts working again.

Yes, that is currently an unimplemented feature since a partial update can't execute an onUpdate handler since there is no object which can be passed to the update handler.
On the other hand, a partial update can't be executed directly since that will result in a security issue (since your onUpdate handler can contain validation code etc.)
So we currently reject any partial update on a class which has an onUpdate handler because there doesn't exist a way how we can actually validate the partial update against your onUpdate code.
We have planned that you can define an extra onPartial handler where you can take some extra steps before the partialUpdate is executed. But that handler will only get the partial update and not the object itself.

I'm pretty sure that partialupdate() will not cause the onUpdate Handler to run.
When I put the log line in and edit the records using website data manager it does log as expected. Not a big deal, I can just rewrite the query to be a full update.
BUT having any code in there does break partialupdate() which is not good.
Here is the code I'm using that works as long as there is nothing in the onUpdateHandler:
requestShift(shiftID) {
db.ready().then((db) => {
db.Applicants.find()
.where({
"shiftID": { "$in": [shiftID] },
})
.singleResult((applicants) => {
return applicants.partialUpdate()
.add("applicants", db.User.me.id)
.add("photos", this.props.UserData.photo)
.execute()
})
Alert.alert(
'Confirmation',
'Shift has been requested.',
)
this.props.navigation.dispatch(goToFindShifts)
})
}

Related

<query>.loading will not change to true

What are the possible reasons for query being stuck on loading = true (networkStatus = 1)?
I cannot get a query result on refetch and cannot log 'called2'
graphql(_stepQuery, {
name: 'stepQuery',
options: ({goalDocId}) => ({
fetchPolicy: 'network-only',
notifyOnNetworkStatusChange: true,
variables: {
goalDocId
}
})
}
)
componentWillReceiveProps(nextProps) {
let stepIdsFromServer
if (nextProps.currentGoalSteps.length > this.props.currentGoalSteps.length) {
console.log('called')
this.props.stepQuery.refetch()
console.log('this.props', this.props)
console.log('nextProps',nextProps)
if (!nextProps.stepQuery.loading) {
// console.log('nextProps.stepQuery.allSteps', nextProps.stepQuery.allSteps)
console.log('called2')
}
This looks quite dangerous for a infinite loop.
First the refetch function is a Promise, so you will not be able to know the correct query state right after the call for refetching. You would need to go on in the .then function. See refetch Api.
Second the query in the end is executed inside the graphql wrapper Component. So you should not check the loading state and refetch in the componentWillReceiveProps function, Because when the query is executed again the whole component is instantiated again and will enter the componentWillReceiveProps function with resetted states and so on.
If you need some kind of search, i suggest you use a mutation as a workaround (using withApollo wrapper and in the componentWillReceiveProps you call this.props.client("MUTATION")), because this will not render the whole component.

Loopback: detect a change in the model

My overall goal in the hook I'm trying to use here in my /models/LocatableUser.js is to figure out if there's an actual change I need to update for, and if there is, do something (make another api call).
I have a structure of custom models that inherit from this custom model, and so it works for both sub models when defining the before save hook in the parent model. Here's a sample of the method I defined in the parent model, LocatableUser:
LocatableUser.observe('before save', function (ctx, next) {
if (ctx.instance){ // new record
ctx.instance._address.getGeopoint(function (error, location) {
setLocation(error, location, ctx.instance, next);
});
} else if (ctx.currentInstance) { // this is an update, currentInstance is treated as immutable
LocatableUser.findById(ctx.currentInstance.id, function(err, data) {
console.log('Locatable User: current data is: ', err, data)
})
console.log('Locatable User: ctx is:', ctx);
ctx.currentInstance._address.getGeopoint(function (error, location) {
setLocation(error, location, ctx.data, next);
});
} else {
console.warn('no context instance');
}
});
The problem with this code is, since there are no concrete classes of a LocatableUser, calling LocatableUser.findById() won't find anything, because the actual class will be some child class of LocatableUser. The only thing I've found that works is defining this method in both child classes, but that duplicates code.
Is there a way to have the derived classes' findById method called from the LocatableUser class?
Loopback version 2.22.0
So it turns out I was going about this the wrong way:
In the PUT call, ctx.currentInstance comes in as the currently stored instance, removing the need for me to query for that same instance by ID. The ctx.data object is the actual payload coming from the call to the rest API, so I could just compare the data coming in from that to the currentInstance, in order to determine if I need to run some update logic.

How to observe a computed property in EmberJS? Creating a FB like notification feature

I am building notification feature for my app just like Facebook's notification. I have almost made it work but just unable to observe a computed property.
Here is the scenario:
There are many deals and when a deal is updated(like it's name/ price is changed), the notification is sent through RabbitMQ. The object payload that we send, it has an attribute "status" which could be 'read' or 'unread'.
controller:
notificationsCount: function() {
var notifications = this.get('notifications');
var unreadCount = 0;
for (var i = 0; i < notifications.length; i++) {
if (notifications[i].status == 'unread') {
unreadCount++;
}
}
return unreadCount;
}.property('notifications.[]'),
Here, initially 'notifications' is an empty array. All the notifications coming from RMQ as object payloads goes inside this. This 'unreadCount' is what I want to show kinda like a small badge over the notification icon.
When I click the notification icon, all the notifications' status should change to 'read' from 'unread'.
controller:
action:{
readNotifications: function () {
var notifications = this.get('notifications');
for (var i = 0; i < notifications.length; i++) {
notifications[i].status = 'read';
}
},
}
Through debugging, I found everything is working fine till this point. But what I want is, once the user clicks the notification icon and all the notifications are marked as read, the notificationCount should be set as zero as there are no more any notifications that is unread.
Theoretically, I have to either observe notificationsCount or execute notificationsCount once inside readNotifications action. But I couldn't find a way to do it. If there is any other way, feel free to share.
Thanks in advance.
The short of it is that you should define your notificationsCount computed property to listen to notifications.#each.status instead of notifications.[]. .[] triggers when the array contents change (elements are added or removed), while an .#each.prop triggers when the prop property on any array element changes.
Refer to the relevant Ember.js docs for details on this.
Additionally, you can make your code more concise using NativeArray methods (because, since you are already using the .property() shorthand, you do have prototype extension enabled). Your entire notificationsCount could be written as
notificationsCount: function() {
return this.get('notifications').filterBy('status', 'unread').length;
}.property('notifications.#each.status'),
and your action as
readNotifications: function () {
this.get('notifications').setEach('status', 'read');
},

Ember: transaction.commit does not fire after becameInvalid event

I am using Ember data with the REST adapter. When you save a record using this.transaction.commit() and the server responds with a 422 validation error, then this case can be captured using the "becameError" event.
However, after changing the data in teh form field and clicking save again (thus doing a second this.transaction.commit(), nothing happens. The transaction is not committed because we are in a Invalid state ...
How can I solve this ?
You can transition the model back to uncommitted state via it's stateManager. If it is an existing record, transition to loaded.updated.committed:
model.get('stateManager').transitionTo('loaded.updated.uncommitted')
and for new records, transition to loaded.created.uncommitted
model.get('stateManager').transitionTo('loaded.created.uncommitted')
Consider this to be a workaround until the ember-data API has a better way.
See What can you do with Ember Data Models when in the error state? and https://gist.github.com/intrica/4773420 for more detail
After transition to ('loaded.created.uncommitted') state in case of a becameInvalid state, you need to use the store defaultTransaction to recommit.
See code below - very dirty check to know whether to use transaction.commit() or defaultTransaction.commit()
save: function () {
//Local commit - author record goes in Flight state
this.transaction.commit();
//After a becameInvalid state, transaction.commit() does not work; use defaultTransaction in that case
//Is this the only way to do this ?
if (this.get('stateManager.currentState.name') == "uncommitted") {
this.get('store').get("defaultTransaction").commit();
}
var author = this.get('model');
author.one('didCreate', this, function () {
this.transitionToRoute('author.edit', author);
});
//If response is error (e.g. REST API not accessible): becameError fires
author.one('becameError', this, function () {
this.get('stateManager').transitionTo('loaded.created.uncommitted');
});
//If response is 422 (validation problem at server side): becameInvalid fires
author.one('becameInvalid', this, function () {
this.set('errors', this.get('content.errors'));
//Does set stateManager.currentState.name to uncommitted, but when committing again, nothing happens.
this.get('stateManager').transitionTo('loaded.created.uncommitted')
});
},
As a horrible workaround, you can try saving a clone of the record instead. This will leave your original record pristine.
If the save succeeds, delete the original record.
Else delete the clone and try again with a new clone.

Recomputing entity changeset in onFlush listener

Consider the following schema:
[Work]
id
tags ManyToMany(targetEntity="Tag", inversedBy="works", cascade={"persist"})
[Tag]
id
works_count
works ManyToMany(targetEntity="Work", mappedBy="tags")
works_count is a counter cache for Tag::works.
I have a onFlush listener on Work that checks if Work::tags has changed, and updates each of the tags' works_count.
public function onFlush(OnFlushEventArgs $args)
{
foreach ($uow->getScheduledEntityUpdates() as $work) {
$changedTags = /* update relevant tags and return all the changed ones */
$metadata = $em->getClassMetadata('Acme\Entity\Tag');
foreach ($changedTags as $tag) {
$uow->recomputeSingleEntityChangeSet($metadata, $tag);
}
}
}
Now if I read the changesets of the updated tags, the changes of works_count appears correctly, but they don't get updated in the database..
If I replace recomputeSingleEntityChangeSet() with computeChangeSet() then everything works as expected and the DB is updated, but computeChangeSet() has an #internal Don't call from the outside. annotation on it, so I'm not sure what the consequences are..
Every source on the internet says to use recomputeSingleEntityChangeSet so why doesn't it work in this case?
P.S
The tags are managed by the EntityManager ($em->contains($tag) returns true)
This problem was related with a bug in UnitOfWork and finally it's fixed with the release of Doctrine ORM 2.4.3 on September 11, 2014. See DDC-2996 for details.
It seems that Doctrine 2.2 can merge change sets or generate new change sets, but it needs to know which. If you get it wrong, it will either replace your existing change sets or do nothing at all. I'd be very interested to know if there is a better option than this, or if this is even right.
if($uow->getEntityChangeSet($entity)) {
/** If the entity has pending changes, we need to recompute/merge. */
$uow->recomputeSingleEntityChangeSet($meta, $contact);
} else {
/** If there are no changes, we compute from scratch? */
$uow->computeChangeSet($meta, $entity);
}
In doctrine 2.4.1, use recomputeSingleEntityChangeSet only if you are changing tag in the event listener AND UOW contain tag ChangeSet (Change that happen outside of the event listener). Basically recomputeSingleEntityChangeSet is a function to merge ChangeSet for an entity.
Doc from the function
The passed entity must be a managed entity. If the entity already has a change set because this method is invoked during a commit cycle then the change sets are added whereby changes detected in this method prevail.
NOTE: You need to make sure UOW already have ChangeSet for the entity, otherwise it will not merge.
For future readers, at all cost try to avoid the listeners. Those are hardly testable, your domain should not rely on magic. Consider OP's test case how to achieve the same without Doctrine events:
Work class:
public function addTag(Tag $tag): void
{
if (!$this->tags->contains($tag)) {
$this->tags->add($tag);
$tag->addWork($this);
}
}
Tag class:
public function addWork(Work $work): void
{
if (!$this->works->contains($work)) {
$work->addTag($this);
$this->works->add($work);
$this->worksCount = count($this->works);
}
}
TagTest class:
public function testItUpdatesWorksCountWhenWorkIsAdded()
{
$tag = new Tag();
$tag->addWork(new Work());
$tag->addWork(new Work());
$this->assertSame(2, $tag->getWorkCount());
}
public function testItDoesNotUpdateWorksCountIfWorkIsAlreadyInCollection()
{
$tag = new Tag();
$work = new Work();
$tag->addWork($work);
$tag->addWork($work);
$this->assertSame(1, $tag->getWorkCount());
}