Building a blog with Grails - tutorial #7

It's been a busy week. Last night and today I was finally able to sit down and get the next installment in the "Building a blog with Grails" tutorial series.

In the last installment we created a form to facilitate creating posts. Instead of using a regular, old textarea tag we installed the Grails RichUI plugin. Not only will it give us a WYSIWYG editor for creating posts, it also gives a more visually appealing date selector.

In this tutorial we'll build on the create_post action we started and use a service to communicate with the database and encapsulate the logic used to create posts.

First off, I need to correct a couple of errors in the last tutorial. Our form will not pass all the necessary parameters. This is for two reasons:

  1. The form tag is not correctly positioned within the page.
  2. Not all the input tags are present.
So, this is what the form should look like:

<html>
    <head>
        <title>
            <g:if test="${blog}">${blog?.name}</g:if>
        </title>
        <meta name="layout" content="admin" />
        <resource:dateChooser />
        <resource:richTextEditor />
    </head>
    <body>

        <g:form method="post" action="create_post">

        <div id="alpha">
            <h1>Admin</h1>

              <g:hasErrors bean="${post}">
                <div class="errors">
                  <g:renderErrors bean="${post}" as="list" />
                </div>
              </g:hasErrors>

                <table>
                    <tr>
                        <td>
                            <label for="title">Post Title:</label>
                        </td>
                        <td>
                            <g:textField size="50" id="title" name="title"
                                 value="${post?.title}" />
                        </td>
                    </tr>

                    <tr>
                        <td>
                            <label for="title">Post Date</label>
                        </td>
                        <td>
                            <richui:dateChooser hourStyle="width:30px;" minuteStyle="width:30px;" name="publishDate"
                            time="true" format="MM/dd/yyyy" value="${post?.publishDate}" />
                        </td>
                    </tr>

                    <tr>
                        <td colspan="2">
                            <richui:richTextEditor name="body"
                               value="${post?.body}" width="575" />
                        </td>
                    </tr>

                    <tr>
                        <td>
                            <label for="tag_list">Tags:</label>
                        </td>
                        <td>
                            <g:textField id="tag_list" name="tag_list"
                                 value="${post?.tag_list}" />
                        </td>
                    </tr>

                    <tr>
                        <td>
                            <label for="keyword_list">Keywords:</label>
                        </td>
                        <td>
                            <g:textField id="keyword_list" name="keyword_list"
                                 value="${post?.keyword_list}" />
                        </td>
                    </tr>

                    <tr>
                        <td colspan="2">
                            <g:submitButton name="edit" value="Create!" />
                        </td>
                    </tr>

               
                </table>
           

        </div>

        <div id="beta">
            <h3>Categories</h3>
            <g:select name="subject"
          from="${Subject.list()}"
          value="${id}"
          optionKey="id" />

            <br clear="all" />

            <h3>Status</h3>
            <g:select name="status"
                      from="${['P', 'R', 'U']}"
                      valueMessagePrefix="post.status" />
            <br clear="all" />

            <span>Accept Comments:</span>
            <g:checkBox name="acceptComments" />
            <br clear="all" />

            <span>Accept Trackbacks:</span>
            <g:checkBox name="acceptTrackbacks" />

        </div>

        </g:form>

    </body>
</html>


Basically what I did to correct the problem was reposition the form tag so that it encloses everything within the body tags and I added an additional field so that the user can enter the keywords associated with a post. In order to facilitate this we also need to add a transient variable to the Post domain class:

static transients = ['tag_list','keyword_list']
String tag_list
String keyword_list


Remember that this creates a variable accessible through the domain object but not persisted to the database.

Also, you'll see this added to the create_post view:

<g:hasErrors bean="${post}">
    <div class="errors">
        <g:renderErrors bean="${post}" as="list" />
    </div>
</g:hasErrors>


That code is for the purpose of rendering any errors that may have occurred during the Post creation.

On to the rest of the tutorial . . .

