Skip to content

CRUD trait for Slick 2.0

This post was similar with what I’ve used in the past months before upgrading Slick to 2.0. From this version, some refactoring were needed.

For each of my case class tables I have a RichTable abstract class from which I extend in order to not repeat id column definition for all the tables.


abstract class RichTable[T](tag: Tag, name: String) extends Table[T](tag, name) {
 def id = column[Int]("id", O.PrimaryKey)

and an example of a case class to describe table is:

class Locations(tag:Tag) extends RichTable[LocationRow](tag, "location") with Logging {
def description = column[String]("description")
def addressId = column[Int]("address_id")
def partyId = column[Int]("party_id")
def contactPersonId = column[Int]("contact_person_id", O.Nullable)
def mainLocation = column[Boolean]("main_location")

//fks
def address = foreignKey("fk_address", addressId, Addresses.tableQuery)(_.id)
def contactPerson = foreignKey("fk_contact_person", contactPersonId, Users.tableQuery)(_.id)
def party = foreignKey("fk_party", partyId, Parties.tableQuery)(_.id)

def * = (id.?, description, addressId, partyId, contactPersonId.?, mainLocation) <> (LocationRow.tupled, LocationRow.unapply _)
}

As you noticed in documentation, we have to use TableQuery of type of our case class so I define this in a companion object of the above case class:

object Locations extends GenericTableUtils[Locations, LocationRow] with Logging {

val tableQuery = TableQuery[Locations]

/**
* Override insert in order to generate id dynamically. Return id of inserted entity.
*/
def insert(model: LocationRow)(implicit db: Session): Int = {
model.id match {
case None => {
val generatedId = getNextId(classOf[LocationRow].getSimpleName())
tableQuery += model.copy(id = Some(generatedId))
generatedId
}
case Some(id) => {
tableQuery += model
id
}
}
}

// other specific methods

}

and here it comes my generic trait:

trait GenericTableUtils[T <: RichTable[A], A] {

val tableQuery: TableQuery[T, T#TableElementType]

/**
 * To dynamically generate ids for insert, use sequences (they are created when schema is created).
 */
 def getNextId(seqName: String)(implicit session: Session) =
 (StaticQuery[Int] + "select nextval('" + seqName + "_seq') ").first

/**
 * Find a specific entity by id.
 */
 def findById(id: Int)(implicit session: Session): Option[A] = {
 val byId = tableQuery.findBy(_.id)
 byId(id).list.headOption
 }

/**
 * Delete a specific entity by id. If successfully completed return true, else false
 */
 def delete(id: Int)(implicit session: Session): Boolean =
 findById(id) match {
 case Some(entity) => { tableQuery.where(_.id === id).delete; true }
 case None => false
 }

/**
 * Update a specific entity by id. If successfully completed return true, else false
 */
 def update(id: Int, entity: A)(implicit session: Session): Boolean = {
 findById(id) match {
 case Some(e) => { tableQuery.where(_.id === id).update(entity); true }
 case None => false
 }
 }
 }

Insert method is more tricky to move it in generic trait but other methods works pretty cool there.
Ps: I use Postgresql, that’s why I generate ids in that way.

Export content as static file in Spray

If you want to export some text as a file using Slick scala library, here is an example:


respondWithHeader(`Content-Type`(new ContentType(MediaTypes.`text/plain`, None))) {
   respondWithHeader(`Content-Disposition`("attachment", Map(("filename", fileName)))) {
      complete {
         "text"
      }
   }
}

and use these imports:


import spray.http.HttpHeaders._
import spray.http.HttpCharsets._
import spray.http.MediaTypes._
import spray.http.ContentType
import spray.http.MediaTypes

Code-First Useful Commands

Create Migration:

Add-Migration “Name”

Generate Script From Migration

Update-Database -Script -TargetMigration:”Name”

Revert Applied Migration:

Update-Database -TargetMigration:”Name_of_previous_migration”

 

Spray default config

For REST services in Scala I use spray. Default configuration (hard to find documentation about this), by listing it programmatically:

spray.can.client.idle-timeout="10 s"
 spray.can.client.parsing.max-chunk-ext-count=16
 spray.can.client.parsing.max-chunk-ext-name-length=64
 spray.can.client.parsing.max-chunk-ext-value-length=256
 spray.can.client.parsing.max-chunk-size="1m"
 spray.can.client.parsing.max-content-length="8m"
 spray.can.client.parsing.max-header-count=64
 spray.can.client.parsing.max-header-name-length=64
 spray.can.client.parsing.max-header-value-length="8k"
 spray.can.client.parsing.max-response-reason-length=64
 spray.can.client.parsing.max-uri-length="2k"
 spray.can.client.reaping-cycle="100 ms"
 spray.can.client.request-size-hint=512
 spray.can.client.request-timeout="5 s"
 spray.can.client.response-chunk-aggregation-limit="1m"
 spray.can.client.user-agent-header="spray-can/1.1-M7"
 spray.can.parsing.max-chunk-ext-count=16
 spray.can.parsing.max-chunk-ext-name-length=64
 spray.can.parsing.max-chunk-ext-value-length=256
 spray.can.parsing.max-chunk-size="1m"
 spray.can.parsing.max-content-length="8m"
 spray.can.parsing.max-header-count=64
 spray.can.parsing.max-header-name-length=64
 spray.can.parsing.max-header-value-length="8k"
 spray.can.parsing.max-response-reason-length=64
 spray.can.parsing.max-uri-length="2k"
 spray.can.server.chunkless-streaming="off"
 spray.can.server.idle-timeout="120 s"
 spray.can.server.parsing.max-chunk-ext-count=16
 spray.can.server.parsing.max-chunk-ext-name-length=64
 spray.can.server.parsing.max-chunk-ext-value-length=256
 spray.can.server.parsing.max-chunk-size="1m"
 spray.can.server.parsing.max-content-length="8m"
 spray.can.server.parsing.max-header-count=64
 spray.can.server.parsing.max-header-name-length=64
 spray.can.server.parsing.max-header-value-length="8k"
 spray.can.server.parsing.max-response-reason-length=64
 spray.can.server.parsing.max-uri-length="2k"
 spray.can.server.pipelining-limit=8
 spray.can.server.reaping-cycle="100 ms"
 spray.can.server.remote-address-header="off"
 spray.can.server.request-chunk-aggregation-limit="1m"
 spray.can.server.request-timeout="30 s"
 spray.can.server.response-size-hint="1k"
 spray.can.server.server-header="spray-can/1.1-M7"
 spray.can.server.ssl-encryption="off"
 spray.can.server.stats-support="on"
 spray.can.server.timeout-handler=""
 spray.can.server.timeout-timeout="500 ms"
 spray.can.server.transparent-head-requests="on"
 spray.can.server.verbose-error-messages="off"
 spray.io.io-bridge-dispatcher.type="akka.spray.io.IOBridgeDispatcherConfigurator"
 spray.io.parallelism=1
 spray.io.read-buffer-size="4k"
 spray.io.tcp.keep-alive=0
 spray.io.tcp.no-delay=0
 spray.io.tcp.receive-buffer-size=0
 spray.io.tcp.send-buffer-size=0
 spray.routing.file-chunking-chunk-size="512k"
 spray.routing.file-chunking-threshold-size="1m"
 spray.routing.relaxed-header-parsing="off"
 spray.routing.render-vanity-footer="yes"
 spray.routing.verbose-error-messages="off"
 spray.util.log-actor-paths-with-dots="off"
 spray.util.log-actor-system-name="off"
 spray.version="1.1-M7"

Starting with Slick – Part 1

I have chosen slick to query a PostgreSql database for a scala + play 2 project I’m working on. Also, because I needed something stable I’ve chosen lifted embedding api because it’s stable (direct embedding is still in experimental state).

In order to help others (documentation has some limitations) I will post here some code I’ve written.

To map a specific table which looks like:

CREATE TABLE geo_location
(
 latitude double precision,
 longitude double precision NOT NULL,
 altitude double precision NOT NULL,
 id serial NOT NULL,
 CONSTRAINT geo_location_pkey PRIMARY KEY (id)
)

we have to write a mapper class:

import scala.slick.driver.PostgresDriver.simple._

case class GeoLocation(id: Option[Int], latitude: Option[Double], longitude: Double, altitude: Double)

/**
 * Define table "geo_location".
 */
object GeoLocations extends RichTable[GeoLocation]("geo_location") {
  def latitude = column[Double]("latitude")
  def longitude = column[Double]("longitude")
  def altitude = column[Double]("altitude")

