1

I have many routes in my app that get information from a database. In one particular situation, I am getting HTML content from a database to render it with cl-who.

I am not sure why the content from the DB will not render. The rest of the page works fine.

When I test adding html into the route manually (hard coded html tags) it works fine. In the case when I use the (second (get-content)) (see below) the html from the DB is not added to the template.

What am I missing?

Database macro

(defmacro access-db(db &body query)
  `(progn
     (clsql:connect ,db)
     (unwind-protect (progn ,@query)
      (disconnect-db ,db)))) 

Page Macro

(defmacro defpage ((&key title) &body content)
  `(cl-who:with-html-output-to-string 
       (*standard-output* nil :prologue t :indent t)
     (:html
      :xmlns "http://www.w3.org/1999/xhtml"
      :xml\:lang "en"
      :lang "en"
      (:head
       (:meta
    :http-equiv "Content-Type"
    :content "text/html;charset=utf-8")
       (:title
    ,(format nil "~A" title))
       (:link :type "text/css" 
          :rel "stylesheet"
          :href "/styles.css"))
      (:body :class "whole-page" 
         (:div :class "site-articles"
           (:div :class "article-body"
             (:div :class "on-page-title"
                   (:h1 :class "main-header" ,title))
             ,@content))))))

Hunchentoot route

(define-easy-handler (test-page :uri "/wow") ()
  (let ((content
      (bt:make-thread
       (lambda ()
         (second (get-content))))))
    (defpage (:title "Main One") 
      (bt:join-thread content))))

get-content uses access-db function and returns a list the first item from the database. A title and HTML. The html is second in that list.

Get content

(defun get-content ()
  (let ((blogs
      (access-db *PSQL-CONNECT-INFO*
        (clsql:query
         "select * from content"))))
    (first blogs)))

