Open gurgl opened 6 years ago
I use a newtype to share that kind of data, and helper functions to convert it from and to Entity
s. The deriving error is probably a bug, but all in all you should not do it as it will result in orphan instances ...
(I will look at why deriving fails though)
Thanks. If you have time i would be glad if you could elaborate on the newtype approach. Im a haskell noob so its not totally clear to me how that would look. It feels like I need to make my newtype an instance of IsElmDefinition as makeModuleWithVersion only accepts that? How I would define its required method
compileElmDef :: Proxy a -> ETypeDef
...is not totally clear to me.
Ok, this is not actually a newtype, more a simple data type. I do not have the code right here, and might be writing wrong stuff from memory, but it basically is something like:
data WithKey a
= WithKey
{ _key :: Int64
, _content :: a
}
You then need to write:
fromEntity :: SomeSqlConstraintICan'tRemember a => Entity a -> WithKey a
toEntity :: SomeSqlConstraintICan'tRemember a => WithKey a -> Entity a
You can then derive the Elm definition from WithKey
in a safe way, at the cost of converting your data. I used Int64
but it depends on your backend, as persistent
has a function to extract a Key
.
Is that clearer?
Thanks. It could work for simple cases but for entites nested in other types it would require all types wrapping the wrapper to be changed too.
Persistent also leads to other problems. All (primary-) keys are (type family?) newtype instances Key Entity = ...
I guess it gets complicated. Been great using elm-bridge - it has performed great for concrete types. but i guess i need to resort to manual work here.
The fact that all keys are the same makes it easy to convert all at once with a mutation in elm-bridge
. Same with the other kinds of newtypes that I use (for example for storing json encoded data in the database).
You could use the same process for nested entities, but I would really need a more precise example :)
@gurgl here's another solution:
update :: IO ()
update = writeFile "elm-ui/src/AutoGen.elm" ([qc|module AutoGen exposing (..)
import Json.Decode
import Json.Encode exposing (Value)
import Json.Helpers exposing (..)
import Dict
import Set
type Key record = Key Int
jsonDecKey : Json.Decode.Decoder (Key record)
jsonDecKey = Json.Decode.map Key <| Json.Decode.field "id" Json.Decode.int
jsonEncKey : Key record -> Value
jsonEncKey (Key k) = Json.Encode.int k
type alias Entity record =
\{ entityKey : Key record
, entityVal : record
}
jsonDecEntity : Json.Decode.Decoder record -> Json.Decode.Decoder ( Entity record )
jsonDecEntity localDecoder_record =
jsonDecKey >>= \pentityKey ->
localDecoder_record >>= \pentityVal ->
Json.Decode.succeed \{entityKey = pentityKey, entityVal = pentityVal}
jsonEncEntity : (record -> Value) -> Entity record -> Value
jsonEncEntity localEncoder_record val =
let
r = localEncoder_record val.entityVal
in
case Json.Decode.decodeValue (Json.Decode.keyValuePairs Json.Decode.value) r of
Ok ok ->
Json.Encode.object <| ("id", jsonEncKey val.entityKey) :: ok
Err _ ->
r
{autoGenCode}
|])
where
autoGenCode = makeModuleContent (myAlterations . defaultAlterations)
[ DefineElm (Proxy :: Proxy User) -- NOTE: This is a Persistent record
]
Here's how one can use this in the Elm REPL:
> u = "{\"email\":\"saurabhnanda@gmail.com\",\"accessToken\":\"TODO\",\"createdAt\":\"2018-06-22T19:41:14.834185Z\",\"refreshToken\":\"TODO\",\"tokenExpiresAt\":\"2018-06-22T19:41:14.834185Z\",\"id\":1,\"updatedAt\":\"2018-06-22T19:41:14.834185Z\"}"
"{\"email\":\"saurabhnanda@gmail.com\",\"accessToken\":\"TODO\",\"createdAt\":\"2018-06-22T19:41:14.834185Z\",\"refreshToken\":\"TODO\",\"tokenExpiresAt\":\"2018-06-22T19:41:14.834185Z\",\"id\":1,\"updatedAt\":\"2018-06-22T19:41:14.834185Z\"}"
: String
> import Json.Decode
> import AutoGen exposing (..)
> Json.Decode.decodeString (jsonDecEntity jsonDecUser) u
Ok { entityKey = Key 1, entityVal = { createdAt = DateTime { date = Date { year = 2018, month = 6, day = 22 }, offset = 70874834 }, updatedAt = DateTime { date = Date { year = 2018, month = 6, day = 22 }, offset = 70874834 }, email = Email "saurabhnanda@gmail.com", refreshToken = "TODO", accessToken = "TODO", tokenExpiresAt = DateTime { date = Date { year = 2018, month = 6, day = 22 }, offset = 70874834 } } }
: Result.Result String (AutoGen.Entity AutoGen.User)
>
The fact that all keys are the same makes it easy to convert all at once with a mutation in
elm-bridge
. Same with the other kinds of newtypes that I use (for example for storing json encoded data in the database).You could use the same process for nested entities, but I would really need a more precise example :)
Could you explain what you mean by mutation in elm-bridge?
A better term would be Alterations
, as seen in Elm.Module
. Look at the default alterations source to get some inspiration!
Im adapting to Yesod Persistent. Thus my old Game type becomes "Entity Game" that is constructed as "Entity GameId Game" where type GameId = Key Game (or something like that).
ive managed to make stuff work by adding a wrapper that can be incorpoarated in elm-bridge, but it feels clumsy
data Ent a b = Ent { _key :: a, _val :: b } deriving (Show,Generic)
Incase you are familiar with Persistent, how would one incorporate encoding of (nested) entity types in elm-bridge.
trying to get :
deriveBoth defaultOptions { fieldLabelModifier = drop 1} ''PS.Entity
..to compile gives :
Can't derive this sum: ForallC [] [AppT (ConT Database.Persist.Class.PersistEntity.PersistEntity) (VarT record_7566047373982557936)] (RecC Database.Persist.Class.PersistEntity.Entity [(Database.Persist.Class.PersistEntity.entityKey,Bang NoSourceUnpackedness NoSourceStrictness,AppT (ConT Database.Persist.Class.PersistEntity.Key) (VarT record_7566047373982557936)),(Database.Persist.Class.PersistEntity.entityVal,Bang NoSourceUnpackedness NoSourceStrictness,VarT record_7566047373982557936)])