Elm updating model from json response - json

I am having an issue with updating a model from a JSON response through one of my actions.
Here is the setup:
I have models that looks like this:
type alias Model =
{ totals: Totals.Total // all values are initialized as 0 or 0.0
}
-- In other module
type alias Total =
{ days : Int
, avgPerDay: Float
, reports : Int
, people : Int
, locations : Int
, totalCoffees : Int
, coffeesPerDay: Float
}
A decoder for said model:
decodeTotal : Json.Decoder Total
decodeTotal =
object7 Total
("days" := Json.int)
("avgPerDay" := Json.float)
("reports" := Json.int)
("people" := Json.int)
("locations" := Json.int)
("totalCoffees" := Json.int)
("coffeesPerDay" := Json.float)
To update the model, the following Http request is called:
getTotals : Effects Action
getTotals =
Http.get Totals.decodeTotal ("/reports/totals")
|> Task.toMaybe
|> Task.map TotalsFetched
|> Effects.task
The response from the server is 200 OK with this body: {"days":347,"reports":1793,"avgPerDay":5.167147,"people":205,"locations":332,"coffees":146,"coffeesPerDay":0.42074928}
However, in the TotalsFetched Action:
update : Action -> Model -> (Model, Effects Action)
update action model =
case action of
TotalsFetched totals ->
log (toString totals) -- logs Nothing
( { model | totals = Maybe.withDefault model.totals totals }
, Effects.none
)
I am assuming that since totals is Nothing my model just stays as is described in the Maybe.withDefault
I don't understand why totals is Nothing instead of the Decoded response from the server. This is my first elm project, so I wouldn't be surprised if I am missing something really obvious to the trained eye.

Your json is returning a field called coffees while your decoder is looking for totalCoffees.
You may want to consider using Task.toResult instead of Task.toMaybe because that way you'll get an error message returned when something goes wrong.

Related

Custom fields in Many2Many JoinTable

I have this model with a custom JoinTable:
type Person struct {
ID int
Name string
Addresses []Address `gorm:"many2many:person_addresses;"`
}
type Address struct {
ID uint
Name string
}
type PersonAddress struct {
PersonID int
AddressID int
Home bool
CreatedAt time.Time
DeletedAt gorm.DeletedAt
}
How is it possible to assign a value to the Home field when creating a new Person?
Method 1
From what I can see in the docs, here's a clean way you might currently do this:
DB.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})
addr1 := Address{Name: "addr1"}
DB.Create(&addr1)
addr2 := Address{Name: "addr2"}
DB.Create(&addr2)
person := Person{Name: "jinzhu"}
DB.Create(&person)
// Add an association with default values (i.e. Home = false)
DB.Model(&person).Association("Addresses").Append(&addr1)
// Add an association with custom values
DB.Create(&PersonAddress{
PersonID: person.ID,
AddressID: addr2.ID,
Home: true,
})
Here we're using the actual join table model to insert a row with the values we want.
We can also filter queries for the association:
addr := Address{}
// Query association with filters on join table
DB.Where("person_addresses.home = true").
Model(&person).
Association("Addresses").
Find(&addr)
Method 2
Here's a more magical way, by (ab)using the Context to pass values to a BeforeSave hook, in addition to the SetupJoinTable code from above:
func (pa *PersonAddress) BeforeSave(tx *gorm.DB) error {
home, ok := tx.Statement.Context.Value("home").(bool)
if ok {
pa.Home = home
}
return nil
}
// ...
DB.WithContext(context.WithValue(context.Background(), "home", true)).
Model(&person).
Association("Addresses").
Append(&addr2)
This method feels icky to me, but it works.
As you can find this point in the official documents of the GROM, you can implement some methods for each table(struct).
You can implement BeforeCreate() and/or AfterCreate() methods for your join table, gorm will check that method on time!
You can do anything inside those methods to achieve your goal.
here you will find the full documentation.
enjoy ;)

How to decode a heterogenous array with remaining values as a list

