Play Framework example: A controller action that handles a SQL exception, creates a new Form, and has custom error messages

If you need to handle a unique constraint in a database table field when writing an add or edit process in a Play Framework application, I hope this example will be helpful. In the end I’ll show:

  • How to write a Play Anorm query that performs a SQL INSERT on a database field that has a unique constraint
  • How to write a Play controller method (Action) that handles that SQL exception
  • How to create a new Form after you catch that exception
  • How to add custom error messages to that form
  • How to redirect control back to your Play template

If you need to see any of those things, I hope the following example is helpful.

The database method

In my case I’m writing a URL-shortening application and I have a unique constraint on a table column named short_uri, so I wrote my database-insert method with Anorm to look like this:

// uses Try b/c `short_uri` has a unique constraint, which can cause an exception
def insertNewUrl(u: Url): Try[Option[Long]] = db.withConnection { implicit c =>
    Try {
        val q = SQL"""
        insert into urls (user_id,long_url,short_uri,notes)
        values (1,${u.longUrl},${u.shortUrl},${u.notes})
    """
        q.executeInsert()  //returns the primary key as an Option
    }
}

I catch the possible unique constraint exception with Try. Because executeInsert returns the primary key inside an Option, this method returns the type Try[Option[Long]]. The key here for the purpose of this example is that the SQL INSERT can fail because of the unique constraint, so I handle the possible exception with Try.

The controller method/action

For me, the harder part in this case is to know what to do in your controller action code. I’m not going to try to explain it all, I’m just going to show my code, and then offer a little explanation:

def handleAddFormSubmission = authenticatedUserAction { implicit request =>
    val formDidNotValidateFunction = { formWithErrors: Form[Url] =>
        BadRequest(views.html.editUrl(0, formWithErrors, addFormSubmitUrl))
    }
    val formValidatedFunction = { data: Url =>
        // form data is valid, try saving to the database
        val url = Url(
            data.id,
            data.longUrl,
            data.shortUrl,
            data.notes
        )
        // there’s a Try here b/c a SQL INSERT can fail due to database constraints
        val pkInOptionInTry: Try[Option[Long]] = urlDao.insertNewUrl(url)
        pkInOptionInTry match {
            case Success(maybePk) => {
                // no exception was throw, so `maybePk` probably has the primary key
                Redirect(routes.UrlAdminController.list())
                    .flashing("info" -> s"URL '${data.shortUrl}' was added.")
            }
            case Failure(e) => {
                // an exception was thrown, so the SQL INSERT failed, probably a duplicate key error
                val formBuiltFromRequest: Form[Url] = form.bindFromRequest()
                val newForm = formBuiltFromRequest.copy(
                    errors = formBuiltFromRequest.errors ++ Seq(
                        FormError("short_uri", "Probably have a duplicate URI in the database."),
                        FormError("short_uri", e.getMessage)
                    )
                )
                BadRequest(views.html.editUrl(0, newForm, addFormSubmitUrl))
            }
        }
    }
    val formValidationResult: Form[Url] = form.bindFromRequest
    formValidationResult.fold(
        formDidNotValidateFunction,
        formValidatedFunction
    )
}

One big key in this method is knowing that you can use Try to easily handle the exception that can be returned by the database access method.

Creating a new Play Form after the error

A second big key is knowing that you can rebuild a Form with either of these two approaches:

val newForm: Form[Url] = form.bindFromRequest()
val newForm: Form[Url] = form.fill(data)

I haven’t looked into the differences between those two possible approaches, but it took me a while to figure out those approaches, so hopefully that knowledge will save someone else time in the future. The key here is that you need to build a new form after the exception that you can pass to your template, and this is how you do that.

Adding custom error messages to the form

Another big key was find out that methods like form.withError(...) and form.withGlobalError(...) don’t work as expected, so I had to write code like this to get my custom error messages to show up in the Play template:

val newForm = formBuiltFromRequest.copy(
    errors = formBuiltFromRequest.errors ++ Seq(
        FormError("short_uri", "Probably have a duplicate URI in the database."),
        FormError("short_uri", e.getMessage)
    )
)

You probably only need one error message there, but since I’m writing this application for myself I don’t mind seeing the output from e.getMessage. Note that this approach binds those error messages to the short_uri field in the template.

The Play template

You might not need to see the Play Framework template to understand what I just showed, but it may help to see the template code, so here it is:

@(
    urlId: Long,
    form: Form[Url],
    postUrl: Call
)(implicit request: RequestHeader, messagesProvider: MessagesProvider)

<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
    <link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/admin.css")">
</head>

<body id="edit-url">
<div id="content">

    <div id="edit-url-form">

    <h1>URL Shortener</h1>

    @* Flash shows updates to a page *@
    @request.flash.data.map{ case (name, value) =>
        <div>@name: @value</div>
    }

    @* Global errors are not tied to any particular form field *@
    @if(form.hasGlobalErrors) {
        @form.globalErrors.map { error: FormError =>
            <div>
                Error: @error.key: @error.message
            </div>
        }
    }

    @helper.form(postUrl, 'id -> "blog-edit-form") {
        @helper.CSRF.formField

        @* id is 0 for 'add', and is set by the 'edit' process *@
        <input type="hidden" name="id" value='@urlId'>

        @helper.inputText(
            form("long_url"),
            '_label -> "Long URL",
            'placeholder -> "the original, long url",
            'id -> "long_url",
            'size -> 68
        )
        @helper.inputText(
            form("short_uri"),
            '_label -> "Short URI",
            'placeholder -> "the short uri you want",
            'id -> "short_uri",
            'size -> 68
        )
        @helper.textarea(
            form("notes"),
            '_label -> "Notes",
            'id -> "notes",
            'rows -> 5,
            'cols -> 60
        )
        <button>Save</button>
    }

    </div>

</div>

</body>
</html>

I’m not going to bother to explain that code at all, other than to say that the short_uri field in the template is where my custom error message will show up.

As a final note, here’s what those custom form error messages look like in the template (a data-entry form):

The Play Framework form template custom error messages

Summary

In summary, if you wanted to see how to handle Play Framework things like how to handle a SQL exception related to a unique constraint violation; how to create a new form after an exception like that; and how to add custom error messages to a Play form, I hope this example is helpful.

Add new comment

Anonymous format

  • Allowed HTML tags: <em> <strong> <cite> <code> <ul type> <ol start type> <li> <pre>
  • Lines and paragraphs break automatically.