Remove underscore from fields with generated lenses in Persistent - haskell

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.

Related

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.

Yesod: querying `persist` database with a custom primary key

Supposing that I have a SQL table with persist, and that I have a custom Text as primary key instead of the auto-incrementing Int64 key.
For example, my database definition is thus:
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
TorrentD
infoHash Text
ipAddr [Text]
Primary infoHash
deriving Show
|]
Supposing that I then have a plain Text value, what is the idiomatic way to query the database for a row with a primary key matching my Text value?
runDB $ get $ toSqlKey ("test" :: Text) doesn't work as toSqlKey doesn't support custom primary keys and thus expects expects an Int64.
Creating the key manually and running runDB $ get $ Key $ PersistText ("test" :: Text) doesn't work as it is giving me an error about Key not being in scope (although I do have Database.Persist.Class in my imports).
I've found (the?) (an?) answer. It's not very pretty, but:
get (TorrentDKey {unTorrentDKey = torrentPInfoHash torrent})
works.
The unTorrentDKey is something generated inside the template haskell.
It looks like i'll have to pepper
let primaryKey = TorrentDKey {unTorrentDKey = torrentPInfoHash torrent}
around in my code.

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

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.

Can template-haskell be used to generate quasi-quotes?

A project that I'm currently working on makes extensive use of persistent. Instead of persistent's quasi-quoted syntax to specify models, I would like to use json. Right now, I use a script to generate the quasiquote which persistent expects using simple-templates. That adds a rather awkward step in the workflow. Can this be avoided using template-haskell?
This is currently generated by the script :
-- File : ProjSpecific.Models
share [mkPersist sqlSettings, mkMigrate "migrateAll"]
[persistLowerCase|
Person
name String
age Int Maybe
deriving Show
BlogPost
title String
authorId PersonId
deriving Show
|]
This is how I would ideally like to do it :
-- File : ProjSpecific.Config
import Data.Aeson.QQ
import Data.Aeson (Value)
models :: Value
models = [aesonQQ| {some json encoding of above models} |]
And
-- File : ProjSpecific.Models
complie time logic to generate the persistent models
Any ideas on how this can be done or is there a better way to accompilsh what I'm trying to do?
Yes, it should be relatively painless. You'd essentially want to use the quoteExp field from persistLowerCase, which will give you a function of type String -> Q Exp. Use your preprocessor to convert JSON into the expected syntax, and then pass it to the function.

Creating a URL Alias or Making Deep-Urls Pretty

I have fairly deep urls with IDs and I want to see if I can convert them into something nicer looking. I tried looking into how Slugs are done for Yesod Blog (https://github.com/yesodweb/yesod/wiki/Slugs) but not sure if I know how to translate that to what I am looking for here.
Suppose let's say I want to display Top Fiction Books, I have a resource that looks like this:
/topbooks/bookcategory/#BookCategoryId
If I go to /topbooks/bookcategory/1 I may get Fiction books, If I got to /topbooks/bookcategory/2 I may get Non-fiction, etc.
All my handlers use the #BookCategoryId input parameter in the database queries to get the appropriate records.
Ideally I would like to create a url that looks like: /topbooks/fiction, /topbooks/non-fictionetc. If I create my route as /topbooks/#Text, I can pattern match the string and return a Key back. However, I will have to manually transform it in every handler using #BookCategoryId. Note that the IDs are used as Foreign keys so it makes a bit cumbersome to rely on getBy like how it is done in Slug example.
So I am wondering if there is a better way to do it: Is it possible to define a custom type similar to Slug but instead of just converting values to/from Text / String, actually output IDs? That way I can just use the parameter directly in my queries.
Update:
To clarify given Michael's comment:
I understand we cannot get the IDs without doing a database lookup. In fact for this example, I am ok hard coding the look-up mechanism. I was just trying to see if the PathPiece mechanism will somehow simplify the conversion process.
For example, if something like this worked then it will be fine but of course I will get a type error since I am trying to return a Key when the compiler is expecting BookCategories.
data BookCategories = FICTION | NONFICTION
instance PathPiece BookCategories where
toPathPiece (FICTION) = T.pack "fiction"
toPathPiece (NONFICTION) = T.pack "nonfiction"
fromPathPiece s =
let ups = map toUpper $ T.unpack s
in
case reads ups of
[(FICTION, "")] -> Just $ Key $ PersistInt64 1
[(NONFICTION, "")] -> Just $ Key $ PersistInt64
[] -> Nothing
otherwise -> Nothing
Of course I could just return Just FICTION and unwrap it in my handler. This is not conceptually very different from actually pattern matching on Text directly with a function with a signature Text -> BookCategoryId.
getBookCategoryR :: BookCategoryId -> Handler Html
getBookCategoryR bcId = do
-- Normal use case when IDs are used in the URL
books <- runDB $ selectList [ModelBookCategory ==. bcId] []
If I swtich to Text input
getBookCategoryR :: Text -> Handler Html
getBookCategoryR bc = do
bcId = convertToId (bc) -- This is the line I am trying to avoid everywhere
books <- runDB $ selectList [ModelBookCategory ==. bcId] []
The one line conversion code is what I am trying to avoid. PathPiece has been handling it nicely for id-based-urls and kept the code clean. If there was a way to get Ids returned through some Type magic then it will be great. With limited knowledge of Haskell, I have no idea if it is even feasible.
Hope my question is clearer now.
Thanks!
No, there's no such way to do that, and the reason is simple: without consulting the database, there's no way to know if foo exists as a slug at all and, if it does, which ID it relates to. You'll always have to perform some database action to convert a slug into an ID.
UPDATE I'm still not certain I understand what you're looking for, but the short answer regarding PathPiece is that it only works on pure conversions, nothing which has side effects. If you're looking to write a function like Text -> Handler BookCategoryId, you can certainly do so. And if you really wanted to, you could even abstract this with a typeclass, though I'm not sure if you'll gain anything.
This may be barking up the wrong tree, but here's a short idea that might inspire you a bit: you could creating different newtype wrappers for each textual slug field, and then create a typeclass to convert a textual slug field into the appropriate entity, e.g.:
newtype BookCatSlug = BookCatSlug Text
deriving PathPiece
BookCategory
slug BookCatSlug
title Text
...
UniqueBookCat slug
class Slug slug where
type SlugEntity slug
lookupSlug :: slug -> YesodDB App (Maybe (Entity (SlugEntity slug)))
instance Slug BookCatSlug where
type SlugEntity BookCatSlug = BookCategory
lookupSlug = getBy . UniqueBookCat
lookupSlug404 slug = runDB (lookupSlug slug) >>= maybe notFound return
myHandler slug = do
Entity bookCatId bookCat <- lookupSlug404 slug
Something along these lines should work, but I'm not sure if the "type magic" is worthwhile, since having a helper function and manually passing in the appropriate Unique constructor would be almost as easy for the call site and result in much simpler error messages.

Resources