Load different resources on urlUpdate elm - elm

I am new to elm and functional programming. Hope this is a simple question. What I am trying to do is, when I change views I want elm to fetch records based on the view that it is about to change to. I was hoping I could do it in the urlUpdate method based on the currentRoute. I have two views one for players and one for perks, both have independent commands objects. I tried to do it like this:
urlUpdate : Result String Route -> Model -> ( Model, Cmd Msg )
urlUpdate result model =
let
currentRoute =
Routing.routeFromResult result
_ =
Debug.log "Current Route" currentRoute
in
if toString currentRoute == "PerksRoute" then
( { model | route = currentRoute }
, Perks.Commands.fetchAll
)
else if toString currentRoute == "PlayersRoute" then
( { model | route = currentRoute }
, Players.Commands.fetchAll
)
else
( { model | route = currentRoute }, Cmd.none )
However I get this error:
The 1st branch has this type:
( { a | route : Route }, Cmd Perks.Messages.Msg )
But the 2nd is:
( { a | route : Route }, Cmd Players.Messages.Msg )
I am not sure why this is happening, I would think that having this Type defined at would be ok.
type Msg
= PlayersMsg Players.Messages.Msg
| PerksMsg Perks.Messages.Msg
Here is the full src

You need to use Cmd.map to map the child command to the parent:
if toString currentRoute == "PerksRoute" then
( { model | route = currentRoute }
, Cmd.map PerksMsg Perks.Commands.fetchAll
)
else if toString currentRoute == "PlayersRoute" then
( { model | route = currentRoute }
, Cmd.map PlayersMsg Players.Commands.fetchAll
)

Related

Elm record shorthand notation

To get better at functional programming in JavaScript, I started leaning Elm.
In JavaScript you have the object shorthand notation:
const foo = 'bar';
const baz = { foo }; // { foo: 'bar' };
I was wondering whether there is something like that in Elm? I'm asking because I have the following model:
type alias Model =
{ title : String
, description : String
, tag : String
, tags : List String
, notes : List Note
}
type alias Note =
{ title : String
, description : String
, tags : List String
}
And an update function that upon receiving the AddNote action adds the note to the notes array and clears the inputs:
AddNote ->
{ model | notes = model.notes ++ [ { title = model.title, description = model.description, tags = model.tags } ], title = "", description = "", tag = "" }
I know that in function definitions you can "destructure" records, but I think even in the return type I would have to explicitly type out each key of the record.
AddNote ->
{ model | notes = model.notes ++ [ getNote model ], title = "", description = "", tag = "" }
getNote : Model -> Note
getNote { title, description, tags } =
{ title = title, description = description, tags = tags }
There is not a shorthand record notation similar to JavaScript objects.
However, a type alias also serves as a constructor function, so you could have something like this:
getNote : Model -> Note
getNote { title, description, tags } =
Note title description tags

CakePHP3 filtering based on custom rules

