How to omit fields that will be filled in by the database - haskell

I've just started off with Persistent from Yesod and have already hit my first roadblock.
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
User
email String
createdAt UTCTime Maybe default=CURRENT_TIME
updatedAt UTCTime Maybe default=CURRENT_TIME
deriving Show
|]
u <- insert $ User "saurabhnanda#gmail.com" Nothing Nothing
I'm coming from a Rails background and like the schema design conventions advocated by them. In this particular case, having every table have a created_at and updated_at timestamp. However, is there any way to NOT specify the createdAt and updatedAt fields for every object that will be created?

I don't know much about Persistent, but the usual technique for this kind of thing is to define some parameterized types. For example, you might write
data Dated a = Dated
{ created :: UTCTime
, updated :: UTCTime
, value :: a
}
Then your bare values need not worry about their metadata, and metadata can be handled uniformly across all different types. e.g. you might expose an API like this:
new :: a -> IO (Dated a)
new a = do
now <- getUTCTime
return (Dated now now a)
update :: Dated a -> a -> IO (Dated a)
update old new = do
now <- getUTCTime
return (old { updated = now, value = new })
Then I would suggest exporting the Dated type constructor and all its fields, together with new and update, but not exporting the Dated data constructor. This will ensure that the created and updated fields are maintained correctly.

Related

How can I refactor duplicate field names in Haskell data types?

