Typesafe Activator

Play Slick with Typesafe IDs

Play Slick with Typesafe IDs

VirtusLab
Source
December 28, 2013
advanced play scala slick

Slick (the Scala Language-Integrated Connection Kit) is a framework for type safe, composable data access in Scala. This template combines Play Framework with Slick and adds tools to use type-safe IDs for your classes so you can no longer join on bad id field or mess up order of fields in mappings. It also provides way to create service with methods (like querying all, querying by id, saving or deleting), for all classes with such IDs in just 4 lines of code.

How to get "Play Slick with Typesafe IDs" on your computer

There are several ways to get this template.

Option 1: Choose play-slick-advanced in the Typesafe Activator UI.

Already have Typesafe Activator (get it here)? Launch the UI then search for play-slick-advanced in the list of templates.

Option 2: Download the play-slick-advanced project as a zip archive

If you haven't installed Activator, you can get the code by downloading the template bundle for play-slick-advanced.

  1. Download the Template Bundle for "Play Slick with Typesafe IDs"
  2. Extract the downloaded zip file to your system
  3. The bundle includes a small bootstrap script that can start Activator. To start Typesafe Activator's UI:

    In your File Explorer, navigate into the directory that the template was extracted to, right-click on the file named "activator.bat", then select "Open", and if prompted with a warning, click to continue:

    Or from a command line:

     C:\Users\typesafe\play-slick-advanced> activator ui 
    This will start Typesafe Activator and open this template in your browser.

Option 3: Create a play-slick-advanced project from the command line

If you have Typesafe Activator, use its command line mode to create a new project from this template. Type activator new PROJECTNAME play-slick-advanced on the command line.

Option 4: View the template source

The creator of this template maintains it at https://github.com/VirtusLab/activator-play-advanced-slick#master.

Option 5: Preview the tutorial below

We've included the text of this template's tutorial below, but it may work better if you view it inside Activator on your computer. Activator tutorials are often designed to be interactive.

Preview the tutorial

You've just created an application with the Play Slick with type-safe IDs template. This template combines Play Framework with Slick and adds tools to use type-safe IDs for your classes so you can no longer join on bad id field or mess up order of fields in mappings. It also provides way to create service with methods (like querying all, querying by id, saving or deleting), for all classes with such IDs in just 4 lines of code. Idea for type-safe ids was derived from Slick creator's presentation on ScalaDays 2013.

Set up the database

The template you're using is already set up with an in-memory instance of the H2 database. You can leave this as it is and skip this section if you want to continue using this database.

It is sometimes advantageous to use H2 in file mode rather than in memory. This will allow debugging through things like the h2-browser or other methods of access to H2. To make this change, edit the conf/application.conf file and find the line that specifies the default url:

db.default.url="jdbc:h2:mem:play"
In order to use a file-based database, change this to
db.default.url="jdbc:h2:/path/to/file
You can then get access to the database by shutting down any processes (including Play) that are accessing it and using the following commands
activator
h2-browser
And then enter the same URL to access H2.

To use a different database technology, use the instructions on the Play Documentation site

Create tables

Play includes a function called evolutions that allows the management of database schema changes. To use evolutions, create an evolutions folder for the default database as conf/evolutions/default then create .sql scripts that will put the database into the appropriate state. Evolutions also has the ability to revert the database to a previous version. This template uses play-slick DDL plugin that creates 1.sql file for you automatically for all tables defined in "model" package. To disable this feature or change package see application.conf and change slick.default="model.*" parameter. In this example, this plugin will create a file called conf/evolutions/default/1.sql with the following content:


    # --- Created by Slick DDL
    # To stop Slick DDL generation, remove this comment and start using Evolutions

    # --- !Ups

    create table "COMMENTS" (
    "ID" BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) NOT NULL PRIMARY KEY,
    "TEXT" VARCHAR NOT NULL,
    "AUTHOR" BIGINT NOT NULL,
    "DATE" TIMESTAMP NOT NULL);

    create table "USERS" (
      "ID" BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) NOT NULL PRIMARY KEY,
      "EMAIL" VARCHAR NOT NULL,
      "FIRST_NAME" VARCHAR NOT NULL,
      "LAST_NAME" VARCHAR NOT NULL
    );

    alter table "COMMENTS" add constraint "COMMENTS_AUTHOR_FK" foreign key("AUTHOR") references "users"("ID") on update NO ACTION on delete NO ACTION;

    # --- !Downs

    alter table "COMMENTS" drop constraint "COMMENTS_AUTHOR_FK";
    drop table "COMMENTS";
    drop table "USERS";
    

