From bb38d331b40f8d9b221db1e2076108c6e9667df9 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 15 Aug 2018 17:37:42 -0500 Subject: [PATCH] Fix intro/part9 --- intro/part9/src/Article.elm | 77 ++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/intro/part9/src/Article.elm b/intro/part9/src/Article.elm index d0bbfa8..b5c550a 100644 --- a/intro/part9/src/Article.elm +++ b/intro/part9/src/Article.elm @@ -40,7 +40,7 @@ import Html.Attributes exposing (class) import Html.Events exposing (stopPropagationOn) import Http import HttpBuilder exposing (RequestBuilder, withBody, withExpect, withQueryParams) -import Json.Decode as Decode exposing (Decoder, bool, int, list, string) +import Json.Decode as Decode exposing (Decoder) import Json.Decode.Pipeline exposing (custom, hardcoded, required) import Json.Encode as Encode import Markdown @@ -83,6 +83,47 @@ type Article a = Article Internals a +{-| Metadata about the article - its title, description, and so on. + +Importantly, this module's public API exposes a way to read this metadata, but +not to alter it. This is read-only information! + +If we find ourselves using any particular piece of metadata often, +for example `title`, we could expose a convenience function like this: + +Article.title : Article a -> String + +If you like, it's totally reasonable to expose a function like that for every one +of these fields! + +(Okay, to be completely honest, exposing one function per field is how I prefer +to do it, and that's how I originally wrote this module. However, I'm aware that +this code base has become a common reference point for beginners, and I think it +is _extremely important_ that slapping some "getters and setters" on a record +does not become a habit for anyone who is getting started with Elm. The whole +point of making the Article type opaque is to create guarantees through +_selectively choosing boundaries_ around it. If you aren't selective about +where those boundaries are, and instead expose a "getter and setter" for every +field in the record, the result is an API with no more guarantees than if you'd +exposed the entire record directly! It is so important to me that beginners not +fall into the terrible "getters and setters" trap that I've exposed this +Metadata record instead of exposing a single function for each of its fields, +as I did originally. This record is not a bad way to do it, by any means, +but if this seems at odds with - now you know why! +See commit c2640ae3abd60262cdaafe6adee3f41d84cd85c3 for how it looked before. +) + +-} +type alias Metadata = + { description : String + , title : String + , tags : List String + , createdAt : Time.Posix + , favorited : Bool + , favoritesCount : Int + } + + type alias Internals = { slug : Slug , author : Author @@ -170,39 +211,15 @@ internalsDecoder maybeCred = |> custom metadataDecoder -type alias Metadata = - { description : String - , title : String - , tags : List String - , favorited : Bool - , favoritesCount : Int - , createdAt : Time.Posix - } - - metadataDecoder : Decoder Metadata metadataDecoder = - {- 👉 TODO: replace the calls to `hardcoded` with calls to `required` - in order to decode these fields: - - --- "description" -------> description : String - --- "title" -------------> title : String - --- "tagList" -----------> tags : List String - --- "favorited" ---------> favorited : Bool - --- "favoritesCount" ----> favoritesCount : Int - - Once this is done, the articles in the feed should look normal again. - - 💡 HINT: Order matters! These must be decoded in the same order - as the order of the fields in `type alias Metadata` above. ☝️ - -} Decode.succeed Metadata - |> hardcoded "(needs decoding!)" - |> hardcoded "(needs decoding!)" - |> hardcoded [] - |> hardcoded False - |> hardcoded 0 + |> required "description" (Decode.map (Maybe.withDefault "") (Decode.nullable Decode.string)) + |> required "title" Decode.string + |> required "tagList" (Decode.list Decode.string) |> required "createdAt" Timestamp.iso8601Decoder + |> required "favorited" Decode.bool + |> required "favoritesCount" Decode.int