Implementing a blog in Common Lisp: Part 2

by Vetle Roeim - last updated 2009-01-15

Table of contents

  1. Introduction
  2. Revamping the model
  3. Showing blog posts on separate pages
  4. Linking from the front page
  5. Editing blog posts
  6. End of part 2

Introduction

This is the continuation of a tutorial showing how a blog can easily be implemented in Common Lisp. This is part 2 of the tutorial, part 1 should be read first.

Here we expand the code from the first part of the tutorial with functionality for showing each blog post on a separate page, and an interface for editing them. For your convenience, the source code of the files created in this tutorial can be downloaded (note that this is the final code for this part of the tutorial, and that it goes through many steps).

If you at any point get some unexplainable internal server error when testing the web server, you can try enabling logging to file by writing (setf (hunchentoot:log-file) "/tmp/error.log"). For more info, see the Hunchentoot documentation.

Update: Part 3 of this tutorial is now available.

Revamping the model

First off, to be able to show each blog post on a separate page, we will need to be able to uniquely identify each individual blog post. In our current model there is no attribute in the blog-post class that we can use for this.

It is common in many blogs to generate a unique URL from the title of the blog post, and we can use that to uniquely identify the post in our code as well. First, we make a function to generate a URL part from a title. Allowed characters are a - z, any digits, and dashes. Spaces will be replaced with dashes before filtering, and all other characters removed.

