Case Study 4: Simple Report Writer


Reminder!
To run these examples, you should first start a new session and then load the sample database using:
  "/localvision/samples/general/sample.load" asFileContents evaluate ;
and load testList using:
    !testList <- Company masterList 
        rankDown: [ sales ] . 
        select: [ rank <= 20 ] ;
    
Any other files referenced can be read from the /localvision/samples/general/ directory.

Note: The sample.load file runs by default on a Unix environment. If you are using a Windows NT platform, this location may be prefixed by a drive and optional path (e.g. d:/visiondb/localvision/samples/general/sample.load). Check with your Vision Administrator for further details.


In the previous example, you learned how to write a generic application that produces a cross-tabular form of report for any type of data. Example 4 develops these concepts further by creating some new classes that can serve as a basis for a general-purpose report writer.


Read the file example4.a. You should see:

     !list <- testList ; 
     !blockList <- 
         [ ticker ] , [ name ] , [ sales ] , [ price / earningsPerShare ] ; 
    
     list 
     do: [ !element <- ^self ; 
           ^my blockList 
              do: [ ^my element send: ^self . print: 12 ] ; 
            newLine print ; 
        ] ; 
    
Execute this program. You should see:
    AET	Aetna Life &	22114.11	 5.46
    T	American Tel	51209.02	23.20
    AN	Amoco Corp	20174.00	19.91
    ARC	Atlantic Ric	16282.00	18.15
    CI	CIGNA Corp	16909.30	 6.90
    ...
    
This program starts out by defining two variables: list refers to the list you wish to process and blockList is a list of blocks that define the data items to display for each element in the list. These blocks can contain simple messages or complex expressions. Each block will be sent to each element in list. It is assumed that the messages in the blocks are defined for the list elements. For each element in list, the value generated by "sending" each block in blockList is printed, producing the simple tabular report above.

The file example4.b uses the block to display headings on the report:

     !list <- testList ; 
     !blockList <- 
         [ ticker ] , [ name ] , [ sales ] , [ price / earningsPerShare ] ; 
    
     blockList                         #--  display each block as a heading 
        do: [ asUndelimitedString print: 12 ] ; 
     newLine print ; 
    
     list 
     do: [ !element <- ^self ; 
           ^my blockList 
              do: [ ^my element send: ^self . print: 12 ] ; 
           newLine print ; 
         ] ; 
    
Execute this program. You should see:
    ticker  name         sales        price / earn
    AET	  Aetna Life &	 22114.11	  5.46
    T	  American Tel	 51209.02	 23.20
    AN	  Amoco Corp	 20174.00	 19.91
    ARC	  Atlantic Ric	 16282.00	 18.15
    CI	  CIGNA Corp	 16909.30	  6.90
     . . .
    
The report now contains some simple headings. You can probably start to see many formatting problems with this approach. For example, all the columns are 12 characters wide. You may want the headings left-justified for columns containing text and right-justified for columns containing numeric data. The contents of the heading itself may need to more precise, especially if the block contains a calculation.

The file example4.c creates a new class called a ReportItem. This class allows you to define a header, a data type, and a column width for each report item in addition to the block used to access the data:

     #---  Create New Class 
     Object createSubclass: "ReportItem" ; 
     #---  Define Some Properties 
     ReportItem 
        defineFixedProperty: 'header' . 
        defineFixedProperty: 'dataType' . 
        defineFixedProperty: 'width' . 
        defineFixedProperty: 'accessBlock'  ; 
    
     #---  Create Some Instances 
     !ticker <- ReportItem new 
        do: [ :header <- "Ticker" ; 
              :dataType <- ^global String ; 
              :width <- 10 ; 
              :accessBlock <- [ ticker ] ; 
            ] ; 
     !name <- ReportItem new 
        do: [ :header <- "Name" ; 
              :dataType <- ^global String ; 
              :width <- 25 ; 
              :accessBlock <- [ name ] ; 
            ] ; 
     !sales <- ReportItem new 
        do: [ :header <- "Sales" ; 
              :dataType <- ^global Number ; 
              :width <- 12 ; 
              :accessBlock <- [ sales ] ; 
            ] ; 
     !pe <- ReportItem new 
        do: [ :header <- "PE Ratio" ; 
              :dataType <- ^global Number ; 
              :width <- 10 ; 
              :accessBlock <- [ price / earningsPerShare ] ; 
            ] ; 
    