When you load up your application, you will be prompted to apply the script(s).

Create the models

This template uses small library called Unicorn that enables usage of type safe primary and foreign keys in your application. Example entity created with this library can be shown here.

Entity declaration contains of four parts:

  • Case class for your type-safe id. It has to mix BaseId and contain id field of type Long. It is also good to extend from AnyVal to get unboxed values.
  • Companion object for your id. It have to extend IdCompanion[YourEntityId]. This step provides you with implicits needed in slick queries and Play! mappings.
  • Your domain model entity. It has to extend WithId[YourEntityId].
  • Table definition for your entity. It extends IdTable[YourEntityId, YourEntity], which accepts table name (and optionally schema name).

In case of any problems related to Slick its' site at slick.typesafe.com has an excellent set of documentation.

Gain base service methods for free!

When you have entity declaration you get (almost) free basic queries and service methods for it. Example is shown in UsersService. It contains trait for queries and trait for service. This 4 lines of code enables you to save your entities, query or delete them by id and some more.

Example usage can be shown in UsersServiceTest.

Adding next class - type-safe joins

Let's add new class to our model. It will be a Comment. With an exception of class names and fields it's same as User.

In queries for comments (CommentsService) we can now create query joining on type-safe userId:


        // parametrized query using UserId
        protected val commentsForUserQuery = for {
            userId <- Parameters[UserId]
            // type-safe join, if you pass here any other id or Long it wont compile!
            comment <- Comments if comment.authorId === userId
        } yield comment
    

Then, in CommentsService you can create service method for searching comments by userId:


        def findForUserId(userId: UserId)(implicit session: Session): Seq[Comment] =
            commentsForUserQuery.list(userId)
    

At last, let's test your new service in CommentsServiceTest:


        object UsersService extends UsersService
        object CommentsService extends CommentsService

        val user = User(None, "test@email.com", "Krzysztof", "Nowak")
        val userId = UsersService save user

        val comments = Seq(
            Comment(None, "It's nice", userId, DateTime.now()),
            Comment(None, "It's very nice", userId, DateTime.now()),
            Comment(None, "Those comments are not spam", userId, DateTime.now())
        )

        val commentIds = CommentsService saveAll comments

        val queriedComments = CommentsService findForUserId userId

        queriedComments.size must be_=== (comments.size)
        queriedComments.flatMap(_.id) must containAllOf(commentIds)
    

Additional goodies

Your type-safe IDs are designed not only to work with Slick, but also with Play! framework internals. You can also use them in mappings (beloved Play! forms API) and routes without any added work and benefit from type-safety in you application.

Using IDs in form mappings:


        val userMapping = Form(
            mapping(
                "id" -> of[UserId], // it will be read from Long and packed to UserId
                "email" -> email,
                "firstName" -> text,
                "lastName" -> text
            )(User.apply)(User.unappply)
        )
    

Using IDs in routes:


        // it will be read from Long and packed to UserId, so you can use it like that in controller method
        /user/show/:id      controllers.UsersController.show(id: model.UserId)
    

About Play! Framework

It is not a scope of this template to dwell into details of the Play! framework. If you have some questions or want to know more about it, you are welcome to browse Play! sources around the web.

The Play Documentation contains much more exhaustive details and also covers a number of other topics which haven't been addressed in this tutorial.
StackOverflow is a great place ask questions about Play.
The play-framework Google Group is a great place to discuss Play.

comments powered by Disqus