Hooks

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:

  • application;

  • session;

  • request.

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.

Accepts Lack's HTTP request environment.

Should return a list of three items:

  • HTTP status code;

  • a plist of HTTP headers;

  • 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.

hook
app action-name action-arguments

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. Should return HTML string.

Called when session is resetted for some reason.

How to use these hooks?

Lets pretend we want to create an extension which will collect all SQL queries and render them in hidden panel like Django Debug Toolbar does? To accomplish this task, we need to add an application-wide render hook:

(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 ON-APPLICATION-HOOK-RENDER macro and used 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.

First, you need to define a hook using defhook macro. Then choose piece of code you want to make hookable and wrap it with another macro, defined by defhook.

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 REBLOCKS/HOOKS package:

  • ON-SESSION-HOOK-CREATE-USER

  • ON-REQUEST-HOOK-CREATE-USER

  • ON-APPLICATION-HOOK-CREATE-USER

  • WITH-CREATE-USER-HOOK

  • CALL-CREATE-USER-HOOK

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 WITH-…-HOOK 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" "sasha@40ants.com")
(:AGE 42 :NAME "Sasha" :EMAIL "sasha@40ants.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" "ilia@40ants.com")
Checking age of a new user
(:AGE 9 :NAME "Ilia" :EMAIL "ilia@40ants.com")

Hooks API

macro
name (&rest args) &optional docstring

Registers a hook

This function should be called inside a body, surrounded by ON-APPLICATION-HOOK-…, ON-SESSION-HOOK-… or a ON-REQUEST-HOOK-… type of macro.