Execute this program. There will not be any output.

This program creates the new class ReportItem and defines its properties. Notice that the defineFixedProperty: methods are "sent" in succession separated by the '.' character. Because the message defineFixedProperty: is defined to return the recipient object, the next message you send is applied to this same object. Four new instances are created and assigned into the temporary variables ticker, name, sales, and pe. Each instance is assigned a value for header, dataType, width, and accessBlock.


The file example4.d redefines the program to use a list of these report item instances instead of the list of blocks:

     !list <- testList ; 
     !itemList <- ticker, name, sales, pe ;       #-- list of ReportItems 
    
     itemList 
      do: [ dataType isNumber             #-- right justify if type is Number 
               ifTrue: [ header print: (width * -1) ] 
              ifFalse: [ header print: width ] ; 
          ] ; 
     newLine print ; 
    
     list 
     do: [ !element <- ^self ; 
           ^my itemList 	     #--  send item's accessBlock to each element 
           do: [ ^my element send: accessBlock . print: width ] ; 
           newLine print ; 
         ] ; 
    
Execute this program. You should see:
    Ticker	Name	                   Sales	PE Ratio
    AET	Aetna Life & Cas	22114.11	    5.46
    T	American Tel & Tel	51209.02	   23.20
    AN	Amoco Corp	        20174.00	   19.91
    ARC	Atlantic Richfield	16282.00	   18.15
    CI	CIGNA Corp	        16909.30	    6.90
    . . .
    
Notice that the headings, column width and column heading justification are now all based on the ReportItem being displayed. Now suppose you wanted to limit the list to elements passing a specific criteria and sort the list based on a user-defined order. The file example4.e adds these capabilities:
     !list <- testList ; 
     !itemList <- ticker, name, sales, pe ; 
     !criteria <- [ sales > 50000 ] ;                 #--  criteria to use 
     !sort <- [ sales ] ;                            #--  sort rule 
     !sortIsUp <- FALSE ;                               #--  sort direction flag 
    
     itemList 
      do: [ dataType isNumber 
               ifTrue: [ header print: (width * -1) ] 
              ifFalse: [ header print: width ] ; 
          ] ; 
      newLine print ; 
     #---  Apply selection criteria and sort the result 
     !universe <- list select: criteria ; 
     sortIsUp 
        ifTrue: [ :universe <- universe sortUp: sort ] 
       ifFalse: [ :universe <- universe sortDown:  sort ] ; 
     universe 
     do: [ !element <- ^self ; 
           ^my itemList 
           do: [ ^my element send: accessBlock . print: width ] ; 
           newLine print ; 
         ] ; 
    
Execute this program. You should see:
    Ticker	Name	                    Sales	PE Ratio
    GM	General Motors Corp	123456.78	 6.39
    XON	Exxon Corp	         76416.00	18.62
    F	Ford Motor Company	 71643.38	 4.36
    IBM	IBM Corp	         54217.02	 9.26
    MOB	Mobil Corporation	 51223.02	21.32
    T	American Tel & Tel	 51209.02	23.20
    
The report now uses the variables criteria, sort, and sortIsUp to restrict the list and sort the results. This report only includes elements with sales values greater than 50,000 and is sorted from highest to lowest sales.

Instead of defining the report variables in this way, it seems appropriate to introduce another class at this time. The file example4.f introduces the class Report and defines properties to store the itemList, criteria, sort, and sortIsUp values. This class also defines a method that can be used to apply these settings to a list supplied as a parameter:

     #---  Create New Class and Properties 
     Object createSubclass: "Report" ; 
     Report 
        defineFixedProperty: 'itemList' . 
        defineFixedProperty: 'criteria' . 
        defineFixedProperty: 'sort' . 
        defineFixedProperty: 'sortIsUp' . 
     ; 
     #--- Define a Report Instance 
     !rep1 <- Report newAs: "Report1" ; 
     rep1 :itemList <- ticker, name, sales, pe ; 
     rep1 :criteria <- [ sales > 50000 ] ; 
     rep1 :sort <- [ sales ] ; 
     rep1 :sortIsUp <- FALSE ; 
     #---  Define A Method to display the Report 
     Report 
     defineMethod: 
     [ | displayFor: list | 
       itemList 
        do: [ dataType isNumber 
                 ifTrue: [ header print: (width * -1) ] 
                ifFalse: [ header print: width ] ; 
            ] ; 
       newLine print ; 
       !universe <- list select: criteria ; 
       sortIsUp 
          ifTrue: [ :universe <- universe sortUp: sort ] 
         ifFalse: [ :universe <- universe sortDown:  sort ] ; 
       universe 
       do: [ !element <- ^self ; 
             ^my itemList 
             do: [ ^my element send: accessBlock . print: width ] ; 
             newLine print ; 
           ] ; 
     ] ; 
    
     #--- And run it for rep1 on testList 
     rep1 displayFor: testList 
    
