7

I have an ajax call sending json to a route in Yesod and I want the route to parse the json and insert it directly into the database. In my model file I have

createtime UTCTime default=now()

which is preventing parsing the json since the client isn't sending down the createtime. I tried to write my own parseJson for log entries, but I've been unable to insert a UTCTime default since getCurrentTime returns a value in the IO monad. I'd like the database to set the value, if possible.

The only thing I can think of at this point is to make a type like LogEntryWithoutTime, parse the JSON into that and convert to a LogEntry. Is there an easier way?

Edit: I show three different failures to add getCurrentTime to the JSON parse. First, the intent is to parse the createtime if available, and default to getCurrentTime on the server. This isn't right anyways, since we shouldn't rely on the client's time.

instance FromJSON Log where
    parseJSON (Object o) = Log
        <$> o .: "userid"
        ...
        <*> o .:? "createtime" .!= liftIO getCurrentTime

The error is

Model.hs:58:32:
Couldn't match expected type ‘UTCTime’
            with actual type ‘m0 UTCTime’
In the second argument of ‘(.!=)’, namely ‘liftIO getCurrentTime’
In the second argument of ‘(<*>)’, namely
  ‘o .:? "createtime" .!= liftIO getCurrentTime’

Second, I try to just get the current time.

<*> liftIO getCurrentTime

and I get the error

Model.hs:58:9:
No instance for (MonadIO
                   aeson-0.7.0.6:Data.Aeson.Types.Internal.Parser)
  arising from a use of ‘liftIO’
In the second argument of ‘(<*>)’, namely ‘liftIO getCurrentTime’

If I change the line to

<*> getCurrentTime

then I get

Model.hs:58:9:
Couldn't match type ‘IO’
              with ‘aeson-0.7.0.6:Data.Aeson.Types.Internal.Parser’
Expected type: aeson-0.7.0.6:Data.Aeson.Types.Internal.Parser
                 UTCTime
  Actual type: IO UTCTime
user3776949
  • 237
  • 2
  • 8

2 Answers2

1

The thing with the default attribute defined in a model is that you still need to define all the values when persisting entities. Defaults are there just for the automatic migrations and schema definitions, as far as I'm aware.

Try lifting the getCurrentTime from the IO monad (with liftIO).

ms.
  • 332
  • 1
  • 10
  • I see that the model file doesn't affect the Haskell code, so I ended up writing raw SQL to insert the row. I tried using liftIO when I posted this question and it didn't work, but I don't recall the exact error. I think it was trying to put it into another monad, but there was no other monad. – user3776949 Jul 13 '14 at 21:50
  • No instance for (MonadIO aeson-0.8.0.2:Data.Aeson.Types.Internal.Parser) – Christopher Gillis Jul 23 '15 at 02:45
0

I'm facing essentially the same problem now. My current thinking is that since I am not expecting the user to provide me with a complete record, I shouldn't be modelling it that way. Instead, I should model the request body I expect from the user, and transform that myself for storage.

So, you may have the concept of a PartialLog, which only has the fields you expect the user to send:

data PartialLog = PartialLog { partialLogMessage :: Text }

Then you might have a function that fills in the gaps and provides you with a complete record:

{-# LANGUAGE RecordWildCards #-}

logFromPartial :: PartialLog -> UserId -> IO Log
logFromPartial p u = do
  let logUserId  = u
      logMessage = partialLogMessage p
  logCreatetime <- liftIO getCurrentTime
  return Log{..}

n.b. I'm passing in the UserId because we might want that value to come from the authenticated session; there's no need for the user to tell us what their ID is.

Jezen Thomas
  • 13,619
  • 6
  • 53
  • 91