I'm trying to write a data structure that describes items on my CV, just to learn about Haskell and its data types. Here's what I have so far:
data Update = Talk { date :: Date,
pubTitle :: Text,
url :: URI,
venue :: Text
}
| Publication { date :: Date,
pubTitle :: Text,
venue :: Venue,
pubType :: PublicationType
}
| Award { date :: Date,
award :: Text,
awardedBy :: Link
}
| News { date :: Date, desc :: Markdown }
deriving Show
data PublicationType = Tutorial | Article | Chapter | Abstract
This won't work, apparently, since Haskell thinks I have multiple declarations of date and so on. I've seen this question where an answer suggests using an extension, but I tried adding {-# LANGUAGE DuplicateRecordFields #-} to the top of my file, and it doesn't seem to help—I'm getting the same errors.
I suspect that there's a way to refactor these records in some way, no? Or maybe I should be doing something different entirely to convey this kind of data structure.
One approach you could take would be to factor out the duplication:
data Update = Update Date Event
data Event = News Markdown
| Award Text Link
| Artifact { title :: Text
, venue :: Venue
, artifact :: Accomplishment
}
data Accomplishment = Talk URI
| Publication PublicationType
Here I've used record syntax for the Artifact constructor, but I'm not convinced it's the best approach. The partial accessors it creates are pretty gross; I just used it because it's otherwise not clear what the Text field means. You could clear this up using non-record syntax by defining a newtype around Text, or a type alias; or you could add a named Artifact type that contains the same thing, with the Artifact constructor just holding an Artifact.
Even with DuplicateRecordFields, you can't have two record fields with the same name on different data constructors of the same type if their types are different. In this case, the problem is that you have venue :: Text for Talk, but venue :: Venue for Publication. Either change one of their types or rename one of them. To understand why this restriction is necessary, think about what type the venue record selector function must be.
Also, it's generally a bad idea to use records on types that have more than one constructor (except in the edge case where all of the fields are the same), because it causes partiality. For example, doing awardedBy (News foo bar) will crash your program.

Query influxdb in Haskell

I'm thinking of developing an app to query our influxdb servers, I've looked at the influxdb library doc (https://hackage.haskell.org/package/influxdb-1.2.2/docs/Database-InfluxDB.html) but as far as I understand it, you need to pre-define some data structure or you can't query anything.
I just need to be able to let the user query whatever without having to define some data in the sources first.
I imagine I could define something with a time field and a value field, then use something like "SELECT active as value FROM mem" to force it to fit that. I think that would work, but it wouldn't be very practical if I need to query two fields later on.
Any better solutions I'm not seeing ? I'm still very much a beginner in Haskell, I'd appreciate any tips.
EDIT:
Even that doesn't work, since apparently it's missing the "String" constructor in that bit :
:{
data Test = Test { time :: UTCTime, value :: T.Text }
instance QueryResults Test where
parseResults prec = parseResultsWith $ \_ _ columns fields -> do
time <- getField "time" columns fields >>= parseUTCTime prec
String value <- getField "value" columns fields
return Test {..}
:}
I copied that from the doc and just changed the fields, not sure where the "String" is supposed to be declared.
I don't know what an "InfluxDB" is, but I can read Haskell type signatures, so maybe we can start with something like this:
import Data.Aeson
import qualified Data.Aeson.Types as A
import qualified Data.Vector as V
newtype GenericRawQueryResults = GenericRawQueryResults Value
deriving Show
instance FromJSON GenericRawQueryResults where
parseJSON = pure . GenericRawQueryResults
instance ToJSON GenericRawQueryResults where
toJSON (GenericRawQueryResults v) = v
instance QueryResults GenericRawQueryResults where
parseResults _ val = pure (V.singleton (GenericRawQueryResults val))
Then try your queries.
From what I can guess by reading the library's code, results from an influx DB query arrive at parseResults inside a json object that has a key called "results" that points to an array of objects, each of which has a key called "series" or a key called "error", and the assumption is that you write a parser to turn each element that's pointed to by "series" into whatever type you want to read from the db.
Except that there's even more framework going on there that I'd probably understand if I knew more about what an InfluxDB is and what kind of data it returns.
In any case, this should get you as close to the raw results as the library will let you get. One additional thing you might do is install the aeson-pretty package, remove the deriving Show bit and do:
instance Show GenericRawQueryResults where
show g = "decode " ++ show (encodePretty g)
(Or you can keep the derived Show instance and just apply encodePretty to your query results to display them)

How to deal with incomplete JSON/Record types (IE missing required fields which I'll later fill in)?

EDIT: For those with similar ailments, I found this is related to the "Extensible Records Problem", something I will personally research more into.
EDIT2: I have started to solve this (weeks later now) by being pretty explicit about data types, and having multiple data types per semantic unit of data. For example, if the database holds an X, my code has an XAction for representing things I want to do with an X, and XResponse for relaying Xs to an http client. And then I need to build the supporting code for shuttling bits between instances. Not ideal, but, I like that it's explicit, and hopefully when my models crystallize, it shouldn't really need much up keep, and should be very reliable.
I'm not sure what the correct level of abstraction is for tackling this problem (ie records? or Yesod?) So I'll just lay out the simple case.
Simple Case / TL;DR
I want to decode a request body into a type
data Comment = Comment {userid :: ..., comment :: ...}
but actually I don't want the request body to contain userid, the server will supply that based on their Auth Headers, (or wherever I want to get data to default fill a field).
So they actually pass me something like:
data SimpleComment = SimpleComment {comment :: ...} deriving (Generic, FromJSON)
And I turn it into a Comment. But maintaining both nearly-identical types simultaneously is a hassle, and not DRY.
How do I solve this problem?
Details on Problem
I have a record type:
data Comment = Comment {userid :: ..., comment :: ...}
I have a POST route:
postCommentR :: Handler Value
postCommentR = do
c <- requireJsonBody :: (Handler Comment)
insertedComment <- runDB ...
returnJson insertedComment
Notice that the Route requires that the user supply their userid (in the Comment type, which is at least redundant since their id is associated with their auth headers. At worst, it means I need to check that users are adding their own id, or throwing away their supplied id, in which case why did they supply it in the first case.
So, I want a record type that's Comment minus userid, but I don't know how to do that intelligently.
My Current (awful but working) Solution
So I made a custom type with derived FromJSON (for the request body) which is almost completely redundant with the Comment type.
data SimpleComment = SimpleComment {comment :: ...} deriving (Generic, FromJSON)
Then my new route needs to decode the request body according to this, and then merge a SimpleComment with a userid field to make it a Comment:
postComment2R :: Handler Value
postComment2R = do
c <- requireJsonBody :: (Handler SimpleComment)
(uid, _) requireAuthPair
insertedComment <- runDB $ insertEntity (Comment { commentUserid = uid
, commentComment = comment c})
returnJson ...
Talk about boilerplate. And my use case is more complex than this simple Comment type.
If it factors in, you might be able to tell, I'm using the Yesod Scaffolding.
What I usually do to get a type minus a field is just to have a function which take that field and return the type. In your case you just need to declare an JSON instance for UserId -> Comment. Ok it doesn't seem natural and you have to go it manually but it actually works really well, especially as there is only one field of type UserId in Comment.
A solution I like is to use a wrapper for things that come from/go to the DB:
data Authenticated a = Authenticated
{ uid :: Uid
, thing :: a
} deriving (Show)
Then you can have Comment be just SimpleComment and turn it into an Authenticated Comment once you know the user id.
I'm also looking for a nice way to solve this. :-)
What I usually do in my code is to operate directly on the Aeson's type Value. This is some of the sample code taken from my current project:
import qualified Data.HashMap.Strict as HM
removeKey :: Text -> Value -> Value
removeKey key (Object xs) = Object $ HM.delete key xs
removeKey _ ys = ys
I directly operate on the value Object and remove the particular key present in the javascript object.
And in the Yesod handler code, I do this processing:
myHandler :: Handler RepJson
myHandler = do
userId <- insert $ User "sibi" 23
guser <- getJuser user
let guser' = removeKey "someId" $ toJSON guser
return $ repJson $ object [ "details" .= guser' ]
In some cases, I actually want to add some specific key to the outgoing JSON object. For those, I have specific helper functions defined which operate on the type Value. While this is not perfect, it has been helping me to avoid a lot of boilerplate code.

Remove underscore from fields with generated lenses in Persistent

Let's suppose that I have a persistent type and want to project some value from this type:
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
User
name Text
email Text
|]
...
getName :: Entity User -> Text
getName (Entity uid vals) = userName vals
The problem is, if I generate lenses for said type, using mkPersist sqlSettings {mpsGenerateLenses = True}, I'll need to add a underscore in the beginning of each projection function or use the lenses getter:
getName :: Entity User -> Text
getName (Entity uid vals) = _userName vals
getName' :: Entity User -> Text
getName (Entity uid vals) = vals ^. userName
Firstly, how can I revert that to the default, userName vals, and add the underscore to use the lenses getter, vals ^. _userName?
Secondly, why is this this way and not the other way around?
Firstly, how can I revert that to the default, userName vals, and add the underscore to use the lenses getter, vals ^. _userName?
Database.Persist.TH does not offer that option (to see what it might look like if it existed, cf. Control.Lens.TH), so, assuming that you won't fork the library over this, there doesn't seem to be a way. (By the way, looking for mpsGenerateLenses in the source will show exactly where the underscores are added.)
Secondly, why is this this way and not the other way around?
Presumably because the library assumes that if you generate the lenses you will use them everywhere instead of the record accessors/labels, including for getting the value of the field. The only cosmetic suggestion I have is that, if the change of writing order from _userName vals to vals ^. userName bothers you, you might prefer using view rather than (^.), as in view userName vals.

How to simply all "data" definitions having the same kind and name of a field?

I have many "data" which represent sql table in a database.
data User = User { userId :: Int, userName :: String }
data Article = Article { articleId :: Int, articleTitle :: String, articleBody :: String }
-- .......
All of them has the field "id" as a primary key. I wonder, is there any way to get rid of necessity to define it each time for each "data", can I anyhow simplify that? If do this:
class DataTable a where
myId :: Int
it won't change anything, will? I'll still have to define "id" for each data and then implement it for DataTable, in fact it'll make more complex.
Sometimes it's best to separate things.
data Identified a = Identified
{ ident :: !Int
, payload :: a }
Now you can deal with identified things in an entirely uniform way.
In GHC 8.0 with DuplicateRecordFields you can use the same record field name for many data types. This is part of the larger feature of OverloadedRecordFields.
Part 3, MagicClasses, introduces derivable type classes for HasField and UpdateField. This is similar in idea to your DataTable.
If you want a field in your data type then you must declare it in the data type definition. I am not aware of any extensions, other than Template Haskell, which change this.

Resources