Reblocks provides a hooks mechanism to allow to set callbacks on different events. These callbacks are cooled "hooks". Each hook can be added on one of three levels:
Hooks added on request level expire after the request was processed. This could be useful for commiting changes to the database.
Session level hooks are called have a longer life time and work while user session is active. They are bound to a session. Some user might have a hook installed in his session and other user don't. This way you might implement such things like turning on debug information, etc.
Application level hooks are stored permanently and work for every user. Despite the name, they aren't bound to an application. These hooks are global and work for any application and any reblocks server.
Hooks are defined to be called on some event happened in the reblocks application. Here is a list of hooks, predefined in the Reblocks:
Called around code reponsible for an
HTTP request processing,
before any application was choosen.
HTTP request environment.
Should return a list of three items:
a plist of
a list of strings corresponding to the page's content.
Called around code which starts all applications and a webserver.
Returns a reblocks server object.
Called around code which stops all applications and a webserver.
Returns a reblocks server object.
Called when action is processed.
Returns a result of
reblocks/actions:eval-action generic-function application
to the arguments.
Called when around whole page or ajax request processing.
How to use these hooks?
Lets pretend we want to create an extension which will collect all
and render them in hidden panel like Django Debug Toolbar does? To accomplish
this task, we need to add an application-wide
(defun inject-debug-panel (html) (str:replace-first "</html>" "<div id=\"debug-panel\"> DEBUG PANEL EXAMPLE </div> </html>" html)) (reblocks/hooks:on-application-hook-render collect-sql-queries-on-render (app) (let* ((*collect-sql* t) (resulting-html (reblocks/hooks:call-next-hook))) (inject-debug-panel resulting-html)))
As you can see, we've added a hook using
call-next-hook function inside it's body. This is very similar
to generic functions, but instead of methods, we are calling nested hooks. Hooks added
later wraps hooks added earlier.
If you don't call
call-next-hook function explicitly, it will be called implicitly
after your hook's body. This makes your code called before hook's event.
Defining your own hooks
You can use hooks mechanics in your own code by defining and calling hooks.
For example, you creating a Reblocks extension which allows users to register on a website. You also want to allow develope who will use your extension to define additional actions when a new user get registered on site. For example, admin might want to validate age and also to send user a greeting email after registration.
To accomplish this task, we need to define a hook:
(reblocks/hooks:defhook create-user (age name email) "Called around the code which creates a new user. Returns a user object.")
This macro will define more macros inside
Now, you need to use
WITH-CREATE-USER-HOOK macro to wrap around
code creating the user. In some cases, when you just want to call hooks
you can use
CALL-…-HOOK version which is equal to calling
without any body.
Here is how you can use it now:
(defun process-new-user-form (age name email) (reblocks/hooks:with-create-user-hook (age name email) ;; For simplicity, we using a plist here. ;; A real application should store the record ;; in a database and return class instance. (list :age age :name name :email email)))
When we call this function, it will return our plist:
REBLOCKS/DOC/HOOKS> (process-new-user-form 42 "Sasha" "firstname.lastname@example.org") (:AGE 42 :NAME "Sasha" :EMAIL "email@example.com")
Now let's add a hook:
(reblocks/hooks:on-application-hook-create-user check-age (age name email) (format t "Checking age of a new user"))
and create a new user
REBLOCKS/DOC/HOOKS> (process-new-user-form 9 "Ilia" "firstname.lastname@example.org") Checking age of a new user (:AGE 9 :NAME "Ilia" :EMAIL "email@example.com")