Let’s create a new directory called post-app
inside beginning-elm
instead of cluttering the src
directory with too many modules.
We need to add post-app
to the source-directories
list in elm.json
.
{
.
.
"source-directories": [
"src",
"post-app"
],
.
.
}
Creating the Post Module
Let’s move the Post
type and the code for decoding it from DecodingJson.elm
to a new module. Create a new file called Post.elm
inside the post-app
directory and add the code below to it.
module Post exposing (Post, postDecoder, postsDecoder)
import Json.Decode as Decode exposing (Decoder, int, list, string)
import Json.Decode.Pipeline exposing (required)
type alias Post =
{ id : Int
, title : String
, authorName : String
, authorUrl : String
}
postsDecoder : Decoder (List Post)
postsDecoder =
list postDecoder
postDecoder : Decoder Post
postDecoder =
Decode.succeed Post
|> required "id" int
|> required "title" string
|> required "authorName" string
|> required "authorUrl" string
Using Custom Types for ID
Currently, the id
field in Post
is an integer. We could mistakenly pass an identifier of a different data structure, which also happens to be an integer, to a function expecting a post id
. The Elm compiler won’t be able to catch a bug like that. We can help the compiler by implementing identifiers as custom types. Add the following code below the Post
type alias in Post.elm
.
type PostId
= PostId Int
PostId
is an opaque type. Let’s expose it in the module definition.
module Post exposing (Post, PostId, postDecoder, postsDecoder)
Change the id
field’s type in Post
from Int
to PostId
.
type alias Post =
{ id : PostId
.
.
PostId
Decoder
Now that id
is a custom type, simply using the int
decoder in postDecoder
won’t be enough. We need to create a custom decoder. Add the following code to the bottom of Post.elm
.
idDecoder : Decoder PostId
idDecoder =
Decode.map PostId int
We used the Decode.map
function to convert an integer to PostId
. Here is what its type signature looks like:
map : (a -> value) -> Decoder a -> Decoder value
Now we can replace int
with idDecoder
in postDecoder
.
postDecoder : Decoder Post
postDecoder =
Decode.succeed Post
|> required "id" idDecoder
.
.
Converting PostId
to String
The viewPost
function in DecodingJson.elm
uses String.fromInt
to convert an id
to a string.
viewPost : Post -> Html Msg
viewPost post =
tr []
[ td []
[ text (String.fromInt post.id) ]
.
.
Now that the id
field holds a value of a custom type that’s not going to work. We need to define a custom function for that. Add the following code to the bottom of Post.elm
.
Note: Although we’re moving away from DecodingJson.elm
, we’ll still be using the viewPost
function in a module responsible for fetching and displaying posts in the next section.
idToString : PostId -> String
idToString (PostId id) =
String.fromInt id
idToString
uses pattern matching to expose the underlying integer value inside the PostId
data constructor. Without this ability, we’d be forced to use a case
expression which is not as elegant.
idToString : PostId -> String
idToString postId =
case postId of
PostId id ->
String.fromInt id
Finally we need to expose idToString
in the module definition.
module Post exposing (Post, PostId, idToString, postDecoder, postsDecoder)
In the next section, we’ll create a separate page for fetching and listing all posts in our database.