I want to decode json string like below.
"[[\"aaa\",1,2,3,4],[\"bbb\",1,2,3]]"
and decode to Elm tuple list.
[("aaa",[1,2,3,4]),("bbb",[1,2,3])] : List (String, List Int)
How to decode it?
jsdecode=index 0 string
|> andThen xxxxxxx??
This isn't straightforward to do, but before I jump straight in how to do it, let me collect a series of thoughts about the data we are trying to decode:
We are decoding a list of lists
Each list should be composed by a starting string and a series of values
But actually there might be an empty list, no initial string but some values or an initial string and no values
So in my mind the difficulty of building the right decoder reflects the complexity of handling all these edge cases. But let's start defining the data we would like to have:
type alias Record =
( String, List Int )
type alias Model =
List Record
jsonString : String
jsonString =
"[[\"aaa\",1,2,3,4],[\"bbb\",1,2,3]]"
decoder : Decoder Model
decoder =
Decode.list recordDecoder
Now we need to define a type that represents that the list could contain either strings or ints
type EntryFlags
= EntryId String
| EntryValue Int
type RecordFlags
= List EntryFlags
And now for our decoder
recordDecoder : Decoder Record
recordDecoder =
Decode.list
(Decode.oneOf
[ Decode.map EntryId Decode.string
, Decode.map EntryValue Decode.int
]
)
|> Decode.andThen buildRecord
So buildRecord takes this list of EntryId String or EntryValue Int and builds the record we are looking for.
buildRecord : List EntryFlags -> Decoder Record
buildRecord list =
case list of
[] ->
Decode.fail "No values were passed"
[ x ] ->
Decode.fail "Only key passed, but no values"
x :: xs ->
case buildRecordFromFlags x xs of
Nothing ->
Decode.fail "Could not build record"
Just value ->
Decode.succeed value
As you can see, we are dealing with a lot of edge cases in our decoder. Now for the last bit let's check out buildRecordFromFlags:
buildRecordFromFlags : EntryFlags -> List EntryFlags -> Maybe Record
buildRecordFromFlags idEntry valueEntries =
let
maybeId =
case idEntry of
EntryId value ->
Just value
_ ->
Nothing
maybeEntries =
List.map
(\valueEntry ->
case valueEntry of
EntryValue value ->
Just value
_ ->
Nothing
)
valueEntries
|> Maybe.Extra.combine
in
case ( maybeId, maybeEntries ) of
( Just id, Just entries ) ->
Just ( id, entries )
_ ->
Nothing
In this last bit, we are using a function from maybe-extra to verify that all the values following the initial EntryId are indeed all of the EntryValue type.
You can check out a working example here: https://ellie-app.com/3SwvFPjmKYFa1
There are two subproblems here: 1. decoding the list, and 2. transforming it to the shape you need. You could do it as #SimonH suggests by decoding to a list of JSON values, post processing it and then (or during the post-processing) decode the inner values. I would instead prefer to decode it fully into a custom type first, and then do the post processing entirely in the realm of Elm types.
So, step 1, decoding:
type JsonListValue
= String String
| Int Int
decodeListValue : Decode.Decoder JsonListValue
decodeListValue =
Decode.oneOf
[ Decode.string |> Decode.map String
, Decode.int |> Decode.map Int
]
decoder : Decode.Decoder (List (List JsonListValue))
decoder =
Decode.list (Decode.list decodeListValue)
This is a basic pattern you can use to decode any heterogenous array. Just use oneOf to try a list of decoders in order, and map each decoded value to a common type, typically a custom type with a simple constructor for each type of value.
Then onto step 2, the transformation:
extractInts : List JsonListValue -> List Int
extractInts list =
list
|> List.foldr
(\item acc ->
case item of
Int n ->
n :: acc
_ ->
acc
)
[]
postProcess : List JsonListValue -> Result String ( String, List Int )
postProcess list =
case list of
(String first) :: rest ->
Ok ( first, extractInts rest )
_ ->
Err "first item is not a string"
postProcess will match the first item to a String, run extractInts on the rest, which should all be Ints, then put them together into the tuple you want. If the first item is not a String it will return an error.
extractInts folds over each item and adds it to the list if it is an Int and ignores it otherwise. Note that it does not return an error if an item is not an Int, it just doesn't include it.
Both of these functions could have been written to either fail if the values don't conform to the expectations, like postProcess, or to handle it "gracefully", like extractInts. I chose to do one of each just to illustrate how you might do both.
Then, step 3, is to put it together:
Decode.decodeString decoder json
|> Result.mapError Decode.errorToString
|> Result.andThen
(List.map postProcess >> Result.Extra.combine)
Here Result.mapError is used to get error from decoding to conform to error type we get from postProcess. Result.Extra.combine is a function from elm-community/result-extra which turns a List of Results into a Result of List, which comes in very handy here.

