XOTcl (www.xotcl.org) is one of several OO extensions for Tcl.
Important properties
Stack: the classical example for classes
# # Create a stack class # Class Stack Stack instproc init {} { # Constructor my instvar things set things "" } Stack instproc push {thing} { my instvar things set things [concat [list $thing] $things] return $thing } Stack instproc pop {} { my instvar things set top [lindex $things 0] set things [lrange $things 1 end] return $top }
Using the class "Stack" in a script
% package req XOTcl % namespace import xotcl::* % source stack.xotcl # Create Object s1 of class Stack % Stack s1 ::s1 % s1 push a a % s1 push b b % s1 push c c % s1 pop c % s1 pop b # Delete object s1 s1 destroy
Previous Stack example does not check for underruns. Implement "Saftety" as separate class, which can be used as a mixin.
Important:
Same mechanism for the
We can use Safety as a per-object mixin
% Stack s2 -mixin Safety ::s2 % s2 push a a % s2 pop a % s2 pop Stack empty!
The mixin can be added/removed to existing objects at any time
The same class Safety can be used as per-class mixin.
# # Create a safe stack class by using Stack and mixin # Safety # Class SafeStack -superclass Stack -instmixin Safety SafeStack s3
All instances of SafeStack are now safe.
Per-Class mixins are transitive.
Mixins are an instrument of composition
Methods can be defined per-class (instproc) or per-object (proc).
Define a single stack for integers:
Since classes are Objects, one can define class-specific methods (similar to static methods in C++/Java) with the same method as object-specific methods (via "proc")
Class Stack # ... Stack proc available_stacks {} { return [llength [my info instances]] } Stack s1 Stack s2 puts [Stack available_stacks]
Create a simple object: create an instance of ::xotcl::Object as superclass
Create a class: create an instance of ::xotcl::Class
Class Person # ... is a short form for ... Class create Person # ... or ... Class create Person -superclass Object
Person can now be used to define specialized objects (Persons)
A meta-class can be used to define different kind of classes (e.g. persistent classes)
# Create a generic object Object o1 # Create a different kind of object: create a new class Class Person Person p1 # Create a different kind of class: create a new meta-class Class PersistentClass -superclass Class # Create a persistent class PersistentClass Page # Create an instance of that class Page p2
Slots are meta-objects that manage property-changes of objects. A property is either an attribute or a role in an relation. In a nutshell, a slot has among other attributes:
We distinguish between system slots (predefined slots like class, superclass, mixin, instmixin, filter, instfilter) and attribute slots (e.g. attributes of classes).
Class Person -slots { Attribute name Attribute salary -default 0 Attribute projects -default {} -multivalued true }
Person p1 -name "Joe"
Since slot projects is multivalued, we can add a value to the list of values the add subcommand.
Project project1 -name XOTcl -description "A highly flexible OO scripting language"
p1 projects add ::project1
p1 projects add some-other-value
The value of the instance variable project of Person
p1 is now the list {some-other-value ::project1}.
Slots in XOTcl are extensible:
Objects
Classes
Slots
OpenACS has its own Type Sytem
DB-Layer is much less flexible then XOTcl:
less support for dynamic operations (adding attributes), no support for re-classing, mixins, ...
Approach:
::xo::db::sql::acs_object new [ -dbn dbn ] ...
Defined in packages/xotcl-core/tcl/05-db-procs.tcl
- Switches:
- -dbn (optional)
- -object_id (optional)
- -object_type (defaults to
"acs_object"
) (optional)- -creation_date (defaults to
"now()"
) (optional)- -creation_user (optional)
- -creation_ip (optional)
- -context_id (optional)
- -security_inherit_p (defaults to
"t"
) (optional)- -title (optional)
- -package_id (optional)
############################################################ # # 1) Create new ACS Objects, destroy it in memory, # load it from the database, delete it in the database. # .. Create a plain new ACS object just for demo purposes. .. The ACS object is created with a new object id. >> set o [::xo::db::Object new_persistent_object] = ::7845 .. Show the contents of object ::7845 by serializing it: >> ::7845 serialize = ::xo::db::Object create ::7845 -noinit -set object_title {Object 7845} -set object_id 7845
# In the next steps, we (a) get the object_id of the newly # created ACS object, (b) destroy the XOTcl object (the ACS # object is still in the database, (c) we recreate the # XOTcl object from the database, and (d) delete it in the # database. .. Step (a) >> set o_id [::7845 object_id] = 7845 # # Delete object from memory: <object> destroy # Check, if an XOTcl object exists: ::xotcl::Object isobject <obj> # >> ::xotcl::Object isobject ::7845 = 1 .. Step (b) >> ::7845 destroy >> ::xotcl::Object isobject ::7845 = 0
# # Load an object from the database: # ::xo::db::Class get_instance_from_db -id <id> # .. Step (c) >> set o [::xo::db::Class get_instance_from_db -id 7845] = ::7845 >> ::xotcl::Object isobject ::7845 = 1 .. Now, we have recreated the same object as before: >> ::7845 serialize = ::xo::db::Object create ::7845 -noinit -set object_title {Object 7845} -set object_id 7845
# # Check, if an ACS object exists in the database: # ::xo::db::Class exists_in_db -id <id> # # Delete object from memory and database: # <object> delete # >> ::xo::db::Class exists_in_db -id 7845 = 1 .. Step (d) >> ::7845 delete # Now, we have deleted the ACS Object and the XOTcl object: >> ::xo::db::Class exists_in_db -id 7845 = 0 >> ::xotcl::Object isobject ::7845 = 0
Approach:
############################################################ # # 2) Create new ACS Object Types, ACS Attributes and # SQL Tables from XOTcl Classes with slot definitions. # .. Create a new ACS Object type and an XOTcl class named ::demo::Person. .. Does the ACS Object type ::demo::Person exist in the database? >> ::xo::db::Class object_type_exists_in_db -object_type ::demo::Person = 0 # We create a new XOTcl Class '::demo::Person'. # By defining this class, the database layer takes care # of creating the ACS Object Type and the necessary table via SQL. .. The persistent attributes (stored in the database) are defined .. as slots of type ::xo::db::Attribute. >> ::xo::db::Class create ::demo::Person -superclass ::xo::db::Object -slots { ::xo::db::Attribute create name -column_name pname ::xo::db::Attribute create age -default 0 -datatype integer ::xo::db::Attribute create projects -default {} -multivalued true } = ::demo::Person
# If the ACS Object Type and the ACS Attributes would be # already defined in the database, the class definition above # would be a no-op operation. # Now, the ACS Object Type exists in the database >> ::xo::db::Class object_type_exists_in_db -object_type ::demo::Person = 1 # The XOTcl class definition created automatically the # following table for storing instances: CREATE TABLE demo_person ( age integer DEFAULT '0' , pname text , projects text DEFAULT '' , person_id integer REFERENCES acs_objects(object_id) ON DELETE CASCADE CONSTRAINT demo_person_person_id_pk PRIMARY KEY ) .. SQL attribute slot names: >> ::demo::Person array names db_slot = name age projects person_id
# The XOTcl class definition created as well a 'save' and # an 'insert' method (latter omitted here): .. Created 'save' method: ::demo::Person instproc save {} { db_transaction { next my instvar object_id name age projects db_dml dbqd..update_demo_person { UPDATE demo_person SET pname = :name,age = :age,projects = :projects WHERE person_id = :object_id } } }
# # Create a new instance of ::demo::Person with name 'Gustaf' # # The method 'new_persistent_object' of a database class (instance of ::xo::db::Class) # creates an ACS Object with a fresh id in the database and # creates as well an XOTcl object in memory >> set p [::demo::Person new_persistent_object -name Gustaf -age 105] = ::7846 .. check, if object ::7846 exists in memory >> ::xotcl::Object isobject ::7846 = 1 .. check, if object ::7846 exists in the database >> ::xo::db::Class exists_in_db -id 7846 = 1 .. Show the contents of object ::7846 (using serialize) >> ::7846 serialize = ::demo::Person create ::7846 -noinit -set object_title {Person 7846} -set name Gustaf -set age 105 -set projects {} -set person_id 7846 -set object_id 7846
# modify some attributes of the XOTcl object >> ::7846 incr age = 106 # save the modified object data in the database >> ::7846 save # deleting xotcl object ::7846 in memory >> $p destroy # check, if object ::7846 exists in the database >> ::xo::db::Class exists_in_db -id 7846 = 1 # fetch person again from database: >> set p [::xo::db::Class get_instance_from_db -id 7846] = ::7846 # serialized content ::demo::Person create ::7846 -noinit -set object_title {Person 7846} -set name Gustaf -set age 106 -set projects {} -set object_id 7846 -set person_id 7846
# Now, we create a subclass of ::demo::Person called ::demo::Employee # which has a few more attributes. Again, we define an XOTcl class # ::demo::Employee which creates the ACS Object Type, the ACS # attributes and the table, if necessary. >> ::xo::db::Class create ::demo::Employee -superclass ::demo::Person -table_name demo_employee -id_column employee_id -slots { ::xo::db::Attribute create salary -datatype integer ::xo::db::Attribute create dept_nr -datatype integer -default "0" } = ::demo::Employee # The XOTcl class definition created automatically the # following table for storing instances: CREATE TABLE demo_employee ( dept_nr integer DEFAULT '0' , salary integer , employee_id integer REFERENCES demo_person(person_id) ON DELETE CASCADE CONSTRAINT demo_employee_employee_id_pk PRIMARY KEY )
############################################################ # 3) Create XOTcl classes from existing ACS Object Types # and ACS Attributes based on the definitions in the # database >> set c [::xo::db::Class get_class_from_db -object_type party] = ::xo::db::party .. XOTcl class ::xo::db::party created (superclass ::xo::db::Object) .. SQL attributes: >> ::xo::db::party array names db_slot = email party_id url >> set c [::xo::db::Class get_class_from_db -object_type person] = ::xo::db::person .. XOTcl class ::xo::db::person created (superclass ::xo::db::party) .. SQL attributes: >> ::xo::db::person array names db_slot = last_name first_names person_id
<instance of ::xo::db::Class> get_instances_from_db ...
Returns a set (ordered composite) of the answer tuples of an 'instance_select_query' with the same attributes. Note, that the returned objects might by partially instantiated.
- Switches:
- -select_attributes (optional)
- -from_clause (optional)
- -where_clause (optional)
- -orderby (optional)
- -page_size (defaults to
"20"
) (optional)- -page_number (optional)
- Returns:
- ordered composite
############################################################ # Create new application classes by sub-typing the # Content Repository, adding additional attributes # # We create a subclass of ::xo::db::CrItem called ::demo::Page # which has a few more attributes. Actually, this class is very # similar to ::xowiki::Page. Again, we define an XOTcl class # ::demo::Page which creates the ACS Object Type, the ACS # attributes and the table, if necessary. >> ::xo::db::CrClass create Page -superclass ::xo::db::CrItem \ -pretty_name "demo Page" -mime_type text/html -slots { ::xo::db::CrAttribute create creator \ -column_name creator_string } = ::demo::Page
# create a page object in memory >> set i [::demo::Page new -name "page0" -title "Joke of the Month" -creator "GN" -text "Three cannibals meet in a NYC subway station..." ] = ::xotcl::__#j # save as a new item under default parent_id (-100), allocates fresh item_id >> $i save_new = 7855 >> set item_id [$i item_id] = 7855 # destroy object in memory >> $i destroy # fetch item per item_id from the database >> ::demo::Page get_instance_from_db -item_id 7855 = ::7855
# Lookup page from CR by name >> ::xo::db::Class lookup -name page0 = 7855 # fetch item per item_id from the database >> ::demo::Page get_instance_from_db -item_id 7855 = ::7855 # modify the object >> ::7855 set title "Kilroy was here" # safe the object with a new revision >> ::7855 save
To get started with our demo package, you will need the following:
For the scope of this tutorial, we follow a couple of assumptions. Be aware of these, provided that you apply lessons taken from here in a different context ...
# Define classes in the Tcl namespace ::demo in file note-procs.tcl namespace eval ::demo { # Define a sub-class of ::xo::Package # and provide some meta-data (package-key, pretty_name ...} ::xo::PackageMgr create Package \ -superclass ::xo::Package \ -package_key "xotcl-demo-note" \ -pretty_name "XOTcl Demo Note Package" \ -parameter {{folder_id 0}} ... }
# We are still in the Tcl namespace ::demo in file note-procs.tcl ... # The folder_id is an parameter of the Package. # Each time we initialize our package, we want to have the # folder id ready. So we extend the basic ::xo::Package class, # provide an additional parameter folder_id and create the folder on # the fly when necessary. Package instproc init {} { next my folder_id [::demo::Note require_folder -name demo-note -package_id [my id]] } ...
# We are still in the Tcl namespace ::demo in file note-procs.tcl ... # The class ::demo::Note has an additional attribute "number" in # addition to the common attributes of CrItems. Since Note is a # subclass of content repository items, the additional attributes # are versioned, this means that when an entry is modified and # saved, the old revisions with the old values are still continue to # exist. ::xo::db::CrClass create Note -superclass ::xo::db::CrItem \ -pretty_name "Demo Note" -pretty_plural "Demo Notes" \ -slots { ::xo::db::CrAttribute create number \ -datatype integer -default 0 } } # end of file note-procs.tcl ns_log Notice "note-procs.tcl loaded"
# file xotcl-demo-note/www/index.tcl ::demo::Package initialize -ad_doc { This is the main page for the package. It displays all of the Demo Notes and provides links to create, edit and delete Notes. @author Gustaf Neumann @cvs-id $Id$ } -parameter { {-orderby:optional "last_modified,desc"} } ...
# still in file xotcl-demo-note/www/index.tcl # # We define a table with an action to add new items # TableWidget index -volatile -actions [subst { Action new -label Add -url [$package_id package_url]edit -tooltip "Add a new [::demo::Note pretty_name]" }] -columns { ImageField_EditIcon edit -label "" -html {style "padding-right: 2px;"} AnchorField name -label "Name" -orderby name Field size -label "Size" -orderby size -html {align right} Field last_modified -label "Last Modified" -orderby last_modified Field mod_user -label "By User" -orderby mod_user ImageField_DeleteIcon delete -label "" ;#-html {onClick "return(confirm('Confirm delete?'));" } } ...
# still in file xotcl-demo-note/www/index.tcl # # We populate the table widget with notes stored in the db # We use the ::xo::db::Class->instance_select_query interface # db_foreach instance_select [::demo::Note instance_select_query -folder_id [$package_id folder_id] -select_attributes [list content_length creation_user "to_char(last_modified,'YYYY-MM-DD HH24:MI:SS') as last_modified"]] { index add -name $name -name.href [export_vars -base edit {item_id}] -last_modified $last_modified -size [expr {$content_length ne "" ? $content_length : 0}] -edit.href [export_vars -base edit {item_id}] -mod_user [::xo::get_user_name $creation_user] -delete.href [export_vars -base delete {item_id}] } set html [index asHTML]
# file xotcl-demo-note/www/admin/edit.tcl ::demo::Package initialize -parameter { {-item_id:integer} } # This script is called in multiple situations: it is called, when # an item should be newly created (with default fields), when the # values for the new item are provided by the user, or when # an existing item should be displayed or saved. Depending on the # situation, we might have the item_id of the note. If it exists, # we can fetch it from the database to use the values from there. if {[info exists item_id] && [::xo::db::Class exists_in_db -id $item_id]} { set item [::demo::Note get_instance_from_db -item_id $item_id] } else { set item [::demo::Note new -package_id $package_id] $item set parent_id [$package_id folder_id] } ...
# still in file xotcl-demo-note/www/admin/edit.tcl # # Provide a form + field specification # ::Generic::Form create form1 -volatile -data $item -fields { {item_id:key} {name:text {label Name}} {number:text {label Number}} {text:richtext(richtext),nospell,optional {label Content} {options {editor xinha}}} {description:text(textarea),nospell,optional {label Description} {html {cols 60 rows 2}} } } form1 generate # provide values for edit.adp form1 instvar context formTemplate set title "My first Form"
# file xotcl-demo-note/www/admin/delete.tcl ::demo::Package initialize -ad_doc { Delete a note } -parameter { {-item_id:integer} } # # We need to verify the current user's privileges # permission::require_write_permission -object_id $item_id # # Proceed with deletion # ::xo::db::CrItem delete -item_id $item_id