Vinn
  • 1,030
  • 5
  • 13
  • What is `get-content`, what does it return, and why are you calling `second` on the value returned by `(get-content)`? – ad absurdum Aug 19 '23 at 18:26
  • I edited my question for clarity with `get-content` – Vinn Aug 19 '23 at 23:58
  • But what does `(second get-content))` return, and what is the result of `defpage`? (I don't know cl-who but seeing the defpage macro I suspect an issue around giving it some text or some s-expressions) – Ehvince Aug 20 '23 at 10:47
  • `(second get-content))` returns a string from the database. somthing like `"This is text"` – Vinn Aug 20 '23 at 10:54
  • I noticed that this `(cl-who:with-html-output-to-string (*standard-output*) (second (get-content)))` returns a blank string. The function is not being evaluated. – Vinn Aug 20 '23 at 11:11

1 Answers1

1

[Disclaimer: I can't get CLSQL to work and anyway don't want anything to do with databases, so this answer is about the problems with the other things.]

I think you may probably be confused about macros in general, but certainly your defpage macro is confused.

What CL-WHO's with-html-output does is to bind a stream to a variable to which you can print, and then treat its body as the implicit 'html-lisp' language that it and all similar macros (I think HTOUT was the first that really did this) understand. That means that, if you want to send output into this stream you need to print it to that stream. with-html-output-to-string just captures that output into a string in the obvious way.

Here is a cleaned-up version of that macro with a better name (defpage smells to me like 'define page' which is not at all what it does).

(defmacro with-output-to-standard-page ((&key (title "page")
                                              (stream '*standard-output*)
                                              (string-form 'nil))
                                        &body content)
  (let ((tv (make-symbol "TITLE")))
    `(let ((,tv ,title))
       (with-html-output-to-string (,var ,string-form :prologue t :indent t)
         (:html
          :xmlns "http://www.w3.org/1999/xhtml"
          :xml\:lang "en"
          :lang "en"
          (:head
           (:meta
            :http-equiv "Content-Type"
            :content "text/html;charset=utf-8")
           (:title (princ ,tv ,var))
           (:link :type "text/css" 
            :rel "stylesheet"
            :href "/styles.css"))
          (:body :class "whole-page" 
           (:div :class "site-articles"
            (:div :class "article-body"
             (:div :class "on-page-title"
              (:h1 :class "main-header"
               (princ ,tv ,var)))
             ,@content))))))))

This has behaviour which is both more conventional than yours:

(let ((title "foo"))
  (with-output-to-standard-page (:title title)
    ...))

will work, and

(with-output-to-standard-page (:title (complicated-function-with-state))
  ...)

will call complicated-function-with-state just once.

Additionally it lets you define what the stream variable and the string form are, if you want:

(with-output-to-standard-page (:var s)
  (princ "foo" s))

will work.

Finally, it puts the content underneath the initial h1, not in it (and outside the on-page-title div in fact), and uses the title for the h1.

All this is also done in a package which uses CL-WHO, HUNCHENTOOT and BORDEAUX-THREADS to avoid the awful package-prefix-everwhere horror and make the code readable.

So then if we replace your get-content function by a shim because no SQL:

(defun get-content ()
  (list 'nothing "my page content"))

Then, correcting your handler to print to the stream, which is the crucial problem

(define-easy-handler (test-page :uri "/wow") ()
  (let ((content-thread
         (make-thread
          (lambda ()
            (second (get-content))))))
    (with-output-to-standard-page (:var p :title "Main One") 
      (princ (join-thread content-thread) p))))

Everything will now work.


Looking at your access-db macro there are also, almost certainly, problems with it.

First of all I'll call it something like with-db-access because with-* is one conventional name for macros like this (another possibility might be accessing-db). Then it has, at least, the problem that it will multiply evaluate its first argument. It should instead be something like

(defmacro with-db-access (db &body query)
  (let ((dbv (make-symbol "DB")))
    `(let ((,dbv ,db))
       (connect ,dbv)
       (unwind-protect
           (progn ,@query)
         (disconnect-db ,dbv)))))

to avoid that problem.

Note that this very common pattern in macros (and the corresponding very common problem when people forget this) can be made a lot simpler using Tim Bradshaw's metatronic macros. Using that, this would be:

(defmacro/m with-db-access (db &body query)
  `(let ((<db> ,db))
     (connect <db>)
     (unwind-protect
         (progn ,@query)
       (disconnect-db <db>))))

My with-output-to-standard-page macro above would be similarly simplified using metatronic macros.

ignis volens
  • 7,040
  • 2
  • 12
  • Site note: If you do not deal with databases how do you manage long term persistance? A genuine question. – Vinn Aug 21 '23 at 09:51
  • A question before I test this, why does the first arguement get evaluated multiple times in my `access-db` macro? – Vinn Aug 21 '23 at 10:28
  • I'm generally with jwz on databases. It should be obvious why it multiply evaluates the first argument: just consider the expansion: `(access-db (compute-my-db)) -> (progn (connect (compute-my-db)) (unwind-protect (progn) (disconnect-db (compute-my-db))))`. – ignis volens Aug 21 '23 at 12:19
  • `db` in the macro accepts a list of strings that is the PSQL connection information. The function `connect` takes that list and connects to the db. Then disconnects with that same list. There is no `compute`, just a list. It works fine. – Vinn Aug 21 '23 at 12:59
  • 1
    `db` is *not* a list: it is a form (a bit of source code) which, when evaluated after the macro is expanded will produce something (which is probably a list of strings in your case). *Your macro evaluates that form multiple times*. If it's a literal, or if it's a variable then you're OK. If not, not. It's up to you: you can write unsafe macros which happen to work some of the time, or you can write safe macros. I don't mind which, but I'm not going leave unsafe macros uncorrected in answers here. – ignis volens Aug 21 '23 at 14:06
  • It is indeed a literal. I believe your answer is still correct because my question didnt make clear what `db` was. – Vinn Aug 21 '23 at 15:20
  • It's correct because programs, and especially programming languages, which is what you are building when writing macros, should be made of constructs which are robust and as safe as possible, not ones which are fragile and unsafe. – ignis volens Aug 23 '23 at 08:02
  • What makes the macro unsafe if the code I described takes a literal? – Vinn Aug 23 '23 at 08:05
  • I don't think I can help you if you're asking this – ignis volens Aug 23 '23 at 08:09
  • Im not asking for additional help. Only for you to clarify your previous statements. – Vinn Aug 23 '23 at 08:10