diff --git a/src/masoniteorm/models/Model.py b/src/masoniteorm/models/Model.py index c0a67a6f..f439c4d2 100644 --- a/src/masoniteorm/models/Model.py +++ b/src/masoniteorm/models/Model.py @@ -385,11 +385,18 @@ def boot(self): f"{type(self).__name__} must specify either __fillable__ or __guarded__ properties, but not both." ) + self.booted() self._booted = True self.observe_events(self, "booted") self.append_passthrough(list(self.get_builder()._macros.keys())) + def booted(self): + """ + Perform any actions required after the model boots + """ + pass + def append_passthrough(self, passthrough): self.__passthrough__.update(passthrough) return self diff --git a/src/masoniteorm/observers/ObservesEvents.py b/src/masoniteorm/observers/ObservesEvents.py index 3e2cb040..3e5ca54b 100644 --- a/src/masoniteorm/observers/ObservesEvents.py +++ b/src/masoniteorm/observers/ObservesEvents.py @@ -1,3 +1,6 @@ +from types import SimpleNamespace + + class ObservesEvents: def observe_events(self, model, event): if model.__has_events__ == True: @@ -25,3 +28,49 @@ def with_events(cls): """Sets __has_events__ attribute on model to True.""" cls.__has_events__ = True return cls + + @classmethod + def creating(cls, callback): + cls._register_model_event('creating', callback) + + @classmethod + def created(cls, callback): + cls._register_model_event('created', callback) + + @classmethod + def deleting(cls, callback): + cls._register_model_event('deleting', callback) + + @classmethod + def deleted(cls, callback): + cls._register_model_event('deleted', callback) + + @classmethod + def hydrating(cls, callback): + cls._register_model_event('hydrating', callback) + + @classmethod + def hydrated(cls, callback): + cls._register_model_event('hydrated', callback) + + @classmethod + def saving(cls, callback): + cls._register_model_event('saving', callback) + + @classmethod + def saved(cls, callback): + cls._register_model_event('saved', callback) + + @classmethod + def updating(cls, callback): + cls._register_model_event('updating', callback) + + @classmethod + def updated(cls, callback): + cls._register_model_event('updated', callback) + + @classmethod + def _register_model_event(cls, event, callback): + anon_observer = SimpleNamespace() + anon_observer.__setattr__(event, callback) + cls.observe(anon_observer) \ No newline at end of file diff --git a/tests/sqlite/models/test_observers.py b/tests/sqlite/models/test_observers.py index 178eaee4..20949877 100644 --- a/tests/sqlite/models/test_observers.py +++ b/tests/sqlite/models/test_observers.py @@ -120,3 +120,52 @@ def test_hydrating_is_observed(self): self.assertEqual(TestM.observed_hydrating, 1) self.assertEqual(TestM.observed_hydrated, 1) DB.rollback("dev") + + def test_model_can_observe_callback(self): + events = { + "creating": False, + "created": False, + "deleting": False, + "deleted": False, + "hydrating": False, + "hydrated": False, + "saving": False, + "saved": False, + "updating": False, + "updated": False, + } + class ModelWithCallbacksObserver(Model): + def booted(cls): + cls.creating(lambda m: events.update({"creating": True})) + cls.created(lambda m: events.update({"created": True})) + cls.deleting(lambda m: events.update({"deleting": True})) + cls.deleted(lambda m: events.update({"deleted": True})) + cls.hydrating(lambda m: events.update({"hydrating": True})) + cls.hydrated(lambda m: events.update({"hydrated": True})) + cls.saving(lambda m: events.update({"saving": True})) + cls.saved(lambda m: events.update({"saved": True})) + cls.updating(lambda m: events.update({"updating": True})) + cls.updated(lambda m: events.update({"updated": True})) + + model = ModelWithCallbacksObserver() + for event in events: + model.observe_events(model, event) + + for event, is_called in events.items(): + self.assertTrue(is_called) + + def test_model_can_observe_two_callbacks_on_same_event(self): + creating_called_num = 0 + def callback(_): + nonlocal creating_called_num + creating_called_num += 1 + + class ModelWithCallbacksObserver(Model): + def booted(cls): + cls.creating(callback) + cls.creating(callback) + + model = ModelWithCallbacksObserver() + model.observe_events(model, 'creating') + + self.assertEqual(2, creating_called_num) \ No newline at end of file