It is currently Thu Mar 28, 2024 4:49 pm


All times are UTC - 8 hours [ DST ]




Post new topic Reply to topic  [ 1 post ] 
Author Message
 Post subject: Unit 3 - Lesson 2: Create/Edit Database - Exercise 5
PostPosted: Sun Jul 10, 2011 10:56 am 
User avatar

Joined: Wed Nov 17, 2010 8:37 am
Posts: 136
Real Name: Terry L. Wiechmann
Began Programming in MUMPS: 0- 0-1971
Exercise 4 Solution

Routine: MP1PEDIT
Code:
MP1PEDIT ; Create/Edit Parts routine for the MP1 Course.
    New REC,X,PNUM,PDESC,PQTY,PLVL,PRC,PSP,UPPER,LOWER,IEN
    ;
INIT ; Initialize all variables and drop through.
    Set (REC,PNUM,PDESC,PQTY,PLVL,PRC,PSP)=""
    Set IEN=0
    ;
PNUM ; Part Number
    Write !,"Part Number: "
    Write:$Get(PNUM)]"" PNUM," // "
    Read X
    If X="" Set X=PNUM
    If X'?1.6N Goto EXIT:X["^" Do  Goto PNUM
    . Write !?5,"Enter 1-6 numeric digits. Required."
    If 'IEN Do
    . Quit:'$Data(^MP1PARTS("B",X))
    . Set IEN=$Order(^MP1PARTS("B",X,""))
    . Set REC=^MP1PARTS(IEN)
    . Set PDESC=$Piece(REC,"^",2)
    . Set PQTY=$Piece(REC,"^",3)
    . Set PLVL=$Piece(REC,"^",4)
    . Set PRC=$Piece(REC,"^",5)
    . Set PSP=$Piece(REC,"^",6)
    Set PNUM=X
    ;
PDESC ; Description
    Write !,"Description: "
    Write:$Get(PDESC)]"" PDESC," // "
    Read X
    If X="" Set X=PDESC
    If X'?1A.29NAP Goto PNUM:X["^" Do  Goto PDESC
    . Write !?5,"Enter 1-30 characters starting with a letter. Required."
    Set PDESC=X
    ;
PQTY ; Quantity
    Write !,"Quantity: "
    Write:$Get(PQTY)]"" PQTY," // "
    Read X
    If X="" Set X=PQTY
    If X'?1.N Goto PDESC:X["^" Do  Goto PQTY
    . Write !?5,"Enter an integer value. Required."
    Set PQTY=X
    ;
PLVL ; Reorder Level (it's time to reorder the part if PQTY<PLVL)
    Write !,"Order Level: "
    Write:$Get(PLVL)]"" PLVL," // "
    Read X
    If X="" Set X=PLVL
    If X'?1.N Goto PQTY:X["^" Do  Goto PLVL
    . Write !?5,"Enter an integer value. Required."
    Set PLVL=X
    ;
PRC ; Replacement Cost
    Write !,"Replacement Cost: "
    Write:$Get(PRC)]"" PRC," // "
    Read X
    If X="" Set X=PRC
    If X'?.N.1".".2N Goto PLVL:X["^" Do  Goto PRC
    . Write !?5,"Enter replacement cost in dollars[.cents]"
    Set PRC=X
    ;
PSP ; Selling Price
    Write !,"Selling Price: "
    Write:$Get(PSP)]"" PSP," // "
    Read X
    If X="" Set X=PSP
    If X'?.N.1".".2N Goto PRC:X["^" Do  Goto PSP
    . Write !?5,"Enter selling price in dollars[.cents]"
    Set PSP=X
    ;