First off, let's create a service that will contain the logic for creating new posts. Right click on "Services" -> "New" -> "Grails Service.":



In the dialog, name your service, "Post." It will actually be called, "PostService.groovy," but that is taken care of for us behind the scenes. Then click, "Finish." ( The dialog below displays the fact that "Post" already exists because I created the "Post" service prior to taking the screenshot. )



Our service will start out pretty basic:

class PostService {

    boolean transactional = true

    def createNewPost( params, user ) {

    }

}


We'll add the above method, createNewPost, to our service. The two parameters, params and user will all be necessary when we create a new Post. We'll come back to the service later.

Last time we started a create_post action in our AdminController. It was pretty basic, but this is what we'll want it to look like so we can create new Posts:

def create_post = {
        if( request.post ){
            def user = User.get( session.user.id )           
           
            def post = postService.createNewPost(params, user)

            if( post.hasErrors() ){
                flash.message = 'There are errors'
                flash.post = post
                render(view:'create_post',model:[post:post])
            }
            else {
               
flash.message = 'The Post was added successfully'
                redirect(controller:'admin', action:'index')
            }
           
        }
    }


Notice that there is a variable that will be assigned to the value of the newly created Post as a result of the createNewPost method of our PostService. We'll need a reference to this service in our controller. Outside of this method, at the top of your controller, you'll need the following:

def postService

Grails will automatically know what's going on when the createNewPost method is called and execute the code in the service.

In the action, any request sent via POST will be handled like so:

First, the most recent User will be fetched from the database. We could use the one in the session, but we never know - there could have been changes to that object. If the Post we receive from the PostService has any errors we set a flash message and send the post object along when the create_post view is rendered so that the User can correct any problems that may have occurred. Otherwise, if there are no errors, the User will be re-directed to the admin index action - along with a message to indicate things were successful.

You might wonder why the logic of creating a new Post is being handed off to a "service." There's a couple good reasons:

  1. Our controllers are not where the "business logic" should reside. I know that's kind of an idealogical reason, but our controllers are meant to handle requests, redirects and forwards - essentially they are traffic cops routing traffic accordingly.
  2. Although this will most likely not be the case with this application, if there were many different paths to creating a new Post, we would have to write the same logic over and over if the business logic were contained in controllers. By using a service the logic is encapsulated in one place and any changes will only have to made there.
So here's what our PostService looks like:

class PostService {

    boolean transactional = true

    def createNewPost(params, user) {
        def blog = Blog.get(1)
        def post = new Post()
        post.properties = params
        post.extendedBody = ''

        def title = post.title       
        post.slug = title.replaceAll('[^A-Za-z0-9\\s]','')
        post.slug = post.slug.replaceAll('\\s','-')
       
        post.author = user       
        post.status = params.status
       
        def tag_list = params.tag_list.split(',')
        def tags = []
        tag_list.each {
            def tag
            tag = Tag.find( "from Tag as t where t.name = ?", [it] )
            if( !tag ){
                tag = new Tag( name: it )
            }
            tags.add( tag )
        }

        def keyword_list = params.keyword_list.split(',')
        def keywords = []
        keyword_list.each {
            def keyword
            keyword = Keyword.find( "from Keyword as k where k.name = ?", [it] )
            if( !keyword ){
                keyword = new Keyword(name:it)
            }
            keywords.add( keyword )
        }

        def subject = Subject.get( params.subject )       
       
        if( post.validate() ){
           
            post.addToSubjects( subject )

            tags.each {
                def t = it.save()
                post.addToTags( t )
            }

            keywords.each {
                def k = it.save()
                post.addToKeywords( k )
            }

            post = post.save()
            user.addToPosts( post )
            blog.addToPosts( post )
           
        }
       
        return post
    }

}