Execute this program. You should see:
    Ticker	Name	                    Sales	PE Ratio
    GM	General Motors Corp	123456.78	    6.39
    XON	Exxon Corp	         76416.00	   18.62
    F	Ford Motor Company	 71643.38	    4.36
    IBM	IBM Corp	         54217.02	    9.26
    MOB	Mobil Corporation	 51223.02	   21.32
    T	American Tel & Tel	 51209.02	   23.20
    
This program creates the new class Report and defines its properties. An initial instance of this class is created and assigned into the variable rep1. The values for itemList, criteria, sort, and sortIsUp are assigned into the rep1 instance. The report has been converted into the method displayFor: which requires a list as a parameter. The displayFor: message is then sent to rep1 with testList as its parameter, producing the report.

You can now change any of the properties of rep1 and rerun the report. For example, the program:

     rep1 :sort <- [ name ] ; 
     rep1 :sortIsUp <- TRUE ; 
     rep1 displayFor: testList 
    
runs the report using these new settings. Any settings that are unchanged retain their value. The file example4.g defines some new methods for updating these values. By encapsulating the procedure to change the values, you can test the values for validity prior to changing them. These methods are also all defined to return ^self, enabling the same type of message streaming that you saw with defineFixedProperty::
     #---  Define Cover Methods to Set the Report Parameters 
     Report 
     defineMethod: 
     [ | setItemListTo: blocks | 
       :itemList <- blocks ; 
       ^self               # return current Report object 
     ] . 
     defineMethod: 
     [ | setCriteriaTo: block | 
       block isBlock 
         ifTrue: [ :criteria <- block ] 
        ifFalse: [ ">>>  Cannot Set Criteria.  <<<" printNL ] ; 
       ^self 	 # return current Report object 
     ] . 
     defineMethod: 
     [ | setSortUpTo: block | 
       block isBlock 
         ifTrue: [ :sort <- block ; 
                   :sortIsUp <- TRUE ; 
                 ] 
        ifFalse: [ ">>>  Cannot Set Sort.  <<<" printNL ] ; 
       ^self 	 # return current Report object 
     ] . 
     defineMethod: 
     [ | setSortDownTo: block | 
       block isBlock 
         ifTrue: [ :sort <- block ; 
                   :sortIsUp <- FALSE ; 
                 ] 
        ifFalse: [ ">>>  Cannot Set Sort.  <<<" printNL ] ; 
       ^self 	 # return current Report object 
     ] . 
     ; 
    
     #---  Set Some Values and Run Report for Company masterList 
     rep1 
       setItemListTo: name, pe, sales . 
       setCriteriaTo: [ sales > 25000 && price / earningsPerShare < 10 ] . 
       setSortDownTo: [ sales ] . 
       displayFor: Company masterList ; 
    
Execute this program. You should see:
    Name	            PE Ratio	    Sales
    General Motors Corp	6.39	123456.78
    Ford Motor Company	4.36	 71643.38
    IBM Corp	        9.26	 54217.02
    Citicorp	        3.67	 27988.01
    Chrysler Corp	        3.92	 26276.51
    
Starting with the full Company universe, this report displays the items in the new order, applies the two screening criteria, and sorts the report from highest to lowest sales.

You can execute the report as of a different date. Read in the file example4.h or type:

    89 evaluate: [ rep1 displayFor: Company masterList ] ;
Execute this program. You should see:
    Name	            PE Ratio	    Sales
    General Motors Corp	7.82	102813.00
    Ford Motor Company	6.41	 62715.72
    
All data in the report has been accessed as 12/31/1989.

