User Registration Example

Step 1 : Design your schema

Let us decide upon the user schema first . A Simple User schema would look like this
Tablename => user
id         long , PRIMARY KEY , AUTOINCREMENT
first_name  string 
last_name   string
mobile      long
email       string

We will now make use of evolutions to create the table for the above schema

Create a new package evolutions.default under the conf directory. Now add a new file with name “1.sql” that contains the evolution script as below

# User schema
# --- !Ups
create table `user` (`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,`first_name` 
TEXT
NOT NULL,`last_name` TEXTNOT NULL,`mobile` BIGINT NOT NULL,
`email` TEXT
NOT NULL) # --- !Downs
drop table `user`

Step 2 : Define a User Model for the above schema design

Create model package under app folder .
Now create a User.scala class in the model package (app>model>User) and define a case class like below

case class User(id : Long , firstName : String, lastName : String , mobile : Long , email : String)

Step 3 : Implement the data access layer(app.dao)

Add a new package dao inside the app folder.
Add UserDAO trait inside the dao package , with all the required User CRUDs

trait UserDAO {
  def add(user:User) : Future[String]
  def get(id : Long) : Future[Option[User]] 
  def delete(id : Long) : Future[Int]
  def listAll : Future[Seq[User]]
}
 

Implement the UserDAO trait using UserDAOImpl class .

Play uses Google Guice DI framework to handle Dependency Injection . Guice provides an out-of-box implementation that helps to automatically inject required objects at run time .
Since the database is configured using PlaySlick , we can obtain the database configuration inside a dao class using DatabaseConfigProvider trait provided by the PlaySlick module .

Now describe a database schema with Table row classes for the tables and implement the abstract CRUD methods

package dao

import javax.inject.Inject
import model.User
import play.api.db.slick.DatabaseConfigProvider
import slick.driver.JdbcProfile
import scala.concurrent.Future

@Singleton
class UserDAOImpl @Inject()(dbConfigProvider: DatabaseConfigProvider) extends UserDAO {

  private val dbConfig = dbConfigProvider.get[JdbcProfile]

  import dbConfig._
  import driver.api._

  class UserTable(tag:Tag)
    extends Table[User](tag, "user") {
    def id = column[Long]("id", O.PrimaryKey,O.AutoInc)
    def firstName = column[String]("first_name")
    def lastName = column[String]("last_name")
    def mobile = column[Long]("mobile")
    def email = column[String]("email")

    override def * =
      (id, firstName,lastName,mobile,email) <> (User.tupled, User.unapply)
  }

  implicit val users = TableQuery[UserTable]

  override def add(user: User): Future[String] = {
    db.run(users += user).map(res => "User successfully added").recover {
      case ex : Exception => ex.getCause.getMessage
    }
  }

  override def delete(id: Long): Future[Int] = {
    db.run(users.filter(_.id === id).delete)
  }

  override def get(id: Long): Future[Option[User]] = {
    db.run(users.filter(_.id === id).result.headOption)
  }

  override def listAll: Future[Seq[User]] = {
    db.run(users.result)
  }
}

Now bind the trait to its implementation using Guice . The simplest way to do this using guice is by adding @ImplementedBy annotation to the trait. So your final dao trait should look like

package dao

import com.google.inject.ImplementedBy
import model.User
import scala.concurrent.Future

@ImplementedBy(classOf[UserDAOImpl])
trait UserDAO {

  def add(user:User) : Future[String]
  def get(id : Long) : Future[Option[User]] 
  def delete(id : Long) : Future[Int]
  def listAll : Future[Seq[User]]

}
[error] E:\MyFirstPlay\app\dao\UserDAOImpl.scala:37: Cannot find an implicit ExecutionContext. You might pass
[error] an (implicit ec: ExecutionContext) parameter to your method
[error] or import scala.concurrent.ExecutionContext.Implicits.global.
[error]     db.run(users += user).map(res => "User successfully added").recover {
[error]                              ^
[error] one error found
[error] (compile:compileIncremental) Compilation failed

So , to provide an implicit execution , you can do it in two ways :

Import the implicit context using the import statement
import play.api.libs.concurrent.Execution.Implicits._
 Or 
import scala.concurrent.ExecutionContext.Implicits.global

OR
Pass an additional parameter to your class definition
class UserDAOImpl @Inject()(dbConfigProvider: DatabaseConfigProvider) (implicit ec: ExecutionContext) extends UserDAO {
....
}

Step 4 : Implement Service Layer( app.service)

Add a new package service inside the app folder.
Add UserService trait inside the service package , with all the required User Service Methods

trait UserService {
  def addUser(user:User) : Future[String]
  def getUser(id : Long) : Future[Option[User]] 
  def deleteUser(id : Long) : Future[Int]
  def listAllUsers : Future[Seq[User]]
}
 

Implement the UserService trait using UserServiceImpl class .

package service

import javax.inject.Inject
import dao.UserDAO
import model.User
import scala.concurrent.Future

@Singleton
class UserServiceImpl @Inject()(userDAO: UserDAO) extends UserService {
  override def addUser(user: User): Future[String] = {
    userDAO.add(user)
  }

  override def deleteUser(id: Long): Future[Int] = {
    userDAO.delete(id)
  }

  override def getUser(id: Long): Future[Option[User]] = {
    userDAO.get(id)
  }
  override def listAllUsers: Future[Seq[User]] = {
    userDAO.listAll
  }
}

Now again bind the trait to its implementation .

package service

import com.google.inject.ImplementedBy
import model.User
import scala.concurrent.Future

@ImplementedBy(classOf[UserServiceImpl])
trait UserService {
  def addUser(user:User) : Future[String]
  def getUser(id : Long) : Future[Option[User]] 
  def deleteUser(id : Long) : Future[Int]
  def listAllUsers : Future[Seq[User]]
}

Step 5 : Implement the View Layer(app.views)

In HTML, the data is usually submitted through html forms . So we need to define a form structure for our User case class to help play handle to the form data in an easy and well-defined manner, with the help of play-forms

Create a userForm for the corresponding User case class that we define above

case class UserFormData(firstName : String, lastName : String , mobile : Long , email : String )

object UserForm {

  val form = Form(
    mapping(
      "firstName" -> nonEmptyText,
      "lastName" -> nonEmptyText,
      "mobile" -> longNumber,
      "email" -> email
    )(UserFormData.apply)(UserFormData.unapply)
  )
}

Create a html file (user.scala.html) inside the views package. Pass the empty userform to be displayed on the html page . The implicit Request header and messages are provided to support Internationalization

@(userForm: Form[model.UserFormData],users : Seq[model.User])(implicit request: RequestHeader,messages : Messages)
@main(Messages("app.name")) {
@request.flash.get(messages("flash.success")).map { msg =>
<div class="col-md-6 col-md-offset-3 alert alert-danger alert-error">
    <a href="#" class="close" data-dismiss="alert">&times;</a>
    <strong>@msg</strong>
</div>
}
@request.flash.get(messages("flash.error")).map { msg =>
<div class="col-md-6 col-md-offset-3 alert alert-danger alert-error">
    <a href="#" class="close" data-dismiss="alert">&times;</a>
    <strong>@msg</strong>
</div>
}
<form id="user-data-form" role="form" action='@routes.UserController.addUser()' method="post" class="form-horizontal" align="center" autocomplete="off">

    <fieldset class="user-fieldset">

        <div class="user-form">
            <label class="form-title" style="color: #F8741B; font-size: 22px;font-weight: bold; text-decoration:none">@messages("page.title")</label>
        </div>
        <br/>
        <table align="center" cellspacing="20">
            <tr>
                <td align="left">
                    <div class="user-form" id="firstName_field_label">
                        <div class="controls col-xs-offset-3 col-xs-9">
                            <div class="input-group">
                                <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span>
                                <strong>@messages("firstname")</strong> :
                            </div>
                        </div>
                    </div>
                </td>
                <td align="center">
                    <div class="user-form" id="firstName_field_value">
                        <div class="controls col-xs-offset-3 col-xs-9">
                            <div class="input-group">
                                <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span>
                                <input type="text" id="firstName" name="firstName" value="" placeholder="First Name" class="form-control input-lg" required>
                            </div>
                        </div>
                    </div>
                </td>
            </tr>
            <tr>
                <td align="left">
                    <div class="user-form" id="lastName_field_label">
                        <div class="controls col-xs-offset-3 col-xs-9">
                            <div class="input-group">
                                <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span>
                                <strong>@messages("lastname")</strong> :
                            </div>
                        </div>
                    </div>
                </td>
                <td align="center">
                    <div class="user-form" id="lastName_field_value">
                        <div class="controls col-xs-offset-3 col-xs-9">
                            <div class="input-group">
                                <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span>
                                <input type="text" id="lastName" name="lastName" value="" placeholder="Last Name" class="form-control input-lg" required>
                            </div>
                        </div>
                    </div>
                </td>
            </tr>
            <tr>
                <td align="left">
                    <div class="user-form" id="mobile_field_label">
                        <div class="controls col-xs-offset-3 col-xs-9">
                            <div class="input-group">
                                <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span>
                                <strong>@messages("mobile")</strong> :
                            </div>
                        </div>
                    </div>
                </td>
                <td align="center">
                    <div class="user-form" id="mobile_field_value">
                        <div class="controls col-xs-offset-3 col-xs-9">
                            <div class="input-group">
                                <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span>
                                <input type="tel" id="mobile" name="mobile" placeholder="Mobile" class="form-control input-lg" required>
                            </div>
                        </div>
                    </div>
                </td>
            </tr>
            <tr>
                <td align="left">
                    <div class="user-form" id="email_field_label">
                        <div class="controls col-xs-offset-3 col-xs-9">
                            <div class="input-group">
                                <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span>
                                <strong>@messages("email")</strong> :
                            </div>
                        </div>
                    </div>
                </td>
                <td align="center">
                    <div class="user-form" id="email_field_value">
                        <div class="controls col-xs-offset-3 col-xs-9">
                            <div class="input-group">
                                <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span>
                                <input type="email" id="email" name="email" placeholder="Email" class="form-control input-lg" required>
                            </div>
                        </div>
                    </div>
                </td>
            </tr>
        </table>
        <br/>
        <div class="form-actions controls ynt-btn-xlarge">
            <button type="submit" class="btn btn-primary ynt-btn-orange">Add</button>
        </div>

    </fieldset>
</form>

<div class="user-display" >
    <fieldset>
        <legend align="center"><h3>Registered Users</h3></legend>
        <table cellspacing="20">
            <tr>
                <th>@messages("userid")</th>
                <th>@messages("firstname")</th>
                <th>@messages("lastname")</th>
                <th>@messages("mobile")</th>
                <th>@messages("email")</th>
            </tr>
            @for(user <- users){
            <tr>
                <td>@user.id</td>
                <td>@user.firstName</td>
                <td>@user.lastName</td>
                <td>@user.mobile</td>
                <td>@user.email</td>
                <td><a href="@routes.UserController.deleteUser(user.id)">delete</a></td>
            </tr>
            }
        </table>
    </fieldset>
</div>

}

Add a new file messages in the conf package. And add these lines into the file

# App name
app.name = My First Play

#Title
page.title = User Registration

# Flash Scope
flash.success = success
flash.error = error

#Labels
userid=UserId
firstname = FirstName
lastname = LastName
mobile = Mobile
email = Email

Step 6 : Implement the Controller Layer (app.controller)

Add the UserController inside the controller package . It should look like below.

package controllers

import com.google.inject.Inject
import model.{User, UserForm}
import play.api.mvc.{Action, Controller}
import service.UserService
import scala.concurrent.Future
import play.api.i18n.{MessagesApi, Messages, I18nSupport}
import play.api.libs.concurrent.Execution.Implicits._

class UserController @Inject()
(userService: UserService,
 val messagesApi: MessagesApi) extends Controller with I18nSupport {

  def home = Action.async { implicit  request =>
    userService.listAllUsers map { users =>
      Ok(views.html.user(UserForm.form,users))
    }
  }

  def addUser() = Action.async { implicit request =>
    UserForm.form.bindFromRequest.fold(
      // if any error in submitted data
      errorForm => Future.successful(Ok(views.html.user(errorForm,Seq.empty[User]))),
      data => {
        val newUser = User(0,data.firstName,data.lastName,data.mobile,data.email)
        userService.addUser(newUser).map(res =>
          Redirect(routes.UserController.home()).flashing(Messages("flash.success") -> res)
        )
      })
  }

  def deleteUser(id : Long) = Action.async { implicit request =>
    userService.deleteUser(id) map { res =>
      Redirect(routes.UserController.home())
    }
  }

}

Now the final step will be to add the corresponding paths in routes(conf.routes)

#User
GET     /home                       controllers.UserController.home
POST    /add                        controllers.UserController.addUser
GET     /delete/:id                 controllers.UserController.deleteUser(id : Long)

Thats it !! We are done with the implementation of User Registration. Run the application to add and manage users 

Open localhost:9000 in your browser

Click “Apply this script now !” button and that should create your required table in the given database


7 comments:

  1. The UserDAOImpl doesn't compile. I get a "expected start of definition" error. The error position is the class keyword before UserDAOImpl.
    I copied the code 1:1 so I have no idea why it doesn't work

    Best regards,
    Phil

    ReplyDelete
    Replies
    1. It is because of the empty line between the @singleton tag and start of class definition . There tag should be just above the class definition .Thanks for the comment , will correct i now

      Delete
  2. I did not really understand what is the role of userForm in the user.scala.html, I see that it's only used in the controller.

    ReplyDelete
    Replies
    1. Well in case we make use of the "play helpers" package for form handling in our views , then the play form(userForm) can be directly mapped and used.

      Eg :
      @helper.form(action = @routes.UserController.addUser()) {
      @helper.inputText(userForm("firstName"))
      @helper.inputText(userForm("lastName"))
      ....
      }

      In this example I made use of plain HTML instead of helper packages.

      Form handling can be done through HTML form , play helper form or mix of both

      Delete
    2. Thank you very much for your reply

      Delete
  3. Thank you and wish you good luck :)

    ReplyDelete
  4. I just couldn't leave your website before telling you that I truly enjoyed the top quality info you present to your visitors? Will be back again frequently to check up on new posts. 188bet link

    ReplyDelete