Tutorial Scaffolding

vreeze42 edited this page Sep 26, 2010 · 21 revisions
Clone this wiki locally

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 command, 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:

lein conjure generate scaffold message text:string

The output from the command should look like:

DEBUG [conjure.core.db.flavors.h2]: Executing query: ["SELECT * FROM sessions LIMIT 1"]
DEBUG [conjure.core.db.flavors.h2]: Create table: :sessions with specs: (("id" "
INT" "NOT NULL" "AUTO_INCREMENT" "PRIMARY KEY") ["created_at" "TIMESTAMP"] ("session_id" "VARCHAR(255)") ["data" "TEXT"])
INFO  [conjure.core.server.server]: Server Initialized.
INFO  [conjure.core.server.server]: Initializing plugins...
INFO  [conjure.core.server.server]: Plugins initialized.
INFO  [conjure.core.server.server]: Initializing app controller...
INFO  [conjure.core.server.server]: App controller initialized.
INFO  [conjure.core.migration.builder]: Migrate directory already exists.
INFO  [conjure.core.migration.builder]: Creating migration file 001_create_messages.clj...
INFO  [conjure.core.model.builder]: Creating model file message.clj...
INFO  [conjure.core.util.file-utils]: Creating unit directory in test...
INFO  [conjure.core.util.file-utils]: Creating model directory in unit...
INFO  [conjure.core.util.file-utils]: Creating file message_model_test.clj...
INFO  [conjure.core.util.file-utils]: Creating fixture directory in test...
INFO  [conjure.core.util.file-utils]: Creating file message.clj...
INFO  [conjure.core.controller.builder]: Creating controller file message_controller.clj...
INFO  [conjure.core.util.file-utils]: Creating functional directory in test...
INFO  [conjure.core.util.file-utils]: Creating file message_controller_test.clj...

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

The first thing generate does is initialize the server. Why initialize the server? Well, all of the configuration files and plugins are loaded at server initialization time. If the server is not initialized, then many of the namespaces you my rely on will not be initialized properly.

Next, generate creates a migration script:

INFO  [conjure.migration.builder]: Migrate directory already exists.
INFO  [conjure.migration.builder]: 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]/src/db/migrate directory. For now, we can ignore the migrate script.

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

INFO  [conjure.model.builder]: Creating model file message.clj...
INFO  [conjure.util.file-utils]: Creating unit directory in test...
INFO  [conjure.util.file-utils]: Creating model directory in unit...
INFO  [conjure.util.file-utils]: Creating file message_model_test.clj...
INFO  [conjure.util.file-utils]: Creating fixture directory in test...
INFO  [conjure.util.file-utils]: Creating file message.clj...

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

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

(clj-record.core/init-model)

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:

INFO  [conjure.controller.builder]: Creating controller file message_controller.clj...
INFO  [conjure.util.file-utils]: Creating functional directory in test...
INFO  [conjure.util.file-utils]: Creating file message_controller_test.clj...

The controller can be found in the [hello_world dir]/src/controllers directory and is called message_controller.clj. The controller looks like:

(ns controllers.message-controller
  (:use [conjure.core.controller.base])
  (:require [controllers.template-controller :as template-controller]))

(copy-actions :template)

All the controller does is copy actions from the template controller which can be found in conjure-core. You can override any action from template, by simply redefining it after the copy-actions call.

The bindings and views can also be found in conjure-core.

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.

ERROR [conjure.server.ring-adapter]: 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 (found in src/db/migrate). 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 command given to us by the Drift library:

lein conjure migrate

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:

lein conjure migrate -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 which is copied from the template controller looks like:

(def-action index
  (redirect-to { :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 home index binding 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 binding 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 binding, and pass the result to the view. Before we can call the function, we need to load the message model into the index binding. We can do this by simply requiring it. Change the ns call at the top of the index binding to:

(ns bindings.home.index
  (:use conjure.core.binding.base
        helpers.home-helper)
  (:require [models.message :as message]))

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

(def-binding []
  (with-home-request-map
    (render-view (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 binding to pass the actual text of the message. The easiest way, is to update the index binding to pass just the text to the view. The final def-binding looks like:

(def-binding []
  (with-home-request-map
    (render-view (: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