In an MVC framework, the Model is the most important entity, View is just kind of Filter on the Model and the Controller just kind of a necessary Evil. So we should be able to build and test the model before even the building phase in the traditional Software Life Cycle Management. This should be the theory, in practice, many people seem to misunderstand the M Part of the MVC Model and most if not all UML / Code Generation tools are just too heavy. So our purpose in our latest serie is to build Agile Tools to achieve this Goal. As usual our preference goes towards mini-dsl.
In our previous article we took as example the PHP Symfony / Propel ORM Framework, but this should apply to any MVC Framework on any Platform.
As reminder, we had used the class function to create the class model of the application which we reproduce again below:
unprotect 'question
unprotect 'answer
unprotect 'user
unprotect 'interest
unprotect 'relevancy
Model: {
class question [
Id: 0
title: ""
body: ""
created_at: 'now
updated_at: 'now
user_Id: [User]
]
class answer [
Id: 0
question_Id: [question]
user_id: [user]
body: ""
created_at: 'now
updated_at: 'now
]
class user [
Id: 0
first_name: ""
last_name: ""
created_at: 'now
]
class interest [
question_id: [question]
user_id: [user]
created_at: 'now
]
class relevancy [
answer_id: [answer]
user_id: [user]
score: 0
created_at: now
]
}
do Model
Since this article, we have added Constructor Support so that we can create a User class with a user Constructor like below (the & symbol has no special significance, it’s a mere personal convention style to distinguish method arguments from object properties):
class User [
;constructor:
user: func[&id &first_name &last_name][
id: &id
first_name: &first_name
last_name: &last_name
created_at: now
]
Id: 0
first_name: ""
last_name: ""
created_at: 'now
]
could be initialized like this:
John: new User[1 "John" "Doe"]
To test it, copy and paste the class and new functions from previous lesson into Rebol Console:
Unprotect 'Class
Class: func['Class Body [block!]][
Type: to-word Class
set Type Make Object! Body
Protect Type
]
Protect 'Class
Unprotect 'New
new: func[Class [Object!] Param-Block [block!] ][
Constructor: to-word pick pick Class 1 2
Obj: Make Class []
params: copy ""
foreach param Param-Block [
if string? param [
param: rejoin [{"} param {"}]
]
append params param
append params " "
]
do rejoin [{do get in Obj Constructor} { } params]
Obj
]
Protect 'New
Unprotect 'User
class User [
;constructor:
user: func[&id &first_name &last_name][
id: &id
first_name: &first_name
last_name: &last_name
created_at: now
]
Id: 0
first_name: ""
last_name: ""
created_at: 'now
]
John: new User [1 "John" "Doe"]
which outputs:
>> John: new User [1 "John" "Doe"]
>> probe john
make object! [
user: func [&id &first_name &last_name][
id: &id
first_name: &first_name
last_name: &last_name
created_at: now
]
Id: 1
first_name: "John"
last_name: "Doe"
created_at: 17-Dec-2009/10:35:53+1:00
]
>>
So far so good. But if we want an End User or Tester to really enter samples data fast, a good idea would be to also provide a primitive GUI Interface to let him enter values without all the hassle of variable declaration and bracketing. For example, we’ll use a simple ask function to request input from user using the interactive method in the new User class below:
Unprotect 'User
class User [
;constructor:
user: func[&id &first_name &last_name][
id: &id
first_name: &first_name
last_name: &last_name
created_at: now
]
interactive: does [
id: ask "User Id: "
first_name: ask "First Name: "
last_name: ask "Last Name: "
created_at: now
]
Id: 0
first_name: ""
last_name: ""
created_at: 'now
]
and modify our new function to be polymorphic - see previous lesson - by accepting block parameters (like before) or none. If none, it would call the Constructor in Interactive mode:
Unprotect 'new
new: func[&Class [Object!] '&Param1 [word! block! unset!] '&Param2 [word! block! unset!]
/local Param-Block ObjName class
][
Param-Block: none
ObjName: none
Class: &Class
if/else value? '&Param1 [
if (type? &Param1) = Word! [
ObjName: &Param1
]
if (type? &Param1) = Block! [
Param-Block: &Param1
]
][
]
if/else value? '&Param2 [
if (type? &Param2) = block! [
Param-Block: &Param2
]
][
]
Constructor: to-word pick pick Class 1 2
Obj: Make Class []
if/else not none? Param-Block [
params: copy ""
foreach param Param-Block [
if string? param [
param: rejoin [{"} param {"}]
]
append params param
append params " "
]
do temp: rejoin [{do get in Obj constructor} { } params]
][
;do rejoin [ObjWord "/" constructor "/interactive" ]
do get in Obj 'interactive
]
if/else not none? ObjName [
Set ObjName :Obj
][
Obj
]
]
protect 'new
With this new function, the 4 syntaxes below are accepted:
John: New User [1 "John" "Doe"]
New User Jane [1 "Jane" "Doe"]
New User Gerard
CS: New User Carl
For the two last cases, since no parameters are given, console will enter interactive mode:
>> CS: New User Carl
User Id: 1
First Name: Carl
Last Name: Sassenrath
CS and Carl are 2 words which will point towards the same Object in Memory, if you modify the Object through Carl, you should see the modification reflected through his CS alias. For example, if you modify the created_at field:
>> carl/created_at
== 18-Dec-2009/12:07:02+1:00
>> carl/created_at: 18-Dec-2009
== 18-Dec-2009
This would be reflected through CS:
>> CS/created_at
== 18-Dec-2009
Thanks to this mini DSL and Console Interactivity, the Model should be able to evolve and tested with sample datas directly by Users or Testers without waiting for implementation / deployment by the Developers.















