TCache-0.9.0.4: demos/triggerRelational.lhs
example of trigger and DBRef usage to maintain data relations
This is an example of trigger usage, but is a bad practice
For a better implementation of two way references, with this same example
see indexQuery.hs that uses Data.TCache.IndexQuery
mimic the example taken from http://docs.yesodweb.com/book/persistent
implements one to many relationships with the use of DBref's and triggers to maintain
the relationships.
> {-# LANGUAGE DeriveDataTypeable, FlexibleInstances, UndecidableInstances#-}
> module Main where
> import Data.TCache
> import Data.TCache.Triggers
> import Data.TCache.DefaultPersistence
> import Data.ByteString.Lazy.Char8(pack,unpack)
> import Control.Concurrent.STM
> import Data.List (delete,nub)
> import Data.Typeable
> import Control.Monad(when)
> import Debug.Trace
> import GHC.Conc
> data Person= Person{ pname :: String, cars :: [DBRef Car]} deriving (Show, Read, Typeable)
> data Car= Car{owner :: DBRef Person , cname:: String} deriving (Show, Read, Eq, Typeable)
> instance Indexable Person where key Person{pname=n} = "Person " ++ n
> instance Indexable Car where key Car{cname= n} = "Car " ++ n
> instance (Read a, Show a) => Serializable a where
> serialize= pack . show
> deserialize= read . unpack
every time a car is added, or deleted the owner's list is updated
this is done by the addCar trigger
> addCar pcar (Just(car@(Car powner _ ))) = addToOwner powner pcar
> addCar pcar Nothing = readDBRef pcar >>= \(Just car)-> deleteOwner (owner car) pcar
> addToOwner powner pcar=do
> Just owner <- readDBRef powner
> writeDBRef powner owner{cars= nub $ pcar : cars owner}
> deleteOwner powner pcar= do
> Just owner <- readDBRef powner
> writeDBRef powner owner{cars= Data.List.delete pcar $ cars owner}
> main= do
> addTrigger addCar
> putStrLn "create bruce's register with no cars"
> bruce <- atomically $ newDBRef $ Person "Bruce" []
> putStrLn "add two car register with \"bruce\" as owner using the reference to the bruces register"
> let newcars= [Car bruce "Bat Mobile" , Car bruce "Porsche"]
> insert newcars
> Just bruceData <- atomically $ readDBRef bruce
> putStrLn "the trigger automatically updated the car references of the Bruce register"
> print . length $ cars bruceData
> print bruceData
>
> insert= withResources [] . const
gives:
>main1
>2
>Person {pname = "Bruce", cars = [DBRef "Car Porsche",DBRef "Car Bat Mobile"]}
- -
the car can be sold, and the modification of the car register can come from
an owner change. The deletion of the car from the previous owner list can be
done because the trigger hook is called before the update, so
the DBRef maintain the old value.
So a better version of addCar is:
> betterAddCar pcar car@(Car powner _ ) = do
> updateNeeded <- processOldOwner powner pcar -- inserted
> when updateNeeded $ addToOwner powner pcar
> where
> processOldOwner powner pcar= do
> mc <- readDBRef pcar
> case mc of
> Nothing -> return True
> Just Car{owner= pother} ->
> if pother== powner
> then return False -- is unchanged, not necesary to update
> else do
> deleteOwner pother pcar
> return True