Chaining DB Insertions Without Explicitly Checking for Success

65 views Asked by At

I am trying to figure out if there is a way to avoid lots of case statements while inserting records into the DB.

My current code sort of looks like this:

mt1 <- runDb $ do
        muid <- insertUnique user
        case muid of 
            Just uid -> do
                let t1 = Table1 {..., user = uid} 
                maid  <- insertUnique t1
                case maid of
                    Just aid -> do 
                        mo <- getBy $ UniqueField "somevalue" 
                        case mo of
                            Just (Entity oid o) -> do
                                mcid <- insertUnique Table2 {..., oid = oid} 
                                case mcid of
                                    Just cid -> do 
                                        mfid <- insertUnique Table3 {..., cid = cid} 
                                        case mfid of 
                                            Just fid -> Just t1 -- original t1 that was created at the top of the chain
                                            Nothing  -> Nothing
                                    Nothing  -> Nothing 
                            Nothing -> Nothing 
                    Nothing -> Nothing 
                Nothing -> Nothing
            Nothing  -> Nothing

First of all, I have issues getting the code to compile but instead of trying to debug that, I wanted to see if there is a better way to do this.

At a conceptual level, I want to do something like below, where all the Maybe values get unwrapped automatically to be used in subsequent invocations. If any point, we hit a Nothing, I just want to return Nothing. The whole code will run in a single transaction so if we hit a Nothing in between, the transaction is rolled back

runDb $ do
    uid <- insertUnique user
    let t1 = Table1 {..., user = uid} -- uid is unwrapped to just the value 
    aid  <- insertUnique t1
    o <- getBy $ UniqueField "somevalue"
    cid <- insertUnique Table2 {..., oid = oid}
    fid <- insertUnique Table3 {..., cid = cid} 
    Just t1 

I am Haskell beginner so I only have superficial understanding of Monads (I can use the simple ones fine) but when it comes to use it inside something like runDb of Persistent, I have no idea how to put the pieces together.

Any suggestions on how I can simply the logic so I don't end up checking for failure each step of the way?

Update: Based on Michael's answer, I did something like this and it automatically unwraps the maybes when used.

mt1 <- runDb $ runMaybeT $ do 
           uid <- MaybeT $ insertUnique user
           ... 
case mt1 of
    Just t -> return t
    Nothing -> lift $ left ... 

Thanks!

1

There are 1 answers

2
Michael Snoyman On BEST ANSWER

The standard approach to something like this is the MaybeT monad transformer. Something like the following will probably work:

runMaybeT $ do
    uid <- MaybeT $ insertUnique user
    ...