{-# OPTIONS_CYMAKE -Wno-incomplete-patterns #-} module Spicey.ControllerGeneration where import Data.Char ( toLower ) import Debug.Trace import AbstractCurry.Types import AbstractCurry.Build import Database.ERD import Database.ERD.Goodies import Spicey.GenerationHelper -- Name of entity-specific authorization module: enauthModName :: String enauthModName = "System.AuthorizedActions" -- Name of module defining the default controller: defCtrlModName :: String defCtrlModName = "Controller.DefaultController" -- "main"-function generateControllersForEntity :: String -> [Entity] -> Entity -> [Relationship] -> CurryProg generateControllersForEntity erdname allEntities entity@(Entity ename attrlist) relationships = let noKeyAttrs = filter (\a -> notKey a && notPKey a) attrlist in CurryProg (controllerModuleName ename) -- imports: [ timeModule , "HTML.Base", "HTML.Session", "HTML.WUI" , model erdname , "Config.EntityRoutes", "Config.UserProcesses" , sessionInfoModule, authorizationModule, enauthModName, spiceyModule , "System.PreludeHelpers" , entitiesToHtmlModule erdname , viewModuleName ename ] Nothing -- defaultdecl [] -- classdecls [] -- instdecls [newEntityType erdname entity relationships allEntities] -- typedecls -- functions ( [ -- controller for dispatching to various controllers: mainController erdname entity relationships allEntities, -- controller for providing a page to enter new entity data: --newController erdname entity relationships allEntities, newController erdname (Entity ename noKeyAttrs) relationships allEntities, newForm erdname (Entity ename noKeyAttrs) relationships allEntities, newStore erdname (Entity ename noKeyAttrs) relationships allEntities, -- transaction for saving data in new entity: createTransaction erdname entity relationships allEntities, -- controller to show an existing record in a form to edit editController erdname entity relationships allEntities, editForm erdname (Entity ename noKeyAttrs) relationships allEntities, editStore erdname (Entity ename noKeyAttrs) relationships allEntities, -- transaction to update a record with the given data updateTransaction erdname entity relationships allEntities, -- controller to delete an entity with the given data deleteController erdname entity relationships allEntities, -- controller to destroy an entity with the given data destroyController erdname entity relationships allEntities, -- transaction to delete an entity with the given data deleteTransaction erdname entity relationships allEntities, -- controller to list all entities: listController erdname entity relationships allEntities, -- controller to show entites: showController erdname entity relationships allEntities ] ++ manyToManyAddOrRemove erdname entity (manyToMany allEntities entity) allEntities ++ --(getAll erdname entity (manyToOne entity relationships) allEntities) ++ --(getAll erdname entity (manyToMany allEntities entity) allEntities) ++ --(manyToManyGetRelated erdname entity (manyToMany allEntities entity) allEntities) ++ manyToOneGetRelated erdname entity (manyToOne entity relationships) allEntities relationships ) [] -- opdecls -- erdname: name of the entity-relationship-specification -- entity: the entity to generate a controller for type ControllerGenerator = String -> Entity -> [Relationship] -> [Entity] -> CFuncDecl -- Generates the main controller that dispatches to the various -- subcontrollers according to the URL parameters. mainController :: ControllerGenerator mainController _ (Entity entityName _) _ _ = controllerFunction ("Choose the controller for a "++entityName++ " entity according to the URL parameter.") entityName "main" 0 controllerType -- function type [simpleRule [] -- no arguments (doExpr [CSPat (CPVar (1,"args")) (constF (spiceyModule,"getControllerParams")), CSExpr (CCase CRigid (CVar (1,"args")) ([cBranch (listPattern []) (constF (controllerFunctionName entityName "list")), cBranch (listPattern [stringPattern "list"]) (constF (controllerFunctionName entityName "list")), cBranch (listPattern [stringPattern "new"]) (constF (controllerFunctionName entityName "new"))] ++ map applyControllerBranch ["show", "edit", "delete", "destroy"] ++ [cBranch (CPVar (3,"_")) (constF (spiceyModule, "displayUrlError"))]) ) ] )] where applyControllerBranch n = let svar = (2,"s") in cBranch (listPattern [stringPattern n, CPVar svar]) (applyF (spiceyModule,"controllerOnKey") [CVar svar, constF (controllerFunctionName entityName n)]) --- Generates a type alias for a "new entity" tuple type which is --- used to create and insert new entities (without an entity key). newEntityType :: String -> Entity -> [Relationship] -> [Entity] -> CTypeDecl newEntityType _ (Entity entityName attrList) relationships allEntities = let notGeneratedAttributes = filter (\attr -> not (isForeignKey attr) && notPKey attr) attrList manyToManyEntities = manyToMany allEntities (Entity entityName attrList) manyToOneEntities = manyToOne (Entity entityName attrList) relationships in CTypeSyn (newEntityTypeName entityName) Private [] (tupleType (map attrType notGeneratedAttributes ++ map ctvar manyToOneEntities ++ map (listType . ctvar . fst) manyToManyEntities)) ------------------------------------------------------------------------------ -- generates a controller to show a form to create a new entity -- the input is then passed to the create controller -- only has to call the blank entry form and pass the create controller newController :: ControllerGenerator newController erdname (Entity entityName attrList) relationships allEntities = controllerFunction ("Shows a form to create a new " ++ entityName ++ " entity.") entityName "new" 0 controllerType -- function type [simpleRule [] $ -- no arguments applyF (pre "$") [applyF checkAuthorizationFunc [applyF (enauthModName, lowerFirst entityName ++ "OperationAllowed") [constF (authorizationModule,"NewEntity")]], CLambda [CPVar infovar] $ doExpr ( map (\ ((ename,erel), num) -> CSPat (CPVar (num,"all" ++ erel ++ ename ++ "s")) (applyF (model erdname,"runQ") [constF (model erdname,"queryAll" ++ ename ++ "s")]) ) (zip (map (\n -> (n,"")) manyToOneEntities ++ manyToManyEntities) [2..]) ++ (if hasDateAttribute attrList then [CSPat (CPVar ctimevar) (constF (timeModule,"getClockTime"))] else []) ++ (if null manyToOneEntities then setParCallAndReturn else [CSExpr $ ifThenElseExp (foldr1 (\e1 e2 -> applyF (pre "||") [e1,e2]) (map (\ (name, varId) -> applyF (pre "null") [CVar (varId, "all" ++ name ++ "s")]) (zip manyToOneEntities [2..]))) (applyF (pre "return") [ list2ac [applyF (html "h2") [list2ac [applyF (html "htxt") [string2ac missingError]]]]]) (doExpr setParCallAndReturn)] ))]] where manyToManyEntities = manyToMany allEntities (Entity entityName attrList) manyToOneEntities = manyToOne (Entity entityName attrList) relationships missingError = "Some related entities are required but not yet undefined" infovar = (0,"sinfo") ctimevar = (1,"ctime") setParCall = applyF (wuiModule "setParWuiStore") [constF (controllerStoreName entityName "new"), tupleExpr ([CVar infovar] ++ map (\ ((ename,erel), i) -> CVar (i, "all" ++ erel ++ ename ++ "s")) (zip (map (\n -> (n,"")) manyToOneEntities ++ manyToManyEntities) [2 ..])), tupleExpr $ attrDefaultValues (CVar ctimevar) attrList ++ map (\ (name, varId) -> applyF (pre "head") [CVar (varId, "all" ++ name ++ "s")]) (zip manyToOneEntities [2..]) ++ map (\_ -> list2ac []) (zip manyToManyEntities [2..]) ] setParCallAndReturn = [CSExpr setParCall, CSExpr $ applyF (pre "return") [list2ac [applyF (html "formElem") [constF (controllerFormName entityName "new")]]]] --- Generates the form definition to create a new entity. newForm :: String -> Entity -> [Relationship] -> [Entity] -> CFuncDecl newForm erdname entity@(Entity entityName attrlist) relationships allEntities = cmtfunc ("A WUI form to create a new " ++ entityName ++ " entity.\n" ++ "The default values for the fields are stored in '" ++ snd (controllerStoreName entityName "new") ++ "'.") (controllerFormName entityName "new") 0 Public (emptyClassType $ applyTC (htmlModule "HtmlFormDef") [newTupleType entity relationships allEntities]) [simpleRule [] (applyF (wuiModule "pwui2FormDef") [string2ac $ showQName $ controllerFormName entityName "new", constF (controllerStoreName entityName "new"), wuiFun, storeFun, renderFun])] where manyToManyEntities = map (\ (ent,rel) -> rel ++ ent) (manyToMany allEntities (Entity entityName attrlist)) manyToOneEntities = manyToOne (Entity entityName attrlist) relationships arity1 = 1 + length manyToOneEntities + length manyToManyEntities listEntityURL = '?' : entityName ++ "/list" wuiFun = CLambda [tuplePattern ([CPVar (1,"_")] ++ map (\ (name, varId) -> CPVar(varId,("possible"++name++"s"))) (zip (manyToOneEntities++manyToManyEntities) [2..]))] $ applyF (viewModuleName entityName, "w" ++ entityName) (map (\ (name, varId) -> CVar(varId,("possible"++name++"s"))) (zip (manyToOneEntities ++ manyToManyEntities) [2..]) ) storeFun = let entvar = (1, "entity") newentvar = (2, "newentity") in CLambda [CPVar (0,"_"), CPVar entvar] (applyF checkAuthorizationFunc [applyF (enauthModName, lowerFirst entityName ++ "OperationAllowed") [constF (authorizationModule,"NewEntity")], CLambda [CPVar (0,"_")] (applyF (spiceyModule,"transactionController") [applyF (model erdname,"runT") [applyF (transFunctionName entityName "create") [CVar entvar]], CLambda [CPVar newentvar] (doExpr [CSExpr $ applyF (spiceyModule,"setPageMessage") [string2ac $ "New " ++ entityName ++ " created"], CSExpr $ applyF (spiceyModule,"nextInProcessOr") [applyF (spiceyModule,"redirectController") [applyF (spiceyModule,"showRoute") [CVar newentvar]], constF (pre "Nothing")]])])]) renderFun = CLambda [tuplePattern (map CPVar (sinfovar : map (\v -> (v,"_")) [2 .. arity1]))] $ letExpr [CLocalPat (CPVar entvar) (simpleRhs (simpleTyped (constF (pre "failed")) (baseType (model erdname, entityName))))] (applyF (spiceyModule,"renderWUI") [CVar sinfovar, string2ac $ "Create new " ++ entityName, string2ac "Create", applyF (spiceyModule,"listRoute") [CVar entvar], constF (pre "()") ]) where sinfovar = (1, "sinfo") entvar = (2, "phantom") --- Generates the store for WUI to create a new entity. newStore :: String -> Entity -> [Relationship] -> [Entity] -> CFuncDecl newStore _ entity@(Entity entityName _) relationships allEntities = cmtfunc "The data stored for executing the \"new entity\" WUI form." (controllerStoreName entityName "new") 0 Private (emptyClassType $ applyTC (sessionModule "SessionStore") [newTupleType entity relationships allEntities]) [simpleRule [] (applyF (sessionModule "sessionStore") [string2ac $ "new" ++ entityName ++ "Store"])] --- Computes the tuple type of the data to be stored and manipulated --- by the WUI to create a new entity. newTupleType :: Entity -> [Relationship] -> [Entity] -> CTypeExpr newTupleType (Entity entityName attrlist) relationships allEntities = tupleType [tupleType $ [userSessionInfoType] ++ map (listType . ctvar) (manyToOneEntities ++ map fst manyToManyEntities), -- possible values applyTC (wuiModule "WuiStore") [baseType (newEntityTypeName entityName)]] where manyToManyEntities = manyToMany allEntities (Entity entityName attrlist) manyToOneEntities = manyToOne (Entity entityName attrlist) relationships --- Generates a transaction to store a new entity. createTransaction :: ControllerGenerator createTransaction erdname (Entity entityName attrList) relationships allEntities = stCmtFunc ("Transaction to persist a new " ++ entityName ++ " entity to the database.") (transFunctionName entityName "create") 1 Private (baseType (newEntityTypeName entityName) ~> applyTC (dbconn "DBAction") [baseType (model erdname,entityName)]) [simpleRule [tuplePattern (map (\ (param, varId) -> CPVar (varId, param)) (zip (parameterList ++ map lowerFirst manyToOneEntities ++ map (\ (e,r) -> lowerFirst r ++ e ++ "s") manyToManyEntities) [1..])) ] -- parameter list for controller (doExpr $ CSPat (cpvar "newentity") (applyF (entityConstructorFunction erdname (Entity entityName attrList) relationships) (map (\ ((Attribute name dom key null), varId) -> if (isForeignKey (Attribute name dom key null)) then applyF (model erdname, lowerFirst (getReferencedEntityName dom) ++ "Key") [CVar (varId, lowerFirst (getReferencedEntityName dom))] else let cv = CVar (varId, lowerFirst name) in if hasDefault dom && not (isStringDom dom) && not null then applyF (pre "Just") [cv] else cv) (zip noPKeys [1..]) )) : map (\ (en,rel) -> CSExpr $ applyF (controllerModuleName entityName, "add" ++ rel) [cvar (lowerFirst rel ++ en ++ "s"), cvar "newentity"]) manyToManyEntities ++ [CSExpr $ applyF (pre "return") [cvar "newentity"]]) ] where noPKeys = (filter notPKey attrList) -- foreignKeys = (filter isForeignKey attrList) -- notGeneratedAttributes = filter (\attr -> (not (isForeignKey attr)) -- && (notPKey attr)) attrList parameterList = map (\(Attribute name _ _ _) -> lowerFirst name) (filter (not . isForeignKey) noPKeys) manyToManyEntities = manyToMany allEntities (Entity entityName attrList) manyToOneEntities = manyToOne (Entity entityName attrList) relationships ------------------------------------------------------------------------------ --- Generates a controller to edit an entity. editController :: ControllerGenerator editController erdname (Entity entityName attrList) relationships allEntities = controllerFunction ("Shows a form to edit the given " ++ entityName ++ " entity.") entityName "edit" 1 (baseType (model erdname,entityName) ~> controllerType) [simpleRule [CPVar pvar] -- parameterlist for controller (applyF (pre "$") [applyF checkAuthorizationFunc [applyF (enauthModName,lowerFirst entityName ++ "OperationAllowed") [applyF (authorizationModule,"UpdateEntity") [CVar pvar]]], CLambda [CPVar infovar] $ doExpr ( map (\ ((ename,erel), num) -> CSPat (CPVar (num,"all" ++ erel ++ ename ++ "s")) (applyF (model erdname,"runQ") [constF (model erdname,"queryAll" ++ ename ++ "s")])) (zip (map (\n -> (n,"")) manyToOneEntities ++ manyToManyEntities) [1..]) ++ map (\ (ename, num) -> CSPat (CPVar (num,(lowerFirst (fst $ relationshipName entityName ename relationships))++ename)) ( applyF (model erdname,"runJustT") [ applyF (controllerModuleName entityName,"get"++(fst $ relationshipName entityName ename relationships)++ename) [CVar pvar] ] ) ) (zip manyToOneEntities [1..]) ++ map (\ ((ename,erel), num) -> CSPat (CPVar (num, lowerFirst erel ++ ename ++ "s")) (applyF (model erdname,"runJustT") [applyF (controllerModuleName entityName, "get" ++ erel ++ entityName ++ ename ++ "s") [CVar pvar]]) ) (zip manyToManyEntities [1..]) ++ [CSExpr setParCall, CSExpr $ applyF (pre "return") [list2ac [applyF (html "formElem") [constF (controllerFormName entityName "edit")]]] ])])] where manyToManyEntities = manyToMany allEntities (Entity entityName attrList) manyToOneEntities = manyToOne (Entity entityName attrList) relationships pvar = (0, lowerFirst entityName ++ "ToEdit") infovar = (1, "sinfo") setParCall = applyF (wuiModule "setParWuiStore") [constF (controllerStoreName entityName "edit"), tupleExpr ([CVar infovar, CVar pvar] ++ map (\ (ename, num) -> CVar (num,lowerFirst (fst $ relationshipName entityName ename relationships) ++ ename)) (zip manyToOneEntities [1..]) ++ map (\ ((ename,erel), num) -> CVar (num, "all" ++ erel ++ ename ++ "s")) (zip (map (\n -> (n,"")) manyToOneEntities ++ manyToManyEntities) [1..])), tupleExpr ([CVar pvar] ++ (map (\ ((ename,erel), num) -> CVar (num,lowerFirst erel ++ ename ++ "s")) (zip manyToManyEntities [1..])))] --- Generates the form definition to edit an entity. editForm :: String -> Entity -> [Relationship] -> [Entity] -> CFuncDecl editForm erdname entity@(Entity entityName attrlist) relationships allEntities = cmtfunc ("A WUI form to edit a " ++ entityName ++ " entity.\n" ++ "The default values for the fields are stored in '" ++ snd (controllerStoreName entityName "edit") ++ "'.") (controllerFormName entityName "edit") 0 Public (emptyClassType $ applyTC (htmlModule "HtmlFormDef") [editTupleType erdname entity relationships allEntities]) [simpleRule [] (applyF (wuiModule "pwui2FormDef") [string2ac $ showQName $ controllerFormName entityName "edit", constF (controllerStoreName entityName "edit"), wuiFun, storeFun, renderFun])] where manyToManyEntities = manyToMany allEntities (Entity entityName attrlist) manyToOneEntities = manyToOne (Entity entityName attrlist) relationships arity1 = 2 + length manyToOneEntities * 2 + length manyToManyEntities wuiFun = let relatedVars vart = map (\ (name, varId) -> vart (varId, "related" ++ name)) (zip manyToOneEntities [2..]) possibleVars vart = map (\ ((ename,erel), varId) -> vart (varId, "possible" ++ erel ++ ename ++ "s")) (zip (map (\n -> (n,"")) manyToOneEntities ++ manyToManyEntities) [2..]) in CLambda [tuplePattern ([CPVar (1,"_"), CPVar (1, lowerFirst entityName)] ++ relatedVars CPVar ++ possibleVars CPVar)] $ applyF (viewModuleName entityName, "w" ++ entityName ++ "Type") ([cvar (lowerFirst entityName)] ++ relatedVars CVar ++ possibleVars CVar) storeFun = let evar = (1, lowerFirst entityName ++ "ToEdit") entvar = (2, "entity") in CLambda [CPVar (0,"_"), CPAs entvar (tuplePattern ([CPVar evar] ++ map (\i -> CPVar (i+2,"_")) [1 .. length manyToManyEntities]))] (applyF checkAuthorizationFunc [applyF (enauthModName,lowerFirst entityName ++ "OperationAllowed") [applyF (authorizationModule,"UpdateEntity") [CVar evar]], CLambda [CPVar (0,"_")] (applyF (spiceyModule,"transactionController") [applyF (model erdname,"runT") [applyF (transFunctionName entityName "update") [CVar entvar]], applyF (pre "const") [doExpr [CSExpr $ applyF (spiceyModule,"setPageMessage") [string2ac $ entityName ++ " updated"], CSExpr $ applyF (spiceyModule,"nextInProcessOr") [applyF (spiceyModule,"redirectController") [applyF (spiceyModule,"showRoute") [CVar evar]], constF (pre "Nothing")]]]])]) renderFun = CLambda [tuplePattern (map CPVar (sinfovar : entvar : map (\v -> (v,"_")) [3 .. arity1]))] $ applyF (spiceyModule,"renderWUI") [CVar sinfovar, string2ac $ "Edit " ++ entityName, string2ac "Change", applyF (spiceyModule,"listRoute") [CVar entvar], constF (pre "()") ] where sinfovar = (1, "sinfo") entvar = (2, "entity") --- Generates the store for WUI to edit an entity. editStore :: String -> Entity -> [Relationship] -> [Entity] -> CFuncDecl editStore erdname entity@(Entity entityName _) relationships allEntities = cmtfunc "The data stored for executing the edit WUI form." (controllerStoreName entityName "edit") 0 Private (emptyClassType $ applyTC (sessionModule "SessionStore") [editTupleType erdname entity relationships allEntities]) [simpleRule [] (applyF (sessionModule "sessionStore") [string2ac $ "edit" ++ entityName ++ "Store"])] --- Computes the tuple type of the data to be stored and manipulated --- by the WUI to edit a new entity. editTupleType :: String -> Entity -> [Relationship] -> [Entity] -> CTypeExpr editTupleType erdname (Entity entityName attrlist) relationships allEntities = tupleType [tupleType $ [userSessionInfoType, baseType (model erdname, entityName)] ++ map ctvar manyToOneEntities ++ -- defaults for n:1 map (listType . ctvar) (manyToOneEntities ++ map fst manyToManyEntities), -- possible values applyTC (wuiModule "WuiStore") [tupleType $ [baseType (model erdname, entityName)] ++ map (listType . ctvar . fst) manyToManyEntities]] where manyToManyEntities = manyToMany allEntities (Entity entityName attrlist) manyToOneEntities = manyToOne (Entity entityName attrlist) relationships --- Generates the transaction to update an entity. updateTransaction :: ControllerGenerator updateTransaction erdname (Entity entityName attrList) _ allEntities = stCmtFunc ("Transaction to persist modifications of a given " ++ entityName ++ " entity\nto the database.") (transFunctionName entityName "update") 2 Private (tupleType ([baseType (model erdname, entityName)] ++ map (\ (name,_) -> listType (ctvar name)) manyToManyEntities) ~> applyTC (dbconn "DBAction") [unitType]) [simpleRule [tuplePattern ([CPVar (0, lowerFirst entityName)] ++ (map (\ (param, varId) -> CPVar (varId, param)) (zip (map (\ (ename,erel) -> lowerFirst erel ++ ename ++ "s") manyToManyEntities) [1..]))) ] -- parameter list for controller (doExpr $ (CSExpr (applyF (model erdname, "update" ++ entityName) [cvar (lowerFirst entityName)]) : (concatMap (\ (ename,erel) -> [CSPat (CPVar (0, "old" ++ erel ++ ename ++ "s")) (applyF (controllerModuleName entityName, "get" ++ erel ++ entityName ++ ename ++ "s") [cvar (lowerFirst entityName)]), CSExpr (applyF (controllerModuleName entityName, "remove" ++ erel) [cvar ("old"++ erel ++ ename ++ "s"), cvar (lowerFirst entityName)])]) manyToManyEntities ) ++ (map (\ (ename,erel) -> CSExpr $ applyF (controllerModuleName entityName, "add" ++ erel) [cvar (lowerFirst erel ++ ename ++ "s"), cvar (lowerFirst entityName)]) manyToManyEntities) ))] where manyToManyEntities = manyToMany allEntities (Entity entityName attrList) -- manyToOneEntities = manyToOne (Entity entityName attrList) relationships -- noPKeys = (filter notPKey attrList) ------------------------------------------------------------------------------ --- Generates controller to delete an entity after confirmation. deleteController :: ControllerGenerator deleteController erdname (Entity entityName _) _ _ = let entlc = lowerFirst entityName -- entity name in lowercase entvar = (0, entlc) -- entity parameter for controller sinfovar = (1, "sinfo") -- "sinfo" parameter in controllerFunction ("Deletes a given "++entityName++" entity (after asking for confirmation)\n"++ "and proceeds with the list controller.") entityName "delete" 1 (baseType (model erdname, entityName) ~> controllerType) [simpleRule [CPVar entvar] (applyF (pre "$") [applyF checkAuthorizationFunc [applyF (enauthModName,entlc++"OperationAllowed") [applyF (authorizationModule,"DeleteEntity") [CVar entvar]]], CLambda [CPVar sinfovar] $ applyF (spiceyModule,"confirmDeletionPage") [CVar sinfovar, applyF (pre "concat") [list2ac [string2ac "Really delete entity \"", applyF (entitiesToHtmlModule erdname, entlc ++ "ToShortView") [CVar entvar], string2ac "\"?"]]]])] --- Generates controller to delete an entity. destroyController :: ControllerGenerator destroyController erdname (Entity entityName _) _ _ = let entlc = lowerFirst entityName -- entity name in lowercase entvar = (0, entlc) -- entity parameter for controller listEntityURL = '?' : entityName ++ "/list" in controllerFunction ("Deletes a given " ++ entityName ++ " entity\n" ++ "and proceeds with the list controller.") entityName "destroy" 1 (baseType (model erdname, entityName) ~> controllerType) [simpleRule [CPVar entvar] (applyF (pre "$") [applyF checkAuthorizationFunc [applyF (enauthModName,entlc++"OperationAllowed") [applyF (authorizationModule,"DeleteEntity") [CVar entvar]]], CLambda [CPVar (0,"_")] $ applyF (spiceyModule,"transactionController") [applyF (model erdname,"runT") [applyF (transFunctionName entityName "delete") [CVar entvar]], applyF (pre "const") [doExpr [CSExpr $ applyF (spiceyModule,"setPageMessage") [string2ac $ entityName ++ " deleted"], CSExpr $ applyF (spiceyModule,"redirectController") [applyF (spiceyModule,"listRoute") [CVar entvar]]]]]])] --- Generates a transaction to delete an entity. deleteTransaction :: ControllerGenerator deleteTransaction erdname (Entity entityName attrs) _ allEntities = let manyToManyEntities = manyToMany allEntities (Entity entityName attrs) entlc = lowerFirst entityName -- entity name in lowercase entvar = (0, entlc) -- entity parameter for trans. in stCmtFunc ("Transaction to delete a given " ++ entityName ++ " entity.") (transFunctionName entityName "delete") 1 Private (baseType (model erdname, entityName) ~> applyTC (dbconn "DBAction") [unitType]) [simpleRule [CPVar entvar] -- entity parameter for controller (doExpr $ concatMap (\ (ename,erel) -> [CSPat (CPVar(0, "old" ++ erel ++ ename ++ "s")) (applyF (controllerModuleName entityName, "get" ++ erel ++ entityName ++ ename ++ "s") [CVar entvar]), CSExpr (applyF (controllerModuleName entityName, "remove" ++ erel) [cvar ("old" ++ erel ++ ename ++ "s"), CVar entvar ])] ) manyToManyEntities ++ [CSExpr $ applyF (model erdname, "delete" ++ entityName) [CVar entvar]])] ------------------------------------------------------------------------------ listController :: ControllerGenerator listController erdname (Entity entityName _) _ _ = controllerFunction ("Lists all " ++ entityName ++ " entities with buttons to show, delete,\n"++ "or edit an entity.") entityName "list" 0 controllerType [simpleRule [] -- no arguments (applyF (pre "$") [applyF checkAuthorizationFunc [applyF (enauthModName,lowerFirst entityName ++ "OperationAllowed") [applyF (authorizationModule,"ListEntities") []]], CLambda [CPVar infovar] $ doExpr [CSPat (CPVar entsvar) (applyF (model erdname,"runQ") [constF (model erdname,"queryAll" ++ entityName ++ "s")]), CSExpr (applyF (pre "return") [applyF (viewFunctionName entityName "list") [CVar infovar, CVar entsvar]]) ] ] )] where infovar = (0, "sinfo") entsvar = (1, lowerFirst entityName ++ "s") ------------------------------------------------------------------------------ showController :: ControllerGenerator showController erdname (Entity entityName attrs) relationships allEntities = let manyToManyEntities = manyToMany allEntities (Entity entityName attrs) manyToOneEntities = manyToOne (Entity entityName attrs) relationships pvar = (0, lowerFirst entityName) infovar = (1, "sinfo") in controllerFunction ("Shows a " ++ entityName ++ " entity.") entityName "show" 1 (baseType (model erdname,entityName) ~> controllerType) [simpleRule [CPVar pvar] -- parameterlist for controller (applyF (pre "$") [applyF checkAuthorizationFunc [applyF (enauthModName,lowerFirst entityName ++ "OperationAllowed") [applyF (authorizationModule,"ShowEntity") [CVar pvar]]], CLambda [CPVar infovar] $ doExpr ( map (\ (ename, num) -> CSPat (CPVar (num,lowerFirst (fst $ relationshipName entityName ename relationships) ++ ename)) (applyF (model erdname,"runJustT") [applyF (controllerModuleName entityName, "get"++ fst (relationshipName entityName ename relationships) ++ ename) [CVar pvar] ]) ) (zip (manyToOneEntities) [1..]) ++ map (\ ((ename,erel), num) -> CSPat (CPVar (num,lowerFirst erel ++ ename ++ "s")) (applyF (model erdname,"runJustT") [applyF (controllerModuleName entityName, "get" ++ erel ++ entityName ++ ename ++ "s") [CVar pvar]]) ) (zip manyToManyEntities [1..]) ++ [CSExpr ( applyF (pre "return") [applyF (viewFunctionName entityName "show") ([CVar infovar, CVar pvar] ++ (map (\ (ename, num) -> CVar (num,lowerFirst (fst $ relationshipName entityName ename relationships) ++ ename)) (zip (manyToOneEntities) [1..])) ++ (map (\ ((ename,erel), num) -> CVar (num,lowerFirst erel ++ ename ++ "s")) (zip manyToManyEntities [1..]))) ]) ]) ] ) ] manyToManyAddOrRemove :: String -> Entity -> [(String,String)] -> [Entity] -> [CFuncDecl] manyToManyAddOrRemove erdname (Entity entityName _) entityrels allEntities = map (addOrRemoveFunction "add" "new" entityName) entityrels ++ map (addOrRemoveFunction "remove" "delete" entityName) entityrels where addOrRemoveFunction :: String -> String -> String -> (String,String) -> CFuncDecl addOrRemoveFunction funcPrefix dbFuncPrefix e1 (e2,mmrel) = stCmtFunc ((if funcPrefix == "add" then "Associates given entities with the " ++ entityName ++ " entity" else "Removes association to the given entities with the " ++ entityName ++ " entity") ++ "\nwith respect to the `" ++ mmrel ++ "` relation.") (controllerModuleName e1, funcPrefix ++ mmrel) 2 Private (listType (ctvar e2) ~> ctvar e1 ~> applyTC (dbconn "DBAction") [tupleType []]) [simpleRule [CPVar (0, lowerFirst e2 ++ "s"), CPVar (1, lowerFirst e1)] (applyF (pre "mapM_") [CLambda [CPVar(2, "t")] (applyF (model erdname, dbFuncPrefix ++ mmrel) [applyF (model erdname, lowerFirst e1 ++ "Key") [cvar (lowerFirst e1)], applyF (model erdname, lowerFirst e2 ++ "Key") [cvar "t"]]), cvar (lowerFirst e2 ++ "s")])] getAll :: String -> Entity -> [String] -> [Entity] -> [CFuncDecl] getAll erdname (Entity entityName _) entities _ = map getAllFunction entities where getAllFunction :: String -> CFuncDecl getAllFunction foreignEntity = stCmtFunc ("Gets all " ++ foreignEntity ++ " entities.") (controllerModuleName entityName, "getAll" ++ foreignEntity ++ "s") 0 Private (ioType (listType (ctvar foreignEntity))) [simpleRule [] (applyF (model erdname,"runQ") [applyF (model erdname,"queryAll") [CLambda [CPVar(0, take 1 (lowerFirst foreignEntity) )] (CLetDecl [(CLocalVars [(1,"key")])] (applyF (model erdname, lowerFirst foreignEntity) [cvar "key", cvar (take 1 (lowerFirst foreignEntity))])) ] ] ) ] manyToOneGetRelated :: String -> Entity -> [String] -> [Entity] -> [Relationship] -> [CFuncDecl] manyToOneGetRelated erdname (Entity entityName _) entities _ relationships = map getRelatedFunction entities where getRelatedFunction :: String -> CFuncDecl getRelatedFunction foreignEntity = let argvar = (1, (take 1 $ lowerFirst entityName) ++ foreignEntity) rname = fst (relationshipName entityName foreignEntity relationships) fkeysel = lowerFirst entityName ++ foreignEntity ++ rname ++ "Key" in stCmtFunc ("Gets the associated " ++ foreignEntity ++ " entity for a given "++ entityName ++ " entity.") (controllerModuleName entityName, "get" ++ rname ++ foreignEntity) 0 Private ((ctvar entityName) ~> applyTC (dbconn "DBAction") [ctvar foreignEntity]) [simpleRule [CPVar argvar] (applyF (model erdname,"get" ++ foreignEntity) [applyF (model erdname,fkeysel) [CVar argvar]])] relationshipName :: String -> String -> [Relationship] -> (String, String) relationshipName e1 e2 (rel:relrest)= case rel of (Relationship name [(REnd relE1 _ _), (REnd relE2 relName _)]) -> if ((relE1 == e1 && relE2 == e2) || (relE1 == e2 && relE2 == e1)) then (name, relName) else relationshipName e1 e2 relrest relationshipName _ _ [] = error "relationshipName: relationship not found" ---- auxiliaries --- displayErrorFunction :: QName displayErrorFunction = (spiceyModule, "displayError") entityConstructorFunction :: String -> Entity -> [Relationship] -> QName entityConstructorFunction erdname (Entity entityName attrs) relationships = (model erdname, "new" ++ entityName ++ newSuffix entityName attrs relationships) -- entityName: Name of entity the controller should be generated for -- controllerType: the function of the generated Controller, e.g. "new", "edit", "list" -- arity -- functionType: the type of the controller function -- rules: the rules defining the controller controllerFunction :: String -> String -> String -> Int -> CTypeExpr -> [CRule] -> CFuncDecl controllerFunction description entityName controllerType arity functionType rules = stCmtFunc description (controllerFunctionName entityName controllerType) arity (if controllerType `elem` ["main"] then Public else Private) functionType rules getReferencedEntityName :: Domain -> String getReferencedEntityName t = case t of KeyDom kd -> kd _ -> "" relatedEntityNames :: Entity -> [Relationship] -> [String] relatedEntityNames (Entity entityName attrlist) relationships = map (\ (Relationship _ ((REnd name1 _ _) : (REnd name2 _ _) : [])) -> if name1 == entityName then name2 else name1) (relationshipsForEntity (Entity entityName attrlist) relationships) -- gets all relationships relationshipsForEntity :: Entity -> [Relationship] -> [Relationship] relationshipsForEntity (Entity entityName _) relationships = filter (\ (Relationship _ ((REnd name1 _ _) : (REnd name2 _ _):[])) -> name1 == entityName || name2 == entityName) (filter (not . isGeneratedR) relationships) ------ from ERD CodeGeneration newSuffix :: String -> [Attribute] -> [Relationship] -> String newSuffix eName attrs rels = let generatedRs = filter isGeneratedR rels exactRs = filter isExactB generatedRs --(i,i), i>1 maxRs = filter isMaxB generatedRs --(0,i), i>1 minMaxRs = filter isMinMaxB generatedRs --(i,j), i>0, j>i in concatMap ("With"++) (map attributeName (filter isForeignKey attrs)) ++ if (length (exactRs ++ maxRs ++ minMaxRs))==0 then "" else concatMap (\k->"With" ++ k ++ "Keys") (map (relatedRelation eName) (exactRs ++ maxRs ++ minMaxRs)) where isExactB (Relationship _ [REnd _ _ _, REnd _ _ c]) = case c of Exactly i -> i>1 _ -> False isMaxB (Relationship _ [REnd _ _ _, REnd _ _ c]) = case c of (Between 0 (Max i)) -> i>1 _ -> False isMinMaxB (Relationship _ [REnd _ _ _, REnd _ _ c]) = case c of (Between i (Max j)) -> i>0 && j>i _ -> False isGeneratedR :: Relationship -> Bool isGeneratedR (Relationship n _) = n == "" -- extracts the name of the relationship related to a given entity name relatedRelation :: String -> Relationship -> String relatedRelation en (Relationship _ [REnd en1 _ _, REnd en2 _ _]) = if en==en1 then en2 else en1 relationshipsForEntityName :: String -> [Relationship] -> [Relationship] relationshipsForEntityName ename rels = filter endsIn rels where endsIn (Relationship _ ends) = any (\ (REnd n _ _) -> ename == n) ends ------------------------------------------------------------------------ -- Auxiliaries: getUserSessionInfoFunc :: CExpr getUserSessionInfoFunc = constF (sessionInfoModule,"getUserSessionInfo") checkAuthorizationFunc :: QName checkAuthorizationFunc = (authorizationModule,"checkAuthorization") ------------------------------------------------------------------------