I'm doing the Elm exercises in Exercism again and there's a thing unclear to me so far. How does the "as" destructuring work? In the beginning I didn't understand anything. After read Yang Wei's Elm destructuring (or pattern matching) cheatsheet, more specifically this part:
myRecord = { x = 1, y = 2, z = 3}
computeSomething ({x, y} as wholeRecord) =
-- x and y refer to the x and y fields of the passed in record
-- wholeRecord is the complete record
-- i.e. x and wholeRecord.x refer to the same field
-- but z is only accessible as wholeRecord.z
A lot of things became more cleat to me. The problem happened when I tried to make it by myself. So, the Ticket, Please! problem imports three types, called Ticket, Status and User and I should write a functions that receives a ticket and an user and returns a modified ticket, as the given example:
assignTicketTo (User "Danny")
(Ticket
{ status = New
, createdBy = ( User "Jesse", 3 )
, assignedTo = Just (User "Alice")
, comments = [ ( User "Jesse", "I've been waiting for 6 months!!" ) ]
}
)
-- => Ticket
-- { status = InProgress
-- , createdBy = ( User "Jesse", 3 )
-- , assignedTo = Just (User "Danny")
-- , comments = [ ( User "Jesse", "I've been waiting for 6 months!!" ) ]
-- }
The big problem comes here, because after descruture the Ticket variable and use as to attribute it to a different variable (as below), the language tells me that the new variable has a different type, i.e. it is not a Ticket.
assignTicketTo : User -> Ticket -> Ticket
assignTicketTo user ({ status, assignedTo } as ticket) =
if (status == New) then
{ ticket | status = InProgress, assignedTo = Just user }
else
ticket
What am I doing wrong? I read the answer about Type mismatch when trying to destructure type in Elm, but the only thing that I can imagine I could possibly be doing wrong, the desctructuring, is not correct either:
assignTicketTo : User -> Ticket -> Ticket
assignTicketTo user (Ticket { status, assignedTo } as ticket) =
if (status == New) then
{ ticket | status = InProgress, assignedTo = Just user }
else
ticket
Edit: The error messages are:
This is not a record, so it has no fields to update!
45| Ticket { ticket | status = InProgress, assignedTo = Just user }
^^^^^^
This `ticket` value is a:
Ticket
But I need a record!
and
The 2nd argument to `assignTicketTo` is weird.
43| assignTicketTo user ({ status, assignedTo } as ticket) =
^^^^^^^^^^^^^^^^^^^^^^
The argument is a pattern that matches record values of type:
{ c | assignedTo : a, status : b }
But the type annotation on `assignTicketTo` says the 2nd argument should be:
Ticket
The problem here is that Ticket is not a record type. It's a custom type with a single variant containing a record. Its definition (which is essential context that you should have posted in the question) is:
type Ticket
= Ticket
{ status : Status
, createdBy : ( User, Int )
, assignedTo : Maybe User
, comments : List ( User, String )
}
To destructure this you have to first unwrap the custom type, then unwrap the record and give just that a name:
Ticket ({ status, assignedTo } as ticket)
And then to return a Ticket again you also have to wrap the record back up in the Ticket constructor after updating it. The full working function therefore is:
assignTicketTo : User -> Ticket -> Ticket
assignTicketTo user (Ticket ({ status, assignedTo } as ticket)) =
if status == New then
Ticket { ticket | status = InProgress, assignedTo = Just user }
else
Ticket ticket
Why the Ticket type has been designed in this way I don't know. It seems unnecessarily convoluted and invites confusion. So please don't feel too bad about that!
Related
I want to load user profile before rendering something into the page but the whole user profile is composed of different parts that are loaded by multiple HTTP requests.
So far I'm loading user profile in sequence (one by one)
type alias CompanyInfo =
{ name: String
, address: ...
, phone: String
, ...
}
type alias UserProfile =
{ userName: String
, companyInfo: CompanyInfo
, ...
}
Cmd.batch
[ loadUserName userId LoadUserNameFail LoadUserNameSuccess
, loadCompanyInfo userId LoadCompanyInfoFail LoadCompanyInfoSuccess
...
]
But that's not very effective. Is there a simple way how to perform a bunch of Http requests and return just one complete value?
Something like this
init =
(initialModel, loadUserProfile userId LoadUserProfileFail LoadUserProfileSuccess)
....
You can achieve this using Task.map2:
Edit: Updated to Elm 0.18
Task.attempt LoadUserProfile <|
Task.map2 (\userName companyInfo -> { userName = userName, companyInfo = companyInfo })
(Http.get userNameGetUrl userDecoder |> Http.toTask)
(Http.get companyInfoGetUrl companyInfoDecoder |> Http.toTask)
You can then get rid of the individual LoadUserName... and LoadCompanyInfo... Msgs. In Elm 0.18, the need for separate Fail and Succeed Msgs is addressed by Task.attempt expecting a Result Error Msg type, so that LoadUserProfile is defined like this:
type Msg
= ...
| LoadUserProfile (Result Http.Error UserProfile)
map2 will only succeed once both tasks succeed. It will fail if any of the tasks fail.
I'm wondering if I can get some help with iterating a list of groups, making a POST request for each group to create a 'room', iterating the users for each group and making a POST request to assign them to this specific room.
I have the following model.
model = {
groups = [
{
title = "Foo"
, users = [
{ name = "Joe" }
, { name = "Mary" }
]
},
{
title = "Bar"
, users = [
{ name = "Jill" }
, { name = "Jack" }
]
}
]
}
The desired result is that the room Foo was created and Joe and Mary were assigned, and Bar was created and Jill and Jack were assigned.
The view, for now, would just be a simple button that triggers an action.
div []
[ button [ onClick InviteUsersToRoom ] [ text "Invite users to room" ] ]
I've created 2 POST requests:
createRoom: take a title, create a room using the title and return the room_id
addUser: take a room_id and a user's name, add the the users to the room and return the status of ok
example:
-- create a room for each group
-- passing in `title` as the room name
-- which will return the room id from `decodeCreateRoomResponse`
createRoom : String -> String -> Cmd Msg
createRoom title =
Task.perform
CreateRoomsFail
CreateRoomsSuccess
(Http.post
decodeCreateRoomResponse
("https://some_api?room=" ++ title)
Http.empty
)
decodeCreateRoomResponse : Json.Decoder String
decodeCreateRoomResponse =
Json.at ["room", "id"] Json.string
-- add a user to a room using a `room_id` and the user's name
-- returns a bool from `decodeAddUserResponse`
addUser : String -> String -> Cmd Msg
addUser room_id user =
Task.perform
AddUserFail
AddUserSuccess
(Http.post
decodeCreateChannelResponse
("https://some_api?room=" ++ room_id ++ "&user=" ++ user)
Http.empty
)
decodeAddUserResponse : Json.Decoder String
decodeAddUserResponse =
Json.at ["ok"] Json.bool
I'm wondering how you'd go about stitching this up altogether, so that onclick :
iterate each group
make the POST to create the Room
take the room_id from the response and iterate the users
POST the room_id and the users name
Any help is appreciated.
You've got a few scattered errors that I won't explicitly point out because the compiler will help you, but you're off to a good start. You already have some Http handling Cmds built up so you just need to wire things up with your update function.
Let's define your Model explicitly (you may already be doing this but it isn't in your example):
type alias User =
{ name : String }
type alias Group =
{ title : String
, users : List User
}
type alias Model =
{ groups : List Group }
Based off your functions, here's how I interpret your Msg type, with one small change which is to add a list of users as a parameter to CreateRoomsSuccess.
type Msg
= InviteUsersToRoom
| CreateRoomsFail Http.Error
| CreateRoomsSuccess (List User) String
| AddUserFail Http.Error
| AddUserSuccess Bool
Now we can tweak createRoom in order to pass along the list of users to create. Note that this isn't creating any users at this time. It is using currying to create a partially-applied function so that when the CreateRoomsSuccess case is handled in the update function, it already has the list of users that need to be created (rather than having to look them up in the model list):
createRoom : Group -> Cmd Msg
createRoom group =
Task.perform
CreateRoomsFail
(CreateRoomsSuccess group.users)
(Http.post
decodeCreateRoomResponse
("https://some_api?room=" ++ group.title)
Http.empty
)
To create the list of rooms, you simply map the list of groups to a list of Cmds that perform the post. This will happen when the button is clicked:
case action of
InviteUsersToRoom ->
model ! List.map createRoom model.groups
...
You'll have to implement the update cases for when errors occur. Next up, we have to handle the CreateRoomsSuccess message. This is where you'll need to look up the list of users for a group. Again, you'll map over the function you already created that handles the Http task:
case action of
...
CreateRoomsSuccess users roomID ->
model ! List.map (addUser roomID << .name) users
...
You'll have to handle the AddUserFail and AddUserSuccess cases, but the above examples should help you understand how to post multiple messages and act accordingly based on the success or failure of each.
This is probably more simple than I'm making it sound.
I'm allowing my users to create their myprofile when the signin. This is a document that is stored in MyProfile = new Meteor.Collection('myprofile');. The principle is exactly the same as LinkedIn...you login, and you fill out a profile form and any edits you make simply updates that one document.
Within the document there will be fields such as 'summary' 'newsInterest' and others, also 'owner' which is the users Id.
1) How can I insert a document into the MyProfile collection with the 'owner' field being the userId of the newly created user on StartUp?
This is so that the data of this document, the values of these fields will be passed onto the myprofile page. Initially the values returned will be blank but as the user types, upon keyup the myprofile document will be updated.
Users are created as follows on the client. This is fine for now.
2) In addition, please provide any links if people have created users on the server. I called a method to insert the following as an object into Meteor.users.insert(object);but this does not work.
Template.join.events({
'submit #join-form': function(e,t){
e.preventDefault();
Accounts.createUser({
username: t.find('#join-username').value,
password: t.find('#join-password').value,
email: t.find('#join-email').value,
profile:{
fullname: t.find('#join-fullname').value,
summary: [],
newsInterest: [],
otherstuff: []
}
});
Router.go('myprofile');
}
});
1) In order to solve issue one you have two options.
Instead of having a separate collection for profiles like you would in a normalized MySQL database for example. Add the users profile data within the profile object already attached to objects in the user collection. You can then pass in the values you want in the options parameter of the Accounts.createUser function
Template.join.events({
"submit #join-form": function (event) {
event.preventDefault();
var firstName = $('input#firstName').val(),
lastName = $('input#lastName').val(),
username = firstName + '.' + lastName,
email = $('input#email').val(),
password = $('input#password').val(),
profile = {
name: firstName + ' ' + lastName
};
Accounts.createUser({
email: email,
username: username,
password: password,
profile: profile
}, function(error) {
if (error) {
alert(error);
} else {
Router.go('myprofile');
}
});
}
});
This is an example using jQuery to get the values but your t.find should work equally fine.
If you really do want to use a separate collection then I recommend using the following code inside the onCreateUser function (server side) instead:
Accounts.onCreateUser(function(options, user) {
user._id = Meteor.users._makeNewID();
profile = options.profile;
profile.userId = user._id;
MyProfile.insert(profile);
return user;
});
When you want to update or add additional data into the profile field for a user you can use the following:
var newProfile = {
summary: 'This summary',
newsInterest: 'This newsInterest',
otherstuff: 'Stuff'
};
Meteor.users.update(Meteor.userId, {$set: {profile: newProfile}});
Or if you went for the separate collection option the following:
var newProfile = MyProfile.findOne(Meteor.userId);
newProfile.summary = 'This summary';
newProfile.newsInterest = 'This newsInterest';
newProfile.otherstuff = 'Stuff';
MyProfile.update(Meteor.userId, newProfile);
Haven't tested this so let my know if I have any syntax / typo errors and I'll update.
Q2: you have to send email, password and profile object to server and use the same Accounts.createUser. Everything works fine.
Question 1 is easily resolved using the Accounts.onCreateUser callback on the server - it provides the user object to the callback, so you can just take the userId from this and insert a new "myProfile" with that owner.
As Will Parker points out below, the user document won't actually have an _id at this point though, so you need to create one and add it to the user object that the callback returns. You can do this with:
user._id = Meteor.users._makeNewID();
I'm making a real push to understand the async powers of Play but finding a lot of conflict with regard to places where async invocation fits and places where the framework seems to conspire against its use.
The example I have relates to form validation. Play allows for ad-hoc constraints to be defined - see this from the docs:
val loginForm = Form(
tuple(
"email" -> email,
"password" -> text
) verifying("Invalid user name or password", fields => fields match {
case (e, p) => User.authenticate(e,p).isDefined
})
)
Nice and clean. However, if I'm using a fully async data access layer (e.g. ReactiveMongo), such a call to User.authenticate(...) would return a Future and I'm thus in the dark as to how I can utilise the power of both the built in form binding features and the async tools.
It's all well and good to publicise the async approach but I'm getting frustrated that certain parts of the framework don't play so well with it. If the validation has to be done synchronously, it seems to defeat the point of the async approach. I've come across a similar problem when using Action composition - e.g. a security related Action that would make a call to ReactiveMongo.
Can anyone shed any light on where my comprehension is falling short?
Yes, validation in Play is designed synchronously. I think it's because assumed that most of time there is no I/O in form validation: field values are just checked for size, length, matching against regexp, etc.
Validation is built over play.api.data.validation.Constraint that store function from validated value to ValidationResult (either Valid or Invalid, there is no place to put Future here).
/**
* A form constraint.
*
* #tparam T type of values handled by this constraint
* #param name the constraint name, to be displayed to final user
* #param args the message arguments, to format the constraint name
* #param f the validation function
*/
case class Constraint[-T](name: Option[String], args: Seq[Any])(f: (T => ValidationResult)) {
/**
* Run the constraint validation.
*
* #param t the value to validate
* #return the validation result
*/
def apply(t: T): ValidationResult = f(t)
}
verifying just adds another constraint with user-defined function.
So I think Data Binding in Play just isn't designed for doing I/O while validation. Making it asynchronous would make it more complex and harder to use, so it kept simple. Making every piece of code in framework to work on data wrapped in Futures is overkill.
If you need to use validation with ReactiveMongo, you can use Await.result. ReactiveMongo returns Futures everywhere, and you can block until completion of these Futures to get result inside verifying function. Yes, it will waste a thread while MongoDB query runs.
object Application extends Controller {
def checkUser(e:String, p:String):Boolean = {
// ... construct cursor, etc
val result = cursor.toList().map( _.length != 0)
Await.result(result, 5 seconds)
}
val loginForm = Form(
tuple(
"email" -> email,
"password" -> text
) verifying("Invalid user name or password", fields => fields match {
case (e, p) => checkUser(e, p)
})
)
def index = Action { implicit request =>
if (loginForm.bindFromRequest.hasErrors)
Ok("Invalid user name")
else
Ok("Login ok")
}
}
Maybe there's way to not waste thread by using continuations, not tried it.
I think it's good to discuss this in Play mailing list, maybe many people want to do asynchronous I/O in Play data binding (for example, for checking values against database), so someone may implement it for future versions of Play.
I've been struggling with this, too. Realistic applications are usually going to have some sort of user accounts and authentication. Instead of blocking the thread, an alternative would be to get the parameters out of the form and handle the authentication call in the controller method itself, something like this:
def authenticate = Action { implicit request =>
Async {
val (username, password) = loginForm.bindFromRequest.get
User.authenticate(username, password).map { user =>
user match {
case Some(u: User) => Redirect(routes.Application.index).withSession("username" -> username)
case None => Redirect(routes.Application.login).withNewSession.flashing("Login Failed" -> "Invalid username or password.")
}
}
}
}
Form validation means syntactic validation of fields, one by one.
If a filed does not pass the validation it can be marked (eg. red bar with message).
Authentication should be placed in the body of the action, which may be in an Async block.
It should be after the bindFromRequest call, so there must me after the validation, so after each field is not empty, etc.
Based on the result of the async calls (eg. ReactiveMongo calls) the result of the action can be either BadRequest or Ok.
Both with BadRequest and Ok can redisplay the form with error message if the authentication failed. These helpers only specify the HTTP status code of the response, independently to the response body.
It would be an elegant solution to do the Authentication with play.api.mvc.Security.Authenticated (or write a similar, customized action compositor), and use Flash scoped messages. Thus the user always would be redirected to login page if she is not authenticated, but if she submits the login form with wrong credentials the error message would be shown besides the redirect.
Please take a look on the ZenTasks example of your play installation.
The same question was asked in the Play mailing list with Johan Andrén replying:
I'd move the actual authentication out of the form validation and do it in your action instead and use the validation only for validation of required fields etc. Something like this:
val loginForm = Form(
tuple(
"email" -> email,
"password" -> text
)
)
def authenticate = Action { implicit request =>
loginForm.bindFromRequest.fold(
formWithErrors => BadRequest(html.login(formWithErrors)),
auth => Async {
User.authenticate(auth._1, auth._2).map { maybeUser =>
maybeUser.map(user => gotoLoginSucceeded(user.get.id))
.getOrElse(... failed login page ...)
}
}
)
}
I've seen on theguardian's GH repo how they handle this case scenario in a asynchronous way while still having the support of the form error helpers from play. From a quick look, seems like they are storing the form errors in an encrypted cookie in a way as to display those errors back to the user the next time the user goes to the login page.
Extracted from: https://github.com/guardian/facia-tool/blob/9ec455804edbd104861117d477de9a0565776767/identity/app/controllers/ReauthenticationController.scala
def processForm = authenticatedActions.authActionWithUser.async { implicit request =>
val idRequest = idRequestParser(request)
val boundForm = formWithConstraints.bindFromRequest
val verifiedReturnUrlAsOpt = returnUrlVerifier.getVerifiedReturnUrl(request)
def onError(formWithErrors: Form[String]): Future[Result] = {
logger.info("Invalid reauthentication form submission")
Future.successful {
redirectToSigninPage(formWithErrors, verifiedReturnUrlAsOpt)
}
}
def onSuccess(password: String): Future[Result] = {
logger.trace("reauthenticating with ID API")
val persistent = request.user.auth match {
case ScGuU(_, v) => v.isPersistent
case _ => false
}
val auth = EmailPassword(request.user.primaryEmailAddress, password, idRequest.clientIp)
val authResponse = api.authBrowser(auth, idRequest.trackingData, Some(persistent))
signInService.getCookies(authResponse, persistent) map {
case Left(errors) =>
logger.error(errors.toString())
logger.info(s"Reauthentication failed for user, ${errors.toString()}")
val formWithErrors = errors.foldLeft(boundForm) { (formFold, error) =>
val errorMessage =
if ("Invalid email or password" == error.message) Messages("error.login")
else error.description
formFold.withError(error.context.getOrElse(""), errorMessage)
}
redirectToSigninPage(formWithErrors, verifiedReturnUrlAsOpt)
case Right(responseCookies) =>
logger.trace("Logging user in")
SeeOther(verifiedReturnUrlAsOpt.getOrElse(returnUrlVerifier.defaultReturnUrl))
.withCookies(responseCookies:_*)
}
}
boundForm.fold[Future[Result]](onError, onSuccess)
}
def redirectToSigninPage(formWithErrors: Form[String], returnUrl: Option[String]): Result = {
NoCache(SeeOther(routes.ReauthenticationController.renderForm(returnUrl).url).flashing(clearPassword(formWithErrors).toFlash))
}
I have a message table that self joins to itself where Message.ID == Message.Parent. So I have 1 Message with several ChildMessages. This is set up nicely with a Navigation property.
The code is currently:
var message = from m in soe.Messages.Include("ChildMessages")
where m.ID == id
&& m.IsActive
select m;
return message.FirstOrDefault();
What I really want is to only return 1 Message (that is equal to a particular ID and isActive) with all of its Active ChildMessages and I want to do it in one call to the database.
I have a solution for 4.0 (I'm not sure if it will work on 3.5, i hadn't check).
First, set your entities model Lazy Loading Enabled property to false. Note that as a result you will lose the references auto-loading.
Next, try this code:
var message = soe.Messages
.Where(m => (m.ID == id || m.ParentId == id) && m.IsActive)
.AsEnumerable()
.FirstOrDefault(e => e.Id == id);
The EF will resolve the parent-child references, and will not load other child references unless an explicit request will be made (like Include or Load). Notice that you must iterate over the Where results using AsEnumerable or otherwise they won't load.
I've concluded this is not possible in Entity framework.
A work around would be to return an anonymous type that satisfies the constraint. E.g.
var message = from m in soe.Messages
where m.ID == id
&& m.IsActive
select
new
{
Message = m,
ChildMessages = m.ChildMessages.Where(c => c.IsActive)
};
return message.FirstOrDefault()