monitoring new user

I am a go student, so I am writing a simple API, that needs analytics
I want to create a monitoring new users, to see how many users had registered by specific period. So I set date (init date and end date) and return number of new users.
Here is my func only:
package db
import (
"github.com/sirupsen/logrus"
"time"
)
func NewUsersByPeriod(start time.Time, end time.Time) (count int) {
Qselect := `SELECT COUNT(*) FROM "User" WHERE datereg BETWEEN $1 and $2 ;`
row := connectionVar.QueryRowx(Qselect, start, end)
err := row.Scan(&count)
if err != nil {
logrus.Fatal(err)
}
return count
}
My question is about how to realize it correctly, what frameworks I could use?
Write any recommendation
I use gin-gonic for every RESTful API I make ( https://gin-gonic.github.io/gin/ ). Whether is 1 QPS or 100,000 QPS, it's a solid performer, simple to use with great documentation.
You wouldn't want to use logrus.Fatal mind you as that would terminate the API. Use gin to handle the errors and output them using JSON or similar and the correct http code obviously.

Using Dict.update with nested update functions in Elm

I'm writing an Elm app where a major part of the state is in a Dict with Records as values. I have an update function for the main state model, and an update function for the individual records in the Dict. Is there a way to use Dict.update with the record state update function?
The issue I'm having is that the state update function for the records returns what update functions usually return: a tuple with the updated object and any commands to trigger (e.g. (newRecord, Cmd.none)). But the Dict.update function needs to take in a record and return a record (e.g. just newRecord), not a tuple with a record and a command object.
Is there a way around this? Right now I've got it working using a combination of Dict.get and Dict.insert, but this seems clumsy.
If update function for record always return Cmd.none, you can simplify it and return only the updated model.
Child modules do not have to follow ( Model, Cmd Msg ) convention if your application architecture does not require that.
If you do need to pass Commands from the lower level, it is also possible to re-structure your child module's update function to simplify those updates.
Example based on example/http
Here is an example of how you can split the update so that you could re-use the same logic in the top-level update without additional trickery.
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
(updateModel msg model, updateCmd msg model)
updateCmd: Msg -> Model -> Cmd Msg
updateCmd msg model =
case msg of
MorePlease ->
getRandomGif model.topic
_ ->
Cmd.none
updateModel: Msg -> Model -> Model
updateModel msg model =
case msg of
NewGif (Ok newUrl) ->
Model model.topic newUrl
_ ->
model
If you need the updated model in updateCmd, then just pass it instead of current model or even pass both if you desire.
As a bonus, you can entirely omit unused branches of the case expression.
Using Dict.update
It will also be possible to use updateModel in Dict.update without retrieving the record and writing it back.
Dict.update childKey (Maybe.map (updateModel childMsg)) model
Not sure if this is what you are looking for, but if you have a nested Dict structure in your model, like this:
type alias Model =
{ parentsAndChildren : Dict String (Dict String Int) }
Then it is not necessary to make the child update output a Cmd. Your update could look something like this:
update : Msg -> Model -> Model
update msg model =
case msg of
NewChild parentName childName age ->
let
newModel =
{ model
| parentsAndChildren =
model.parentsAndChildren
|> Dict.update
parentName
(Maybe.map insertChild)
}
in
(newModel, Cmd.none)
-- helper function to update a child Dict
insertChild: String -> Int -> Dict (String Int) -> Dict (String Int)
insertChild name age childDict =
Dict.insert name age childDict
The only update function that NEEDS to output a Cmd is the update function in your top component.
So your child update function doesn't have to output a Cmd.
Not that I know of. I have this helper function in my one of my projects
updateDict : comparable -> msg -> Dict comparable b -> (msg -> b -> ( b, Cmd msg )) -> (comparable -> msg -> c) -> ( Dict comparable b, Cmd c )
updateDict uid act dict fn wrapper =
case Dict.get uid dict |> Maybe.map (fn act) of
Just ( m, e ) ->
( Dict.insert uid m dict
, Cmd.map (wrapper uid) e
)
Nothing ->
( dict, Cmd.none )

Elm Json request not working?

In Elm, you can use the Json.decode and Http package to request json data. My attempt was to work out a periodic lookup for emails ( from this url ). The timer operation does work (i tried it with with a simple counter).
I have used this example and this SO question as reference.
Now the types:
type alias Email = { title: String, ... }
type Action =
NoAction
| TickCounter -- TODO rem
| AddEmails (List Email)
Then the main + state + actions ...
main: Signal Html
main = Signal.map (view actions.address) state
state: Signal Model
state = Signal.foldp update makeEmptyModel input
-- handle inputs (merging signals)
input : Signal Action
input =
Signal.mergeMany
[ actions.signal
-- , other actions
, Signal.map checkForNewMails (Time.every (Time.minute / 6.0) ) -- TODO precise timer (quick test)
]
actions: Signal.Mailbox Action
actions = Signal.mailbox NoAction
update: Action -> Model -> Model
update action model =
case action of
NoAction -> model
TickCounter -> { model | count = model.count + 1 }
AddEmails newMails -> { model | emails = newMails }
checkForNewMails: Time -> Action
checkForNewMails t =
let mails = startGettingEmailData
in TickCounter -- TODO replace with AddEmails using mails
The TickCounter is an Action, which i have used to test my timing operation. But the problem is startGettingEmailData. It uses the next snippet, but it doesn't fire any JSON request (i have checked it through the console). Once that has been resolved, i can convert mails to an action so that i can add the emails in the model.
-- url to json
jsonUrl = "https://dl.dropboxusercontent.com/u/14070433/temp.json"
-- get emails
startGettingEmailData: Task Http.Error (List Email)
startGettingEmailData = Http.get emailJsonDecoder jsonUrl
emailJsonDecoder: Json.Decoder (List Email)
emailJsonDecoder =
let makeEmail = Json.object4
(\ti fr da bo -> makeNewEmail -1 ti fr da bo )
("title" := Json.string)
("from" := Json.string)
("date" := Json.string)
("body" := Json.string)
in
"emails" := Json.list makeEmail
Is there a problem with my code ? If not, then is there a way to check the Http.Error content ? (Maybe the problem lies not in my code, but in the network, but i can access the dropbox file by browser...)
Your code for checkForNewMails doesn't actually do anything with mails, so it never gets invoked. The let statement doesn't make any calls, it only lets you define one-off functions within the body of a larger function. Since the in portion merely returns TickCounter, then it means this function only ever returns TickCounter and does nothing else.
Furthermore, startGettingEmailData is returning a Task, which means it only gets invoked when in a port. You can't use it in a function that only returns an Action, because it would never get run.
You'll instead want to write a port that triggers on a timer, then creates a Task which polls your url, then maps the response of that GET request to an Action, calling the actions mailbox. You can use Task.onError to write a simple error handler, which could forward an error message to your view by creating an Error String constructor on your Action type.
Here's an example:
getEmailData _ =
let
request =
Http.get emailJsonDecoder jsonUrl
|> Task.map AddEmails
in
request
`Task.onError` (\err -> Task.succeed (Error (toString err)))
`Task.andThen` (\action -> Signal.send actions.address action)
port getEmails : Signal (Task a ())
port getEmails =
Signal.map getEmailData (Time.every (Time.minute / 6.0) )
The above code will cause the URL to be retrieved, parsed, then, on success, it will trigger you actions mailbox and cause an update of the view. If there is an error, it will send the new action of Error with a message, which you should handle in the view. (You'll have to add Error String to the Action union type and handle it in the model, update, and view functions).