Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

a scenario that before save hook may not cover #433

Closed
clark0x opened this issue Feb 6, 2015 · 5 comments
Closed

a scenario that before save hook may not cover #433

clark0x opened this issue Feb 6, 2015 · 5 comments
Assignees

Comments

@clark0x
Copy link
Contributor

clark0x commented Feb 6, 2015

@bajtos I'm trying migrate my codes to use the new hook, and I found a scenario that I can't drop the old beforeCreate hook: before creating a model, some related models need to be created or executed, but the creating model must be valid at first.

e.g., for a room booking system, we need to lock the room resource before create a booking:

Booking.observe('before save', function (ctx, next) {
    var booking = ctx.instance;
    booking.room(function (err, room) {
      room.lock(booking.time, next);
    });
});

Currently, the before save hook is called before checking if model is valid. If booking is invalid, room.lock() has already been executed, then we have to unlock the room.

The expect behavior is:

  1. checking if creating booking data is valid
  2. if booking data is valid, try lock the room
  3. if success to lock the room, create booking, then send emails, blah blah...

What do you think?

@bajtos bajtos self-assigned this Feb 6, 2015
@bajtos
Copy link
Member

bajtos commented Feb 6, 2015

Hi @clarkorz, thank you for describing your use case in detail.

The before save hook is intended for case where you don't need to distinguish between different write operations (create, update, prototype.save, updateOrCreate, findOrCreate). Especially with findOrCreate, the hook may be executed even when the create operation is not performed at all, see your patch #419.

I am also wondering how does your application handle the case when the creation of booking fails (e.g. due to database error) just after the related models were already updated?

Setting those concerns aside, you can run the validation yourself in the hook and bail out on validation error.

Booking.observe('before save', function (ctx, next) {
  if (!ctx.instance) {
   // partial update, skip this hook
   return next();
  }

  // TODO: detect `.prototype.save()` and possibly other cases
  // when this is not an update. Or not?

  var booking = ctx.instance;
  booking.isValid(function(valid) {
    if (!valid) {
      return next(new booking.constructor.app.loopback.ValidationError(booking));
    }

    // business as usual
    booking.room(function (err, room) {
      room.lock(booking.time, next);
    });
  });
});

@bajtos
Copy link
Member

bajtos commented Mar 31, 2015

In #545 (comment), I am proposing to add a hook that will allow you to clean up resources after save failed, either because a validation error or some other error.

I am wondering if this would be a good solution for you?

Booking.observe('before save', function (ctx, next) {
    var booking = ctx.instance;
    booking.room(function (err, room) {
      room.lock(booking.time, next);
    });
});

Booking.observer('save error', function(ctx, next) {
    var booking = ctx.instance;
    booking.room(function (err, room) {
      room.unlock(booking.time, next);
    });
});

BTW I am not sure that hooks are the right tool for this job. IIUC, you want to lock the booked room only on CREATE, not on UPDATEs. In which case it's probably better to implement a custom remote method instead of using hooks.

Booking.create = function(data, cb) {
  var self = this;

  self.isValid(function(valid) {
    if (!valid) return cb(new loopback.ValidationError(self));

    booking.room(function (err, room) {
      if (err) return cb(err);

      room.lock(booking.time, function(err) {
        if (err) return cb(err);

        Booking.base.create.call(self, data, function(err, created) {
          if (err) {
            // unlock the room
          }
          cb(err, created);
        });
      });;
    });
  });
};

@bajtos
Copy link
Member

bajtos commented Mar 31, 2015

We can extend the create method to accept options.validate allowing the caller to skip the validation, in which case you would write

Booking.base.create.call(self, data, { validate: false }, function(err, created) {
  // ...
});

@bajtos
Copy link
Member

bajtos commented Apr 8, 2015

Based on the discussion in strongloop/loopback#1226, we are thinking about adding a new hook called "persist" that can be used as a replacement for "afterValidate". See #559.

I am closing this issue, let's move the discussion to 559.

@bajtos bajtos closed this as completed Apr 8, 2015
@bajtos
Copy link
Member

bajtos commented Jun 12, 2015

@clarkorz FWIW, the new "persist" has been landed on master recently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants