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.20The 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: testListExecute 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.20This 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: testListruns 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.51Starting 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.72All 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 masterListExecute 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.72The 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 |