Skip to content

Tutorial 0.4 Scaffolding

macourtney edited this page Sep 13, 2010 · 1 revision

This tutorial will introduce the scaffolding feature of Conjure.

Creating a Message Scaffold

In our last tutorial, Parameter Passing, we discussed how to pass parameters around to display messages in our page. Wouldn’t it be nice if we could save messages in a database, and allow the user to choose which message to display. We can do this easily with Conjure. To start, lets create a scaffold for messages.

Generating the Scaffold

To create a scaffold for messages we use the generate script, but we must decide what information a message has which must be saved in the database. A message is pretty simple, all we need is a text field to save the text of the message. Conjure supports two text types, string and text. String relates to a VARCHAR database type. Text relates to the text database type which can hold an arbitrary length string. for our purposes, a string will do nicely.

Now we’re ready to generate a scaffold for messages. To do this, we run the generate.clj script, and pass it a “scaffold” command followed by the name of our model, “message”, and a list of column name and type pairs which look like [column name]:[type]. In our case, the only column is “text” and its type is “string”, thus the pair looks like “text:string”. The full command to run on the command line looks like:

./run.sh script/generate.clj scaffold message text:string

The output from the command should look like:

Migrate directory already exists.
Creating migration file 001_create_messages.clj ...
Creating model file message.clj ...
Creating unit directory in test ...
Creating model directory in unit ...
Creating file message_model_test.clj ...
Creating fixture directory in test ...
Creating file message.clj ...
Creating controller file message_controller.clj ...
Creating functional directory in test ...
Creating file message_controller_test.clj ...
Creating controller directory in views...
Creating view file ajax_row.clj ...
[hello_world dir]/test/unit directory already exists.
Creating view directory in unit ...
Creating message directory in view ...
Creating file ajax_row_view_test.clj ...
[hello_world dir]/app/views/message directory already exists.
Creating view file delete_warning.clj ...
[hello_world dir]/test/unit directory already exists.
[hello_world dir]/test/unit/view directory already exists.
[hello_world dir]/test/unit/view/message directory already exists.
Creating file delete_warning_view_test.clj ...
[hello_world dir]/app/views/message directory already exists.
Creating view file list_records.clj ...
[hello_world dir]/test/unit directory already exists.
[hello_world dir]/test/unit/view directory already exists.
[hello_world dir]/test/unit/view/message directory already exists.
Creating file list_records_view_test.clj ...
[hello_world dir]/app/views/message directory already exists.
Creating view file ajax_delete.clj ...
[hello_world dir]/test/unit directory already exists.
[hello_world dir]/test/unit/view directory already exists.
[hello_world dir]/test/unit/view/message directory already exists.
Creating file ajax_delete_view_test.clj ...
[hello_world dir]/app/views/message directory already exists.
Creating view file ajax_show.clj ...
[hello_world dir]/test/unit directory already exists.
[hello_world dir]/test/unit/view directory already exists.
[hello_world dir]/test/unit/view/message directory already exists.
Creating file ajax_show_view_test.clj ...
[hello_world dir]/app/views/message directory already exists.
Creating view file edit.clj ...
[hello_world dir]/test/unit directory already exists.
[hello_world dir]/test/unit/view directory already exists.
[hello_world dir]/test/unit/view/message directory already exists.
Creating file edit_view_test.clj ...
[hello_world dir]/app/views/message directory already exists.
Creating view file add.clj ...
[hello_world dir]/test/unit directory already exists.
[hello_world dir]/test/unit/view directory already exists.
[hello_world dir]/test/unit/view/message directory already exists.
Creating file add_view_test.clj ...
[hello_world dir]/app/views/message directory already exists.
Creating view file ajax_add.clj ...
[hello_world dir]/test/unit directory already exists.
[hello_world dir]/test/unit/view directory already exists.
[hello_world dir]/test/unit/view/message directory already exists.
Creating file ajax_add_view_test.clj ...
[hello_world dir]/app/views/message directory already exists.
Creating view file show.clj ...
[hello_world dir]/test/unit directory already exists.
[hello_world dir]/test/unit/view directory already exists.
[hello_world dir]/test/unit/view/message directory already exists.
Creating file show_view_test.clj ...

In the above output, I’ve replaced the full path to my hello_world directory with [hello_world dir].

Obviously a lot is going on here. Let’s break it down.

The first thing generate does is create a migration script:

Migrate directory already exists.
Creating migration file 001_create_messages.clj ...

The migration script has a name based on the model name you passed in to it. In this case, it is called 001_create_messages.clj. You can find the file in the [hello_world dir]/db/migrate directory. For now, we can ignore the migrate script.

Next, generate creates a model script and model test scripts:

Creating model file message.clj ...
Creating unit directory in test ...
Creating model directory in unit ...
Creating file message_model_test.clj ...
Creating fixture directory in test ...
Creating file message.clj ...

The model script is named message.clj and you can find it in the [hello_world dir]/app/models directory. The model script looks like:

(ns models.message
  (:use conjure.model.base
        clj-record.boot))

(clj-record.core/init-model)

(defn
#^{ :doc "Returns the metadata for the table associated with this model." }
  table-metadata []
    (doall (find-by-sql [(str "SHOW COLUMNS FROM " (table-name))])))

