Cristian Boariu
Cristian Boariu

Reputation: 9621

How to pass user to each request?

I have a play 2 app where I use securesocial module. In request there is Identity available but I need my user object (which extends Identity) to pass to my templates (because user contains some additional fields) so for each controller I have code like:

def index = SecuredAction { implicit request =>
    implicit val currentUser = UsersDAO.Users.findByIdentityId(request.user.identityId).get
    Ok(views.html.platform.support.index())
  }

Basically for each controller's method where a user is logged in I am doing a request to the DB to get current logged in user which I found pretty ugly. Is there any way I can have this loaded once per user session?

UPDATE:

I added a partial function to make things a little bit simpler

def loadCurrentUser(f: (User) => Result)(implicit request: SecuredRequest[play.api.mvc.AnyContent]): Result =
        (for {
            loggedInUser <- UsersDAO.Users.findByIdentityId(request.user.identityId)
        } yield f(loggedInUser)).getOrElse(
            Redirect(securesocial.controllers.routes.LoginPage.login))

and now each method from controllers will be like:

def index = SecuredAction { implicit request =>
    loadCurrentUser { implicit user =>
        Ok(views.html.platform.support.index())
    }
}

but I would still like to have user in request or pass it trought request somehow after he has logged in and not load it each time from DB.

Upvotes: 0

Views: 126

Answers (1)

Michael Zajac
Michael Zajac

Reputation: 55569

I don't think it would be wise to store the entire user within the session, as I imagine it's considered bad practice, and it would make updating the session messy when the user object has been changed (particularly by another user, perhaps an administrator).

I would use the cache API if you want to avoid DB calls for every request, however that will still require care for updating/invalidating the cache when the user has changed. At least in the cache it will all be in one place, though.

While your loadCurrentUser function will make it absolutely sure that user exists in the database, saving the usage of .get, I think it's a bit much. If loadCurrentUser was called in the first place, that user should exist, and if they somehow don't, their session should have been invalidated therefore blocking the request at the SecureSocial level.

Consider defining:

implicit def currentUser(implicit request: SecuredRequest[AnyContent]): User = UsersDAO.Users.findByIdentityId(request.user.identityId).get

This would shorten your calls to currentUser within the SecuredAction. This is also the technique used by Play2 Auth.

And if you want to avoid a trip to the database with each request, perhaps a function that returns a cached user:

import play.api.cache.Cache

def findByIdentityId(id: Id): Option[User] = ...

def findCachedByIdentityId(id: Id): Option[User] = {
    Cache.getAs[User](s"User.$id").orElse{
        val user: Option[User] = findByIdentityId(id)
        user.foreach(user => Cache.set(s"User.${user.id}"), user)
        user
    }
}

It would also be worthwhile to look into Action Composition.

Upvotes: 1

Related Questions