   def * = id.? ~ latitude.? ~ longitude ~ altitude <> (GeoLocation, GeoLocation.unapply _)
  def forInsert = latitude.? ~ longitude ~ altitude <> ({ (lat, long, alt) => GeoLocation(None, lat, long, alt) },
    { g: GeoLocation => Some((g.latitude, g.longitude, g.altitude)) })

}

As you see, I had to declare id as Optional and also a project named forInsert (consider it a hack in order to force PostgreSql driver to auto-inc our id for the follwing case, an INSERT statement):

GeoLocations.forInsert.insert(GeoLocation(None, 22.23, 25.36, 22.22))

Also, RichTable is an abstract class in order to not declare ids for each Table I have but just extend this::

abstract class RichTable[T](name: String) extends Table[T](name) {
  def id = column[Int]("id", O.PrimaryKey, O.AutoInc)

  val byId = createFinderBy(_.id)
}

5 tips to make your customers happy

I’m a freelancer in my free time.

I do some projects for fun to keep in touch with new technologies. I had the opportunity to work for clients living in US or Europe. And I tried to make great stuff to see them happy. Most of the times, I think I’ve succeded.

Here are a few tips I recommend (entirely from my experience).

1. Be honest

Yes, this is extremely important. Be honest about your skills. Be honest about what you know and what you don’t. Be honest about what you promise. Don’t say “I’m an expert” if you don’t are. I met so-called “experts” who wrote code which was very close to ruin the business of the client. It’s better to say “I’m not an expert” and write great code than to say “I’m an expert” and write poor code. Seriously.

2. Meet deadlines

Sometimes your client ask you for a time estimation. If you say 1 month, make sure you finish it a few days earlier. If you say one week, finish it after 5 days. Your client will really apreciate this.

3. Be available

Sometimes my clients have some urgent things to do. When I say urgent I don’t mean to add a feature which takes 12 days of work. I mean that my client found an ugly bug and wants to see it solved asap. Maybe you’re reading a programming book or play a game or just rest. But if you have the chance to hear skype/email buzz from one of your clients just answer them and be available. I promise you’ll get a great feedback when your project is done.

4. Communicate

I’ve seen a lof of complaints from clients who were dissapointed about their freelancers. In most of the situations, the biggest issue was communication. If you have a missunderstanding just ask your client. Don’t go on with the project spending client’s money on wrong direction and in the end tell “Sorry but I tought that…”. No! Just communicate. Ask. Answer.

5. Keep in touch

I’ve seen job postings like: “You need to keep in touch with me daily”. Do you know why this requests are there? Because that client met freelancers who was working 15 days on the product and sent to the client no mail about the progress. Please, don’t do this. Just keep him in touch with your progress. Your client will really apreciate this!.

Have fun and don’t forget: if you’re a great programmer, you will be first concerned about your client’s satisfaction and his product. Not money first. Not you first. Client first.

Happy programming.

Play Framework 2 – tutorials

As most of you may already know, I am a huge fan of Play Framework since 2010 (I remember when I discovered it and other colleagues I shown the demo laughed at me, not trusting this framework then, now I am sure they no longer laugh:).

Since 2011, I have done about 4 projects in this framework, mostly in my free time (what a pity that in 2010 I was not looking for a full-time job in Play… I would have been an expert now:). A few weeks ago, I started a new project in Play 2(check it here if you want), using Java (soon I ‘ll switch to Scala). Well, I just wanted to put down here some links to some great resources you may need to start with Play 2 quickly:

VIDEO: recording of James Ward JavaOne 2012 presentation, “Client/Server Apps with HTML5 and Java”

Text:

Books:

Have fun!!!

Ps: For Spring, Struts, JSF (even SEAM!) etc developers, a small advice for your future: the time has come to make a choice:)

Follow

Get every new post delivered to your Inbox.