FEI ; File/Edit/Ignore Entry
    Set Default="F"
    Write !,"File (F), Edit (E) or Ignore (I) : "
    Write:$Get(PLVL)]"" Default," // "
    Read X
    If X["^" Goto PSP
    If X="" Set X=Default
    ; This code demands rigid upper/lowercase entry. Will be normalized.
    If $Extract("Edit",1,$Length(X))=X Goto PNUM
    If $Extract("Ignore",1,$Length(X))=X Goto INIT
    If $Extract("File",1,$Length(X))=X Do PUT Goto INIT
    Write " Enter F, E or I."
    Goto FEI
    ;
PUT ; File the Parts record.
   New ERR,OPNUM,OPDESC
   ; If entry does exist, get the old indice values.
   If IEN Do
   . ;Get original indices values.
   . Set OPNUM=$Piece(^MP1PARTS(IEN),"^",1)
   . Set OPDESC=$Piece(^MP1PARTS(IEN),"^",2)
   ; If entry does not exist, get a new Internal Entry Number (IEN).
   Else  Do
   . ; Init old values to null - new record.
   . Set (OPNUM,OPDESC)=""
   . Lock +^MP1PARTS(0):0 ; Increment lock count on node. Force timeout.
   . Else  Write !?5,"Cannot get next node number – record not filed." Quit
   . If '$Data(^MP1PARTS(0)) Set ^MP1PARTS(0)=0
   . Set (IEN,^MP1PARTS(0))=^MP1PARTS(0)+1
   . Lock -^MP1PARTS(0) ; Decrement lock count on node
   ; Make sure indices do not exist - multiple indices not permitted.
   If PNUM'=OPNUM,$Data(^MP1PARTS("B",PNUM)) Do  Quit
   . Write !?5,"Number "_PNUM_" is already being used."
   If PDESC'=OPDESC,$Data(^MP1PARTS("C",PDESC)) Do  Quit
   . Write !?5,"Description "_PDESC_" is already being used."
   ; Lock record node incrementally.
   Lock +^MP1PARTS(IEN):0 ; Increment lock count on record. Force timeout.
   ; If timeout occurred and no lock then pass back record busy message.
   Else  Write !?5,"Record busy – record not filed." Quit
   ; Remove old indices.
   Kill:OPNUM]""&(OPNUM'=PNUM) ^MP1PARTS("B",OPNUM)
   Kill:OPDESC]""&(OPDESC'=PDESC) ^MP1PARTS("C",OPDESC)
   ; Set the new record and indices nodes.
   Set ^MP1PARTS(IEN)=PNUM_"^"_PDESC_"^"_PQTY_"^"_PLVL_"^"_PRC_"^"_PSP
   Set ^MP1PARTS("B",PNUM,IEN)=""
   Set ^MP1PARTS("C",PDESC,IEN)=""
   ; Incrementally unlock record node.
   Lock -^MP1PARTS(IEN) ; Decrement lock count on node
   Write !?5,"Record filed successfully."
   Quit
   ;
EXIT ;
    Quit

Review
  1. At this point you should have an operational routine that Creates a new or edits an existing record. I recommend that you save this routine to a backup name so that you have a baseline to refer to. You can continue to modify this routine.
  2. Make sure you understand what all the code is doing. If you don't, don't just ignore it.
  3. Concerning the structured programming philosophy, there is a school of thought that says you should NEVER use a Goto command. In my view this is an extreme position. It is absolutely true that excessive use of the Goto command can lead to what we call "spaghetti code". However, there are situations where Goto command is useful. To a certain degree, our Create/Edit routine is spaghetti code. It uses the Goto command to jump around and there is no real structure to it. We will rectify that over in the MUMPS Programming II course.
Goals
  1. Replace the write prompt, default and read with an ASK extrinsic function. Move it to the View side.
  2. Modify the PUT subroutine to become an extrinsic function. Separate it from the MP1PEDIT routine. Move it to the model side. Add the record construction code to the model side under the extrinsic function SETREC.
  3. Extract the GET database record code and make it an extrinsic function. Move it to the model side.
  4. Extract the Business rules and move them to the model side.
Description
  1. At this point we need to look at the routine and start structuring the code based on the MVC pattern, reusability and generalization. In large part, doing this will rectify the spaghetti code problem.
  2. What patterns do you see where the code can be put into subroutines and called using parameter passing? Clearly, the parts of each query that asks for the user to enter a value. Here is an example of how that can be generalized. Create a new routine called MP1VIEW. Add this code to the MP1VIEW routine.
    Code:
    ASK(Prompt,Default,Upper) ; Extrinsic function that asks for a value.
        ; Prompt – Contains prompt displayed to user.
        ; Default – Contains the default valuer to display
        ; Upper – 0 means no normalization to uppercase, 1 means normalization.
        ; Insure that the variable exist if not passed it.
        ; The $Get values are null by default if the second parameter is not specified.
        Set Prompt=$Get(Prompt) ;Default to null if not passed in.
        Set Default=$Get(Default) ;Default to null if not passed in.
        Set Upper=$Get(Upper,0) ;Default to 0 if not passed in.
        New X ; Protect callers X if it exists.
        Write !,Prompt,": "
        Write:Default]"" Default," // "
        Read X
        If X="" Set X=Default ; If null entered, take default
        If Upper Set X=$$Upper^MP1XLATE(X) ; If normalize to upper, call converter.
        Quit X ;Pass back the value to the caller.
        ;
  3. When writing a callable body of code that has parameters passed in, it is important to make sure that they actually exist. It is possible to not pass values into the called context. In the above case, the parameter may not be passed in. Consequently Prompt and Default variables would not be defined resulting in an error. To avoid this, we generally use the $Get function to initialize the variable to a default value. A $Get(Prompt) and $Get(Prompt,"") are the same. The second parameter determines what value the variable is initialized to where null is the default.
  4. Replace the literal code in each query with a call to the ASK function. Test completely before proceeding.
  5. Now lets separate the PUT subroutine and make it callable. Notice that the subroutine is tightly bound to the view (roll and scroll interface). We want to separate it so that it can be called as a model side function that know nothing of who is calling it.
  6. File this extrinsic function under a new routine named MP1PDATA.
    Code:
    PUT(IEN,REC) ; File the Parts record.
       ; IEN is either the Internal Entry Number or zero (not defined).
       ; REC is the full record structure.
       New ERR,OPNUM,OPDESC
       ;Default return message to success.
       Set ERR="0;Record filed successfully." ; Initial error state to success.
       ; Get indice values.
       Set PNUM=$Piece(REC,"^",1)
       Set PDESC=$Piece(REC,"^",2)
       ; If entry does exist, get the old indice values.
       If IEN Do
       . Set OPNUM=$Piece(^MP1PARTS(IEN),"^",1)
       . Set OPDESC=$Piece(^MP1PARTS(IEN),"^",2)
       ; If entry does not exist, get a new Internal Entry Number (IEN).
       Else  Do
       . ; Init old values to null - new record.
       . Set (OPNUM,OPDESC)=""
       . Lock +^MP1PARTS(0):0 ; Increment lock count on node. Force timeout.
       . Else  Write !?5,"Cannot get next node number – record not filed." Quit
       . If '$Data(^MP1PARTS(0)) Set ^MP1PARTS(0)=0
       . Set (IEN,^MP1PARTS(0))=^MP1PARTS(0)+1
       . Lock -^MP1PARTS(0) ; Decrement lock count on node.
       ; Make sure indices do not exist - multiple indices not permittted.
       If PNUM'=OPNUM,$Data(^MP1PARTS("B",PNUM)) Do
       . Write !?5,"Number "_PNUM_" is already being used."
       If PDESC'=OPDESC,$Data(^MP1PARTS("C",PDESC)) Do
       . Write !?5,"Description "_PDESC_" is already being used."
       If 'ERR Do  ;File if no errors.
       . ; Lock record node incrementally.
       . Lock +^MP1PARTS(IEN):0 ; Increment lock count on record. Force timeout.
       . ; If timeout occurred and no lock then pass back record busy message.
       . Else  Write !?5,"Record busy – record not filed." Quit
       . ; Remove old indices.
       . Kill:OPNUM]""&(OPNUM'=PNUM) ^MP1PARTS("B",OPNUM)
       . Kill:OPDESC]""&(OPDESC'=PDESC) ^MP1PARTS("C",OPDESC)
       . ; Set the new record and indices nodes.
       . Set ^MP1PARTS(IEN)=REC
       . Set ^MP1PARTS("B",PNUM,IEN)=""
       . Set ^MP1PARTS("C",PDESC,IEN)=""
       . ; Incrementally unlock record node.
       . Lock -^MP1PARTS(IEN) ; Decrement lock count on node
       Quit ERR
       ;
  7. Of course, this function takes the Parts record in the variable REC. Since the model side is responsible for record structure, it must contain a function to construct it. Add the following extrinsic function to the MP1PDATA routine. It is called by the view side just before the PUT is called.
    Code:
    SETREC(PNUM,PDESC,PQTY,PLVL,PRC,PSP) ; Construct Parts record.
        ; Parameters are passed by reference, not value.
        Quit PNUM_"^"_PDESC_"^"_PQTY_"^"_PLVL_"^"_PRC_"^"_PSP
        ;
  8. Also, we will introduce more formalized error reporting keeping in mind that in a real application environment, this is probably already written and its use is generally required .
  9. Now that we have the PUT subroutine separated and reusable, lets extract the Get code and make it reusable. Add this code the MP1PDATA routine.
    Code:
    GET(Key) ; Return record based on primary key look up
        ; Determine the correct response to Key being null?
        New REC
        If $Data(^MP1PARTS("B",Key)) Do
        . ;Edit entry
        . Set REC=$Order(^MP1PARTS("B",Key,""))_";"_^MP1PARTS(IEN)
        Else  Do
        . ;Create Entry
        . Set REC=0_";"
        Quit REC
        ;
  10. Replace the code in the PNUM query with the $$GET^MP1PDATA call.
  11. At this point we have separated the model side functions (GET and PUT) and stored them in a separate routine (MP1PDATA). We separated out the view side (ASK) and stored it in a separate routine (MP1PVIEW). The MP1PEDIT routine is the controller and is independent of all model and view side dependencies except the syntax checking code.
  12. The syntax code actually contains the pattern matching code for each data element. These are business rules. In a system that supports a more sophisticated database system, these rules would reside in a data dictionary along with all other information germain to each data elements.
  13. We will extract the checks from the controller code and place them in the model side routine MP1PDATA.
    Code:
    PNUM(X) ;Check PNUM syntax
        Quit $Select(X?1.6N:1,1:0)
        ;
    PDESC(X) ;Check PDESC syntax
        Quit $Select(X?1A.29NAP:1,1:0)
        ;
    PQTY(X) ;Check PNUM syntax
        Quit $Select(X?1.N:1,1:0)
        ;
    PLVL(X) ;Check PLVL syntax
        Quit $Select(X?1.N:1,1:0)
        ;
    PRC(X) ;Check PRC syntax
        Quit $Select(X?.N.1".".2N:1,1:0)
        ;
    PSP(X) ;Check PSP syntax
        Quit $Select(X?.N.1".".2N:1,1:0)
        ;

_________________
Terry L. Wiechmann


Top
Offline Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 1 post ] 

All times are UTC - 8 hours [ DST ]


Who is online

Users browsing this forum: No registered users and 7 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
Theme created StylerBB.net