The current report method does not indicate the "as of" date for the data. The file example4.i adds the date as another attribute of the report and modifies the displayFor: method to print the date. A title attribute is also defined in this version:

     Report 
     #---  Add asOfDate and title as Report attributes 
         defineFixedProperty: 'asOfDate' . 
     defineMethod: 
     [ | setAsOfDateTo: date | 
       :asOfDate <- date asDate ; 
       ^self 
     ] . 
         defineFixedProperty: 'title' . 
     defineMethod: 
     [ | setTitleTo: string | 
       :title <- string asString ; 
       ^self 
     ] . 
    
     #---  Modify to use title and asOfDate; display other criteria 
     defineMethod: 
     [ | displayFor: list | 
       !totalWidth <- itemList total: [ width ] ; 
       title centerNL: totalWidth . print; 
       !evaluationDate <- asOfDate ;        #-- use asOfDate if defined 
       evaluationDate isNA ifTrue: [ :evaluationDate <- ^date ] ; 
       "As Of: " print ; 
       evaluationDate printNL ; 
       "    Criteria: " print ; criteria printNL ; 
       "    Sort " print ; 
       sortIsUp ifTrue: [ "Up: " print ] ifFalse: [ "Down: " print ] ; 
       sort printNL ; 
       newLine print ; 
       itemList 
        do: [ dataType isNumber 
                 ifTrue: [ header print: (width * -1) ] 
                ifFalse: [ header print: width ] ; 
            ] ; 
       newLine print ; 
       evaluationDate evaluate: [            #-- evaluate report using correct date 
       !universe <- list select: criteria ; 
       sortIsUp 
          ifTrue: [ :universe <- universe sortUp: sort ] 
         ifFalse: [ :universe <- universe sortDown:  sort ] ; 
       universe 
       do: [ !element <- ^self ; 
             ^my itemList 
             do: [ ^my element send: accessBlock . print: width ] ; 
             newLine print ; 
           ] ; 
       ] ;                  #--- end of evaluate: 
     ] ;                    #--- end of method 
    
     #--- Set Title and rerun it as of 1989 
     rep1 
       setTitleTo: "First Report" . 
       setAsOfDateTo: 89 . 
       displayFor: Company masterList 
    
Execute this program. You should see:
                     First Report
    As Of: 12/31/1989
        Criteria: [sales > 25000 && price / earningsPerShare < 10]
        Sort Down: [sales]
    
    Name	            PE Ratio	    Sales
    General Motors Corp	7.82	102813.00
    Ford Motor Company	6.41	 62715.72
    
The properties asOfDate and title and the appropriate cover methods are defined. The displayFor: method is modified to print out a title section at the top of the report. First the title is centered over the total width of the report. The evaluation date is defined to be the asOfDate if it has been set. Otherwise, the current default date is used. The evaluation date, criteria, and sort rule for the report is then displayed.

The file example4.j defines a new Report Item which accesses the sales value from the prior year-end. The itemList is redefined to include this item and the report is rerun for the full Company universe:

     !salesPrior <- ReportItem new 
        do: [ :header <- "Prev Sales" ; 
              :dataType <- ^global Number ; 
              :width <- 12 ; 
              :accessBlock <- [ :sales lag: 1 yearEnds ] ; 
            ] ; 
     rep1 
       setItemListTo: name, sales, salesPrior . 
       displayFor: Company masterList ; 
    
Execute this program. You should see:
                      First Report
    As Of: 12/31/1989
        Criteria: [sales > 25000 && price / earningsPerShare < 10
        Sort Down: [sales]
    
    Name	                    Sales	Prev Sales
    General Motors Corp	102813.00	  96371.63
    Ford Motor Company	 62715.72	  52774.31
    

To run the same report as of 12/31/90, read in the file example4.k or type:
     rep1 
       setAsOfDateTo: 90 . 
       displayFor: Company masterList ; 
    
Execute this program. You should see:
                      First Report
    As Of: 12/31/1990
        Criteria: [sales > 25000 && price / earningsPerShare < 10]
        Sort Down: [sales]
    
    Name	                    Sales      Prev Sales
    General Motors Corp	101781.00	102813.00
    Ford Motor Company	 71643.38        62715.72
    IBM Corp	         54217.02	 51250.02
    Citicorp	         27988.01	 23496.00
    Chrysler Corp	         26276.51	 22586.30
    

| Vision Basics | Creating a Demo Database | Single Object Access | Using Lists | Using Dates and TimeSeries |