(defun make-url-part (title)
  "Generate a url-part from a title. A url-part should only have
alphanumeric characters or dashes (in place of spaces)."

  (string-downcase
   (delete-if #'(lambda (x) (not (or (alphanumericp x) (char= #\- x))))
              (substitute #\- #\Space title)
)
)
)

Now we can add a url-part attribute to our model, and we also need to make sure that Elephant indexes our model so that we can create queries that fetch instances of this class from the store.

(defpclass blog-post ()
  ((title :initarg :title
          :accessor title
)

   (body :initarg :body
         :accessor body
)

   (timestamp :initarg :timestamp
              :accessor timestamp
              :initform (get-universal-time)
              :index t
)

   (url-part :initarg :url-part
             :accessor url-part
             :initform nil
             :index t
)
)
)

As you can see, we have defined a new attribute, and it is initialized to nil if it is not specified when the blog post is created. Since it is generated from the title, we can generate it automatically when the blog post is created, by writing a method that runs after initialize-instance, which is run by make-instance. For more details on object orientation in Common Lisp, see Practical Common Lisp, Chapter 17.

(defmethod initialize-instance :after ((obj blog-post) &key)
  "If :url-part wasn't non-nil when making the instance, generate it
automatically."

  (cond ((eq nil (url-part obj))
         (setf (url-part obj) (make-url-part (title obj)))
)
)
)

Unfortunately, previously created posts are not automatically added to indexes, so we would have to do that manually. Since we have only created two posts so far, the easiest thing is to simply re-create them. Adding them to the right indexes is left as an exercise to the reader. Simply run the calls to make-instance from part 1 of the tutorial.

MY-BLOG>  (make-instance 'blog-post :title "Hello blog world"
                                       :body "First post!")
#<BLOG-POST oid:29>
MY-BLOG> (make-instance 'blog-post :title "This is fun"
                                      :body "Common Lisp is easy!")
#<BLOG-POST oid:30>
MY-BLOG>

Because they are indexed, we no longer need to maintain a list of posts separatly, so we can get rid of the *blog-posts* variable. With indexing turned on we can query the Elephant store directly instead.

MY-BLOG> (drop-pobject *blog-posts*) ; Remove the list of blog posts from the store
NIL

We of course remove *blog-posts* from our source code as well. Next we will have to rewrite the code that used that variable to query the store instead. This can be done with functions such as get-instances-by-class, get-instances-by-value and get-instances-by-range (see the Elephant API documentation for more information). To get a list of blog posts ordered by timestamp, we will use the latter function.

The arguments to get-instances-by-range are the class we want to get instances of, the attribute we want to sort on, and two values indicating a range. If the last two parameters are both nil, all instances will be returned, ordered by the ordering specified by the index.

In generate-index-page we can now replace (pset-list *blog-posts*) with (nreverse (get-instances-by-range 'blog-post 'timestamp nil nil)) (reverse to list posts by newest first).

(defun generate-index-page ()
  "Generate the index page showing all the blog posts."
  (with-output-to-string (stream)
    (html-template:fill-and-print-template
     #P"index.tmpl"
     (list :blog-posts
           (loop for blog-post in (nreverse (get-instances-by-range 'blog-post 'timestamp nil nil))
              collect (list :title (title blog-post) :body (body blog-post))
)
)

     :stream stream
)
)
)

Showing blog post on separate pages

With all this in place, we can then create the functionality for showing blog posts in their own pages. First, let's create a template, post.tmpl, that we will use for this.

<!DOCTYPE html>
<html>
<head>
  <title><!-- tmpl_var title --> - My Blog</title>
  <style type="text/css">
body {
    margin-left: 10%;
    margin-right: 10%;
    background: white;
    color: black;
}

h1 {
    font-variant: small-caps;
    border-bottom: 2px solid black;
    color: darkblue;
}

h2 {
    font-variant: small-caps;
    color: darkblue;
}
  </style>
</head>
<body>
  <h1>* <!-- tmpl_var title --></h1>
  <div>
    <!-- tmpl_var body -->
  </div>
</body>
</html>

Next, we will create a function generate-blog-post-page that accepts a url-part uniquely identifying the blog post (as discussed above), and generates the HTML based on the template and the blog post object.

(defun generate-blog-post-page (url-part)
  (with-output-to-string (stream) ; Create a stream that will give us a sting
   (let ((blog-post (get-instance-by-value 'blog-post 'url-part url-part))) ; Get the blog post we're interested in
     (html-template:fill-and-print-template
       #P"post.tmpl"
       (list :title (title blog-post)
             :body (body blog-post)
)

       :stream stream
)
)
)
)

Let's test it!

MY-BLOG> (generate-blog-post-page "another-blog-post")
WARNING:
   New template printer for #P"/Users/vetler/Documents/devel/cl-webapp-intro/source/post.tmpl" created
"<!DOCTYPE html>
<html>
<head>
  <title>Another blog post - My Blog</title>
  <link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"/>
</head>
<body>
  <h1>Another blog post</h1>
  <div>
    Common Lisp isn't so hard
  </div>
</body>
</html>
"
MY-BLOG> 

Now we need to make the web server call this function when it gets a request for a blog post page. We can do that by modifying the dispatch table, and creating a regex dispatcher that sends all requests matching a particular regular expression to our function.

We will also have to change the previously defined dispatcher to only match the single slash. If we kept it as it is now, any URL would show the index page.

(setq hunchentoot:*dispatch-table*
      (list (hunchentoot:create-regex-dispatcher "^/$" 'generate-index-page)
            (hunchentoot:create-regex-dispatcher "^/view/$"
                                                 'generate-blog-post-page
)
)
)

This will result in generate-blog-post-page being called if we go to http://localhost:8080/view/. The blog post to be viewed will be specified in the query string, i.e. http://localhost:8080/view/?hello-blog-world.

Unfortunately we will have to change generate-blog-post-page to read the query string before fetching the page from the store. We will simply wrap the existing function body with a let macro.

(defun generate-blog-post-page ()
  (let ((url-part (hunchentoot:query-string)))
    (with-output-to-string (stream) ; Create a stream that will give us a sting
     (let ((blog-post (get-instance-by-value 'blog-post 'url-part url-part))) ; Get the right blog post
       (html-template:fill-and-print-template
         #P"post.tmpl"
         (list :title (title blog-post)
               :body (body blog-post)
)

         :stream stream
)
)
)
)
)

The blog posts are now available on separate URLs, for instance http://localhost:8080/view/?hello-blog-world [screenshot].

Linking from the front page

The functionality implemented above is of course of no use if no one can find our pages, so we will add links from the front page. First, the function that generates the index page will have to be changed to also send the url-part attribute to the template. We simply add url-part to the list of parameters sent to the template in generate-blog-post-page.

(list :title (title blog-post)
      :body (body blog-post)
      :url-part (url-part blog-post)
)

Next, the template will be changed so that the blog post title is a link to the blog post page.

<h2>* <a href="/view/?<!-- tmpl_var url-part -->"><!-- tmpl_var title --></a></h2>

The front page now both lists the blog posts and links to the separate pages [screenshot].

Editing blog posts

Finally, we will create a page for editing blog posts, and functionality for saving edits. First, we create a template with a form for editing the blog posts.

<!DOCTYPE html>
<html>
<head>
  <title><!-- tmpl_var title --> - My Blog</title>
  <style type="text/css">
body {
    margin-left: 10%;
    margin-right: 10%;
    background: white;
    color: black;
}

h1 {
    font-variant: small-caps;
    border-bottom: 2px solid black;
    color: darkblue;
}

h2 {
    font-variant: small-caps;
    color: darkblue;
}

textarea {
  width: 30em; height: 20em;"
}
  </style>
</head>
<body>
  <h1>Edit blog post</h1>
  <form action="?<!-- tmpl_var url-part -->" method="POST">
    <h2>Title</h2>
    <input style="width: 20em;" type="text" name="title" value="<!-- tmpl_var title -->"/>
    
    <h2>Body</h2>
    <textarea name="body"><!-- tmpl_var body --></textarea>

    <input style="display: block" type="submit"/>
  </form>
</body>
</html>

Next, we must make the web server serve this page. We happen to have a function that does a lot of what we need to do - fetch the right blog post instance and call fill-and-print-template. The function we use for generating blog posts pages, generate-blog-post-page, does exactly this, but the template name is hardcoded into the function.

To avoid duplicating the function, we can make it take the template filename as a parameter, but if we do that then we will have to create some additional functions that the web server can use to generate the right page with the right template.

(defun generate-blog-post-page (template)
  ...
)
; As before, except template filename replaced with the function parameter

(defun view-blog-post-page ()
  "Generate a page for viewing a blog post."
  (generate-blog-post-page #P"post.tmpl")
)


(defun edit-blog-post ()
  "Generate a page for editing a blog post."
  (generate-blog-post-page #P"post-edit.tmpl")
)


; Update the dispatch table
(setq hunchentoot:*dispatch-table*
      (list (hunchentoot:create-regex-dispatcher "^/$" 'generate-index-page)
            (hunchentoot:create-regex-dispatcher "^/view/$"
                                                 'view-blog-post-page
)

            (hunchentoot:create-regex-dispatcher "^/edit/$"
                                                 'edit-blog-post
)
)
)

Now http://localhost:8080/edit/?hello-blog-world [screenshot] will show a form for editing the blog post.

Finally we will need to handle what happens when the user submits the form. The current form will submit its data to the same URL that it uses for generating the blog post, but use a POST request instead of GET. First, let's change edit-blog-post to handle differently if the request is a POST request.

(defun edit-blog-post ()
  (cond ((eq (hunchentoot:request-method) :GET)
         (generate-blog-post-page #P"post-edit.tmpl")
)

        ((eq (hunchentoot:request-method) :POST)
         (save-blog-post)
)
)
)

Then we just have to define save-blog-post.

(defun save-blog-post ()
  "Read POST data and modify blog post."
  (let ((blog-post
         (get-instance-by-value 'blog-post
                                'url-part (hunchentoot:query-string)
)
)
)

    (setf (title blog-post) (hunchentoot:post-parameter "title"))
    (setf (body blog-post) (hunchentoot:post-parameter "body"))
    (setf (url-part blog-post) (make-url-part (title blog-post)))
    (hunchentoot:redirect (url-part blog-post))
)
)

This function first gets the blog post we're editing from the store, using the old url-part from the query string. Then it is just a matter of getting the post parameters and setting the attributes in the blog post instance. Finally a new url-part is generated and the user redirected to the newly edited blog post.

End of part 2

The basic example from the first part has now been extended with a little more functionality, but there is still a lot missing and it has a lot of flaws to be a proper web application. However, this should be enough to give you an overview of the basics and get you started.

Stay tuned for part 3!