The model uses the clojure library clj-record Clj-record adds a bunch of functions for storing and retrieving data from our database. We’ll use some of those functions later.

The test script for the message model is called message_model_test.clj and can be found in the directory [hello_world dir]/test/unit/model. We’ll look at this script in depth later when we discuss testing.

Then, the generate script creates a controller and controller test for message:

Creating controller file message_controller.clj ...
Creating functional directory in test ...
Creating file message_controller_test.clj ...

The controller can be found in the [hello_world dir]/app/controllers directory and is called message_controller.clj. The controller is rather large and contains a number of actions.

Finally generate creates all of the views and view tests for the actions in the message controller.

Let’s start our server again, and see what the scaffold gave us. When the server has finished starting up, and you refresh your browser, you should see a new tab called “Message”. Click on it, to see our message scaffold.

SEVERE: An error occurred while processing the request.
org.h2.jdbc.JdbcSQLException: Table MESSAGES not found; SQL statement:
select * from messages where true [42102-114]
...

Oops, the server threw and exception. What happened here? The answer has to do with the migration script. The server is looking at our database for the message table, but it doesn’t exist. Of course it doesn’t exist, we haven’t created it yet.

Migrations

To create the missing message table, we need to run the up function in the 001_create_messages.clj migration script. The function was generated by the generate script for us, and looks like:

(defn
#^{:doc "Migrates the database up to version 1."}
  up []
  (create-table "messages" 
    (id)
    (string "text")))

The code is pretty easy to read. The up function creates a table called “messages”, adds an id column, and a column of type string called “text”. To run this function, we call the migrate script:

./run.sh script/migrate.clj

The migrate script determines what version of the database you have and runs all of the scripts needed to update the database to the latest version. The version of the database corresponds the the number prefix of the last migration script run.

In our example, since no migration script has run, the database has a version number of 0. After we run the 001_create_message.clj script, the database version becomes 1.

If you made a mistake and want to downgrade the database to a previous version temporarily while you make a change to a migration, you can pass a version number to migrate using the -version option. For example, to migrate our database back to its initial state, we can migrate back to version 0:

./run.sh script/migrate.clj -version 0

When migrate wants to down grade a database it will call all of the down functions in each migration file starting with the highest version down to the version you wish to downgrade to. If the version you’re downgrading to is version 0, then all of the down functions are called in all of the migration files. The down function in 001_create_message.clj, simply drops the table “messages” which was created with the up function.

Let’s put the database back to version 1 by running the migrate command without a version number again.

Now we can run our server again, and see the message page without any exceptions.

The Message Page

When you add a new controller, Conjure automatically creates a new tab named after the controller you create. When you click on the tab, Conjure assumes you want to see the index action of the controller for that tab. The message controller’s index action looks like:

(defn index [request-map]
  (redirect-to request-map { :action "list-records" }))

The index action simply redirects the browser to the list-records action in the same controller. Thus when we look at the message tab, the first thing we see is the list-records page. We can add a new message by clicking the add button. Let’s add the message “Hello World!”.

If you have javascript enabled, the add button is replaced with a form for entering in the text for the message. You can type in “Hello World!” in the box and click create to create a new message.

A new row appears in the table, with the id 1, and the text of our message. If you click the “1”, you can view a more details about the message. Since there isn’t much to the message, the detailed view isn’t much help. You can hide it by clicking the hide link.

To delete a message, click on the delete link. When you click on the link, an alert box appears to verify that you want to delete a message. If you click ok, the message is removed from the table.

If you disable javascript, you still get the same functionality, but the entire page reloads for each action.

Add back the “Hello World!” message. In the last section we’ll display this message on the home page.

Displaying a Message From the Database

Now that we have a message in our database to display, let’s change the index action in the home controller to display the message from the database.

The plan is, find the first message in the database and display it. To find the first message in the database we’ll use the message model, then use the controller to pass the message to our view and display it.

First, let’s add a find first function to the message model. The find first function looks like:

(defn
#^{ :doc "Returns the first message in the database." }
  find-first []
  (find-record ["true"]))

The function “find-record” is given to us by clj-record and it returns the first record which matches the given criterion. In our case, the criterion is “true” which matches all records in the database. Thus find-first returns the first record in the database.

Next we need to call this function from the controller, and pass the result to the view. Before we can call the function, we need to load the message model into the home controller. We can do this by simply requiring it. Change the ns call at the top of the home controller to:

(ns controllers.home-controller
  (:use conjure.controller.base
        helpers.home-helper)
  (:require [models.message :as message]))

Now we can call our find-first model function in the index action and pass the result to the view. The new index action looks like:

(defn index [request-map]
  (render-view (home-request-map request-map) (message/find-first)))

There is one last thing we need to do. Since our view is expecting a string instead of a message, we need to either change our view to accept messages, or change the index action to pass the actual text of the message. The easiest way, is to update the index action to pass just the text to the view. The final index action looks like:

(defn index [request-map]
  (render-view (home-request-map request-map) (:text (message/find-first))))

You will need to restart your server to see the changes. Once restarted, you will see the hello world message you added to the database. To prove it is coming from the database, you can change the text of the message and the message will change on the home index page.

PreviousTutorial IndexNext

Clone this wiki locally