Let's discuss the service. The first thing I do is get the most current data regarding the Blog. Of course, since there's only one, it's easy to find. Then a new Post object is created and most of the variables are populated from the parameters passed when the form was submitted. There are a few exceptions to that, however:

  1. Right now, I'm setting the extended body to an empty string. There's something more that I want to do with that in terms of the UI. When that's done we'll set that appropriately.
  2. The post title is going to be used when we populate the post slug. If you don't know what a slug is, it is essentially a formulation of the post title without any special characters or whitespace. The naturally occurring whitespace ( the space between words ) will be filled with dashes ( - ). This way, we'll be able to create some pretty URLs.
  3. We'll have to create Tag and Keyword objects using the tag_list and keyword_list parameters passed to us.
To create the slugs I uses the Java, replaceAll, function to essentially replace any character that doesn't happen to be alphanumeric or whitespace. I use it again to replace any whitespace with a dash.

Then, I use another Java function, split, to gather each of the tagwords and keywords. As I iterate through the list of tagwords I check to see if there has ever been a tag created by the same name. If that is false, I go ahead and create one ( but I don't save to to the database just yet ) and then add it to a list. You'll see that this is done for the keywords as well. Last, I grab a Subject object using the subject parameter and populate that Post variable.

Now that the Post object is fully populated ( or at least I think it is ) I call the validate() method. This method will let me know if the Post object passes validation that I have set up within the Post domain class. Since we really haven't set up any validation whatsoever, it will pass. However, let's set up a minor validation that we probably should do.

Our Post wouldn't be much good without a title, right? Actually, if we don't have a title we can't even produce a slug. So let's let GORM know that our Post must have a title. In our Post domain class, we'll add the following:

static constraints = {
        title(blank:false,nullable:false)
    }


We have told GORM that our title can neither be blank nor false. There's one more step to this, however. Unless you're particularly fond of default error messages:

default.blank.message=Property [{0}] of class [{1}] cannot be blank

you will want to add something to your messages.properties file:

post.title.blank.error=A Post must have a title.

That way, when the User forgets to add a title to their Post they'll get a friendly reminder that Blog Posts always have titles. In your CSS file you might add something like this so the errors are readily apparent:

.errors li {
    color: red;
}


You might want to do one more thing. Right now there's no easy way to get to our create_post action once we've logged in. Add the following to your admin layout:

<li><a href="${createLink(controller:'admin', action:'create_post')}">create</a></li>

Now, once you've logged in, you're just a click away from entering you're own posts. With this completed we can now run GroovyBlog and see how things have worked. Start up your app and log in to the admin section:



Of course, at this point you should still only be seeing the one post that is currently being loaded via bootstrap unless you've added any others to your bootstrap routine. Now click on the "create" link that we added to our admin template a few minutes ago. However, when the form is filled out go ahead and leave the title blank so we can see how our application reacts to that:



When we try to submit that form, the createNewPost method should return a Post that has errors. When our controller tests to see if the Post has errors it should render the create_post view and display the errors:



Now when we enter a title, not only should our data be saved to the database, we should also see that a decent slug was created for our entry. I'm using the title: "Th$$is is a @totally cool ##test post 999%&%"



Our app, upon successful entry of a new Post, should redirect us to the admin index page which displays all entries to date:



As you can see, the Post was saved and upon redirection it is displayed ( the title and the other information we asked to be shown ) as it should. Did the slug get created as it should? Using our connection to MySql, we could always query the database easily and find out since we are not currently displaying that data on our admin page:



Sure enough. Things are working pretty good. But does the Post display on the main page?



There's still other things that we might want to think about for future reference, however. What about the Tags and Keywords? Will we want to run them through the same kind of filtering to prevent non-alphanumerics?

Currently, we're only using validation on the title. Should there be more validation? How much do we want to trust our users? Leave your recommendations, thoughts, etc. in the comments.

Feel free to move on to the next tutorial.

That concludes this tutorial. I haven't any plans for the next installment. It's possible that it will be over editing the Post we just created. Until then . . .
OpenID accepted here Learn more about OpenID

About this Entry

This page contains a single entry by Jim published on April 25, 2009 1:48 PM.

Friday Links was the previous entry in this blog.

I'm testing this new Plug-In is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.