Christopher Ross-Gill
April 21, 2007
QuarterMaster is still at an early stage of development, and therefore should be used with utmost caution. It is, however, at a stage where testing and feedback is greatly appreciated.
Requires: A working version of REBOL. REBOL/Core or even REBOL/Base will do; and Mod_Rewrite access is a must.
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.+) /cgi-bin/qm.r [QSA,L]
That should be enough to start.
Currently this does not work with the .exe version of Cheyenne.
An application consists of Model, View and Controller components, contained within their relative subfolders:
my-application/
+-- models/
views/
controllers/
Note: Ensure that in QM’s config, the “system” file space points to /path/to/my-application/. The component subdirectories should exist before invoking QM.
Controllers evaluate user requests, communicates with the Model, then selects the appropriate View. The particular Controller is determined by the request URI:
http://a-site.net/pages/welcome
This URI would invoke the ‘pages’ Controller.
controllers/pages.r
A controller is a REBOL file that contains: a router and some actions. It can also contain helper functions related to that controller:
REBOL [
title: "Pages Controller"
type: 'controller
default: "welcome"
]
event "prepare" does [
welcome-message: "Welcome!"
]
action "welcome" does [
render %welcome.rsp
]
action "hello" does [
redirect-to %/pages/welcome
]
action "contact" does []
Left untouched, an action automatically invokes a View with the corresponding name. In this case, the ‘contact’ action renders %contact.rsp
Views primarily consist of RSP pages, though other pre-processors can be used. The Views folder contains subfolders that correspond to controllers, containing files that correspond to actions:
views/
+-- pages/
+-- welcome.rsp
contact.rsp
Documenting RSP is beyond the scope of this introduction. A sample for %welcome.rsp might be:
<html>
<head><title>Welcome</title></head>
<body>
<h1>Welcome!</h1>
<p>Welcome this fine <%= form-date now "%A" %>!</p>
</body>
</html>
By default, QuarterMaster uses a flat-file, single-table DBMS. Records are manipulated using the Active Record pattern.
What it lacks in sophistication, it makes up for in flexibility. Each record is assigned its own subdirectory that can be used to store all manner of related files. Tables are defined in the models folder:
models/
+-- pages.r
users.r
A definition includes a router (id->folder), key indices, active record methods.
REBOL [
title: "Pages Database"
type: 'roughcut
]
record: make record [
get-web-content: does [
read path/web-content.html
]
]
index: [name tags] ; indexing is not yet implemented
Once a definition exists within the Model folder, you can go straight to work:
page: select pages "welcome"
page/get-web-content
notes: select pages [where tags = "notes"] ; queries are not yet implemented
map notes func [page][page/get-web-content]
close pages
File sandboxes are built into QM. The sandbox method used by QM restricts access to predefined folders. It will prevent access to parent folders or attempts to save files with special characters.
err404: read qm://system/views/errors/not-found.rsp
logo: read/binary qm://site/images/logo.png
Natural polymorphic publishing functions:
render "This"
render/as read/binary qm://site/images/logo.png image/png
render %welcome.rmd ; REBOL MakeDoc
render/status %errors/not-found.rsp 404
redirect-to %/this/page
redirect-to http://rebol.com
>> know %make-doc/scanner.r
qm://root/support/make-doc/scanner.r
REBOL [
Title: "Make-Doc Scanner"
Exports: [scan-doc]
]
...
Functions that assist data-driven operations:
Pagination allows you to extract a section from a data source in order to browse data in bite sizes:
>> pages: paginate wiki 1
>> ? pages
PAGES is an object of value:
last integer! 1
current integer! 1
next logic! false
previous logic! false
records block! length: 2
offset integer! 0
upper block! length: 0
lower block! length: 0
start logic! true
end logic! true
More to follow…
A variation of strfdate:
form-date now "%A %d"
Data import and validation:
import [one "1" two "2.0"][
one: integer! is less-than 2
two: decimal! three: opt integer!
]
Yields:
[one 1 two 2.0 three none]
Quickly transform a block:
map-each [num name][1 "one" 2 "two" 3 "three"][
num: num * num name: uppercase name
]
Yields:
[1 "ONE" 4 "TWO" 9 "THREE"]
Paths are evaluated and transformed to links:
redirect-to wiki/show/(page-name)
<%: a wiki/show/(page-name) %><%= page-name %></a>
<%: form blog/new %>