I have following schema:
User hasMany RegistrationState
RegistrationState belongsTo User
to its classic 1->M relation where user_id is in RegistrationState table.
RegistrationState has following attributes
id, user_id, state, created_at ... ( others are not relevant )
User can have more registration states for example:
Registration state -> 1, created -> 01-02-2017
Registration state -> 2, created -> 01-03-2017
Registration state -> 3, created -> 01-04-2017
Registration state -> 4, created -> 01-05-2017
Registration state -> 2, created -> 01-06-2017
Registration state -> 3, created -> 01-07-2017
I need to filter based on this 'state property', where the valid state is last one created.
Valid state for this example is 3, because its latest state.
Now, i have controler, and index method where i have
// push custom query for filtering to finder method
$this->paginate['finder'] = [
'clients' => ['q' => $this->request->query]
];
try {
$clients = $this->paginate( $this->Users );
} catch (\Exception $e) {
// redirect on main
return $this->redirect($this->here);
}
My finder method looks like this
public function findClients($query, array $options)
{
$opts = $query->getOptions()['q'];
if (!empty($opts['email'])) {
$query->andWhere(['email LIKE' => '%' . trim($opts['email']) . '%']);
}
if (!empty($opts['identity_num'])) {
$cn = $opts['identity_num'];
$query->matching('Identities', function ($query) use ($cn) {
$query->andWhere(['Identities.number' => $cn]);
});
}
// registration state
if (!empty($opts['reg_state'])) {
// WHAT SHOULD BE HERE???
}
return $query;
}
There is also another 1->M relation User -> Identity, but ( matching method ) but that works fine because number is always unique.
I can't resolve problem with registration states, how can I implement this kind of search in CakePHP3? ( i dont want to break pagination so i would like to solve this in finder method )
One of possible (dirty?) solutions to this is subquery ( INNER JOIN on RegistrationState )
// registration state
if (!empty($opts['reg_state'])) {
$conn = ConnectionManager::get('default');
$subquery = $conn->execute(
'SELECT uir.user_id ' .
'FROM registration_state uir ' .
'INNER JOIN ( ' .
'SELECT user_id, max(created) as m_created ' .
'FROM registration_state ' .
'GROUP BY user_id ' .
') muir ON uir.user_id = muir.user_id AND uir.created = muir.m_created AND uir.status = '
. intval( $opts['reg_state'])
);
$usersInStateIds = [];
// transform result of subquery to usable array
foreach( $subquery->fetchAll('assoc') as $r ) {
$usersInStateIds[] = intval( $r['user_id'] );
}
$query->matching('RegistrationState', function ($query) use ($usersInStateIds) {
return $query->andWhere(['RegistrationState.user_id IN' => $usersInStateIds]);
});
}

Strange error using Type Bool in Record in elm

