Thursday 5 September 2013

Building an assessment app in 2+ɛ days -- 16th commit

Right, back to it then… After the fun of the CEO's visit this morning, (and a big long sleep last night) back to work on the assessment app.

The students are going to be using it on Monday, so that's a deadline I can't let whoosh past me, as it's not just me making up a deadline for myself.

User updates in a functional world

The commit I just pushed changes course pre-enrolments so they happen automatically when the browser asks for the user's courses.

That means this is a request that changes something about the user -- it changes the user's registration.

I've written the app in a way that is functional and typically works with immutable data. That means there's one small additional wrinkle in this request.

DataAction.returning.one automatically converts an item to JSON for the requesting user. It uses an Approval[User] that typically has a LazyId for the user, fetched the first time it's needed.

But, I happen to have written this particular app in a functional style -- with immutable data objects. (You don't have to write your app with immutable data types, I just did for this one.) And this request modifies the user.

If your data types are immutable, you can find yourself with a small bug where this happens:

  1. We ask for the user, because to look up any pre-enrolments, we need a list of their social identities

  2. This triggers the lazy reference to the user to load.

  3. We find a pre-enrolment in the database, and update the user's registrations.

  4. We return the course from DataAction.returning.one

  5. But the Approval in the request has an immutable representation of the user that was fetched before we registered them to the new course, and the JSON comes out as if they weren't registered.

The solution involves a change to one line of code, and the addition of two more:

  1. Change the method from DataAction.returning.one to DataAction.returning.json

    (or from DataAction.returning.many to DataAction.returning.manyJson)

  2. Create a new approval for the updated user.

  3. Call the JsonConverter with the new Approval

We can see this in CourseController.myCourses

def myCourses = DataAction.returning.manyJson 
{ implicit request =>

  val userAfterUpdates = for (
    u <- request.user; 
    updated <- doPreenrolments(u)
  ) yield updated

  // As we've updated the user, we'll need a new Approval
  val approval = Approval(userAfterUpdates)

And at the end of the method:

  approved <- approval ask Permissions.ViewCourse(c.itself);
  j <- CourseToJson.toJsonFor(c, approval)
) yield j

No comments: