Reputation: 1116
I have a post that has an Include'
as I've fetch
ed it with a fetchRelated
, so it has this type:
post :: Include' ["comments"] Post
If I'd like to save it, I'd get an error
post
|> set #title "Foo"
|> updateRecord
The compiler will complain it's not a Post
. It's an Include ... Post
My question is - can the Post
be extracted out of the include, so I won't have to fetch it again from the DB?
Upvotes: 4
Views: 98
Reputation: 347
I wanted to have a little fun and see if I could generalize Marc's answer above. Here's what I came up with! This is for a single include:
clearInclude :: forall model field fieldValue. (
UpdateField field (Include field model) model fieldValue fieldValue,
HasField field model fieldValue,
Record model
) => Include field model -> model
clearInclude model =
model
|> updateField @field (getField @field (newRecord @model))
This should work with any datatype with a single Include
d data type. Then I tried it with two and three, which is pretty easy:
clearInclude2 :: _ => Include field1 (Include field2 model) -> model
clearInclude2 model =
model
|> clearInclude
|> clearInclude
clearInclude3 :: _ => Include field1 (Include field2 (Include field3 model)) -> model
clearInclude3 model =
model
|> clearInclude
|> clearInclude
|> clearInclude
I still haven't figured out how to get it to compile when I fill out the typeclass constraints fully, and I still need to figure out how to make it work with the Include'
compact notation, but this was a fun problem to get started on, and maybe we can continue to generalize further :)
Upvotes: 1
Reputation: 1396
There's no built-in function in IHP to do this. But you can use a custom helper like this:
clearComments :: Include "comments" Post -> Post
clearComments post = updateField @"comments" (newRecord @Post).comments post
And then use it like this:
post
|> set #title "Foo"
|> clearComments
|> updateRecord
Things can get slightly more complex when we have multiple Include. The compiler will error out,
clearIncludes :: Include ["comments", "tags"] Post -> Post
clearIncludes post = post
|> updateField @"comments" (newRecord @Post).comments
|> updateField @"tags" (newRecord @Post).tags
We need to split this into multiple functions, each with types annotations:
clear1 :: Include' ["comments", "tags"] Post -> Include "tags" Post
clear1 post = post
|> updateField @"comments" (newRecord @Post).comments
clear2 :: Include "tags" Post -> Post
clear2 post = post
|> updateField @"tags" (newRecord @Post).tags
March has explained the reason:
TL;DR: updateField
has a more open type signature than set
and sometimes GHC needs a bit of help
In very early IHP versions updateField
was actually set
and it was later changed (because it was causing errors like these). The core problem with updateField
is, that it's type definition is very open. It's defined as updateField :: value' -> model -> model'
. Here model
and model'
are two independent type variables. This means a call to updateField
can actually change the return type of the record (e.g. turning Post
to Post "tags" LandingPage
). The problem with your first version was that GHC could not figure out the model'
variable because multiple updateField
were chained (the model
type argument is easy for GHC to figure out, it's just that the model'
is independent of that).
For comparison set
is defined as set :: value -> model -> model
. In that case when GHC can figure out model
it will never error. So set
is typically easy to figure out and unlikely to error. One the other side the problem with set
is that it's less flexible and cannot change the output type (e.g. with model = Post
it will be set :: value -> Post -> Post
, so there's no way to express e.g. the Include
stuff). That's why updateField
exists.
Upvotes: 4