i have the following type User:
type alias User =
{ id : Int
, name : String
, age : Maybe Int
, deleted : Bool
}
User is a type used in my Model:
type alias Model =
{ users : List User
, name : String
, age : String
, message : String
}
When I iterate over "List User" using List.map like this...
Delete id ->
let
newUserList =
List.map
(\user ->
if user.id == id then
{ user | deleted = True }
else
user
)
model.users
in
( { model | users = newUserList }, Cmd.none )
... the Compiler tells me:
The 2nd argument to function `map` is causing a mismatch.
List.map
(\user ->
if user.id == id then
{ user | deleted = True }
else
user
)
model.users
Function `map` is expecting the 2nd argument to be:
List { a | id : Int, name : String }
But it is:
List (Bool -> User)
That is pretty strange for me.
Why does my map function change the Type User...?
I do not change it, I just iterate over, map each user and if I found the right one, by its id, I change deleted value to True...
I am a bit confused...
Can anyone help?
kind regards :)
UPDATE: It does not seem to me a problem of the List.map function but of the type alias User declaration.
As soon as I add another value this breaks...
Here is the whole code for it. It is kept pretty simple.
Note: As soon as you uncomment the Users property "deleted" the compiler throws an error
module Main exposing (..)
import Html exposing (Html, text, h1, div, img, input, form, ul, li, i, hr, br)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Html.App as App
import String
import Random
--import Debug
--import Uuid
main : Program Never
main =
App.program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
--MODEL
type alias Model =
{ users : List User
, name : String
, age : String
, message : String
}
type alias User =
{ id : Int
, name : String
, age :
Maybe Int
-- , deleted : Bool
}
init : ( Model, Cmd Msg )
init =
( initModel, Cmd.none )
initModel : Model
initModel =
{ users = []
, name = ""
, age = ""
, message = ""
}
--UPDATE
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
InsertName username ->
( { model | name = username }, Cmd.none )
InsertAge age ->
let
newAge =
case String.toInt age of
Err err ->
""
Ok value ->
toString value
newMessage =
case String.toInt age of
Err err ->
"Age must be a number!"
Ok int ->
""
in
( { model | age = newAge, message = newMessage }, Cmd.none )
InitNewUser ->
( model, Random.generate AddNewUser (Random.int 1 9999) )
AddNewUser randomId ->
if String.isEmpty model.name then
( { model | message = "Please give a name" }, Cmd.none )
else
let
ageAsInt =
case String.toInt model.age of
Err err ->
Nothing
Ok int ->
Just int
newUser =
User randomId model.name ageAsInt
newUserList =
newUser :: model.users
in
( { model | users = newUserList, name = "", age = "" }, Cmd.none )
Delete id ->
let
newUserList =
List.map
(\user ->
if user.id == id then
{ user | name = "--deleted--" }
else
user
)
model.users
in
( { model | users = newUserList }, Cmd.none )
--VIEW
type Msg
= InsertName String
| InsertAge String
| AddNewUser Int
| InitNewUser
| Delete Int
userListView : Model -> Html Msg
userListView model =
let
newList =
List.filter (\user -> (user.name /= "--deleted--")) model.users
in
newList
|> List.sortBy .name
|> List.map userView
|> ul []
userView : User -> Html Msg
userView user =
let
ageAsString =
case user.age of
Just val ->
val |> toString
Nothing ->
"-"
in
li []
[ div [] [ text ("ID: " ++ toString user.id) ]
, div [] [ text ("Name: " ++ user.name) ]
, div [] [ text ("Age: " ++ ageAsString) ]
, input [ type' "button", value "Delete", onClick (Delete user.id) ] []
]
view : Model -> Html Msg
view model =
div [ class "wrapper" ]
[ h1 [] [ text ("We have " ++ toString (List.length model.users) ++ " Users") ]
, Html.form []
[ input [ type' "text", onInput InsertName, placeholder "Name", value model.name ] []
, input [ type' "text", onInput InsertAge, placeholder "Age", value model.age ] []
, input [ type' "button", onClick InitNewUser, value "Add new user" ] []
]
, div [] [ text model.message ]
, userListView model
, hr [] []
, div [] [ text (toString model) ]
]
The problem is this part of the AddNewUser message:
newUser =
User randomId model.name ageAsInt
It is missing False as the forth argument when you use the deleted property.
If you do not include it, the User function will return a partially applied function that still needs a Bool to return a proper user. The compiler seems to get thrown off by this even though all your types and functions have proper annotations.
Another way that leads to a better error message would be to define newUser like this:
newUser =
{ id = randomId
, name = model.name
, age = ageAsInt
, deleted = False
}

how to update an inner record in elm

I have this model
type alias Model =
{ exampleId : Int
, groupOfExamples : GroupExamples
}
type alias GroupExamples =
{ groupId : Int
, results : List String
}
In my update function, if I want to update the exampleId would be like this:
{ model | exampleId = updatedValue }
But what if I need to do to update, for example, just the results value inside of GroupExamples?
The only way to do it in the language without anything extra is to destructure the outer record like:
let
examples = model.groupOfExamples
newExamples = { examples | results = [ "whatever" ] }
in
{ model | groupOfExamples = newExamples }
There is also the focus package which would allow you to:
set ( groupOfExamples => results ) [ "whatever" ] model

Simplifying recursive updating of records

Can this code be simplified?
update : Action -> Model -> Model
update action model =
let
formValue = model.formValue
in
case action of
UpdateWhat what ->
let
newValue = { formValue | what <- what }
in
{ model | formValue <- newValue }
UpdateTrigger trigger ->
let
newValue = { formValue | trigger <- trigger }
in
{ model | formValue <- newValue }
As I plan to add a couple more of Update... clauses, it is helpful to abstract this out a bit.
The code is written the way it is because Elm does not accept inner record updates.
I think what you're looking for is the focus library:
Focus
A Focus is a way to work with particular parts of a large chunk of data. On the most basic level, it lets you get and set fields of a record in a simple and composable way. This means you could avoid writing special record update syntax and use something that composes much more elegantly.
It gives you the ability to write stuff like freeze in the following snippet:
mario =
{ super = False
, fire = False
, physics = { position = { x=3, y=4 }
, velocity = { x=1, y=1 }
}
}
freeze object =
set (physics => velocity) { x=0, y=0 } object
In the code example physics and velocity are Foci. You can create a focus with code like the following, to use your example:
formValue = Focus.create .formValue (\f r -> { r | formValue <- f r.formValue })
what = Focus.create .what (\f r -> { r | what <- f r.what })
trigger = Focus.create .trigger (\f r -> { r | trigger <- f r.trigger })
update : Action -> Model -> Model
update action model =
case action of
UpdateWhat w -> Focus.set (formValue => what) w model
UpdateTrigger t -> Focus.set (formValue => trigger) t model