ACF mvc.lua example: Difference between revisions
Line 57: | Line 57: | ||
local n = f:read("*a") or "none" | local n = f:read("*a") or "none" | ||
f:close() | f:close() | ||
n=string.gsub(n, "\n$", "") | |||
hosttype.value = n | |||
return (hosttype) | |||
end | |||
Revision as of 13:05, 26 October 2007
Set the hostname with mvc.lua
In this example we will create a simple hostname-setting command-line application using mvc.lua. Once the controller/model are built, you can use the same code to set the hostname via the web with a web-based application controller.
For this example, we will assume you have root access on the linux box you are running on (preferably an alpine box!)
Get the mvc.lua module
svn "export" the mvc.lua module. Export will grab a copy without all the extra hidden "version control" stuff
svn export svn://svn.alpinelinux.org/acf/core/trunk/www/cgi-bin/mvc.lua
Create a model and controller
Create a file hostname-model.lua, defining the module functions to set and read the hostname. We return a table for each function including the value, error message and type of the value ("String" in the case of the hostname).
hostname-model.lua
-- Model functions for retrieving / setting the hostname module ( ..., package.seeall ) -- All functions return a table with -- A value, the type of the value, and a message if there was an error local hosttype={ type="string" } update= function ( name ) -- Check to make sure the name is valid if (name == nil) then hosttype.msg = "Hostname cannot be nil" elseif (#name > 16) then hosttype.msg = "Hostname must be less than 16 chars" elseif (string.find(name, "[^%w%_%-]")) then hosttype.msg = "Hostname can contain alphanumerics only" end -- If it is, set the hostname if (hosttype.msg == nil ) then local f = io.open("/etc/hostname", "w") if f then f:write(name .. "\n") f:close() end f = io.popen ("/bin/hostname -F /etc/hostname") f:close() return read() -- Otherwise, return the error message else hosttype.value = name return hosttype end end read= function () local f = io.popen ("/bin/hostname") local n = f:read("*a") or "none" f:close() n=string.gsub(n, "\n$", "") hosttype.value = n return (hosttype) end
Create a file hostname-controller.lua, defining the functions that an "end user" could run. We define Create Read Update Delete as standard actions:
hostname-controller.lua
-- hostname controller code module ( ... , package.seeall ) create = function (self ) return self.model.update(self.clientdata.hostname) end read = function (self) return self.model.read() end update = create delete = function (self ) self.clientdata.hostname="" return self.worker":create() end
Optionally test the model code (without mvc.lua)
If you want, you can create a test.lua script to validate the model code works on its own:
test.lua
m=require("hostname-model") print(m.update(arg[1]).msg) print(m.read().value)
You can then test this with:
#lua test.lua "Alpine" nil Alpine
#lua test.lua "Invalid Name" Hostname can contain alphanumerics only Alpine
Make an MVC based application
To make the model and and controller work within the mvc.lua framework, we must do serveral things.
1. Create a configuration file. We'll call the application helloworld, so edit helloworld.conf and add:
appdir=helloworld/app/
2. Move the model and controller to the helloworld app directory:
mkdir -p helloworld/app mv helloworld-*.lua helloworld/app
3. Create an application level controller in the helloworld/app directory, named helloworld/app/app-controller.lua
module ( ..., package.seeall) -- application specific functions will go here
Nothing else needs to go in this controller for now.
4. Create a dispatch wrapper program, named helloworld.lua in the current directory:
-- Simple CLI based mvc application -- this is to get around having to store -- the config file in /etc/helloworld/helloworld.conf ENV={} ENV.HOME="." -- load the module require("mvc") -- create an new "mvc object" MVC=mvc:new() -- load the config file so we can find the appdir MVC:read_config("helloworld") -- create an application container APP=MVC:new("app") -- dispatch the request APP.clientdata.hostname=arg[2] APP:dispatch( "", "hostname", (arg[1] or ""))
This application loads the "mvc.lua" framework, creates an mvc "object" named "MVC", then reads the helloworld.conf file to find out where the app dir is (helloworld/app/). It then loads the app-controller.lua into a new "application level" object named APP. Finally, it sets the clientdata and dispatches the hostname-controller/model pair.
5. Test the application:
# hostname Alpine # lua helloworld.lua no-such-function foo The following unhandled application error occured: controller: "hostname" does not have a "no-such-function" action. # hostname Alpine
# hostname Alpine # lua helloworld.lua update Alline Your controller and application did not specify a view resolver. The MVC framework has no view available. sorry. # hostname Alline
Note in the second case the hostname was changed, although the application does not know how to report success.
Create a view resolver
The view resolver is a function that returns a function that processes the view. the second function (the one that is returned) receives input and processes the output.
We will build a very simple view resolver and view processor for our application. Add this to the end of helloworld/app/hostname-controller.lua
local private = {} private.view = function (f) if (f.msg) then print( (f.value or "") .. " is not a valid hostname ") else print ("Hostname is currently " .. f.value ) end end view_resolver = function (self) return private.view end
Now we can test:
# lua helloworld.lua update "1 2 3" 1 2 3 is not a valid hostname # lua helloworld.lua update is not a valid hostname # lua helloworld.lua update Alpine Hostname is currently Alpine
But we have two problems:
1. We now have to make a view resolver and view function for every controller. If we add a date setting controller, we'll have to make a view resolver and view function for it, and so on.
2. Perhaps more importantly, view_resolver is now an "action" in our appliction. Recall that invalid actions are captured, but try this:
# lua helloworld.lua no_such_action Alpine The following unhandled application error occured: controller: "hostname" does not have a "no_such_action" action. # lua helloworld.lua view_resolver Alpine The following unhandled application error occured: ./helloworld/app/hostname-controller.lua:27: attempt to index local 'f' (a function value) stack traceback: ./helloworld/app/hostname-controller.lua:27: in function 'viewfunc' ./mvc.lua:139: in function <./mvc.lua:90> [C]: in function 'xpcall' ./mvc.lua:90: in function 'dispatch' helloworld.lua:23: in main chunk [C]: ?
Because view_resolver is an action in the worker table, the mvc.lua runs it; but it returns a function, not a table, and causes an unhandled exception.
The solution to both problems is to move the view resolver and the view function out of the controller's worker table, into the next higher level, in this case, the application's worker table:
1. Delete the private.view and view_resolver functions from helloworld/app/hostname-controller.lua
2. Add the following to helloworld/app/app-controller.lua