Tuesday, March 11, 2014

Slicker than most

Let's start with a song from my college days


So as my Scala journey progressed it was time to pick a persistent engine.  I looked at Anorm and decided on Slick.  Mostly because its supported by TypeSafe which I would assume with their bankroll and influence is a safe option.  Technically, I like that I can still fall back on raw sql if I need to, because my experience with every Java ORM is that you need that option at one point or another.

So the first thing to do was get my model going.  I quickly discovered something called case classes.

1:  case class Meeting (id: Option[Long], meetingEid: String, name: String,   
2:                  var host: Option[String], var hostSalt:Option[String],  
3:                  welcomeMessage:Option[String],dialNumber:Option[String],  
4:                  voiceBridge:Option[String],startTime:DateTime,duration:Int)  

So that is sorta like an immutable POJO in the Java world, but in one line!  It comes with hashcode(), equals(), toString(), all built in. Already I was digging things, that would have probably need like 50 lines of code in Java, albeit my IDE would have vomited most of it out for me, but still, its very concise, me likes.

Based on patterns I found googling around, the next business was to create the Meetings class.  This guy is sorta like what you might do with an hbm file or annotations to describe how columns map to your model.


1:  class Meetings extends Table[Meeting]("MEETING") {  
2:   def id = column[Long]("ID", O.PrimaryKey, O.AutoInc)  
3:   def meetingEid = column[String]("MEETING_EID", O.NotNull)  
4:   def name = column[String]("NAME", O.NotNull)  
5:   def host = column[String]("HOST", O.Nullable)  
6:   def hostSalt = column[String]("HOST_SALT", O.Nullable)  
7:   def welcomeMessage = column[String]("WELCOME_MSG", O.Nullable)  
8:   def dialNumber = column[String]("DIAL_NUMBER", O.Nullable)  
9:   def voiceBridge = column[String]("VOICE_BRIDGE", O.Nullable)  
10:   def startTime = column[DateTime]("START_TIME", O.NotNull)  
11:   def duration = column[Int]("DURATION", O.NotNull)  
12:   def uniqueName = index("IDX_MEETING_EID", meetingEid, unique = true)  
13:   def newMeeting = meetingEid ~ name  
14:   def * = id.? ~ meetingEid ~ name ~ host.? ~ hostSalt.? ~ welcomeMessage.? ~ dialNumber.? ~  
15:    voiceBridge.? ~ startTime ~ duration <> (Meeting.apply _, Meeting.unapply _)  
16:  }  

So most of that is probably pretty obvious until you get to

1:   def * = id.? ~ meetingEid ~ name ~ host.? ~ hostSalt.? ~ welcomeMessage.? ~ dialNumber.? ~  
2:    voiceBridge.? ~ startTime ~ duration &lt;&gt; (Meeting.apply _, Meeting.unapply _)  
What the hell is that crap ?  Yeah, I didn't know either.  What does <> mean ?  What about def * ? Eventually (like hours later)  I came across this, http://stackoverflow.com/questions/13906684/scala-slick-method-i-can-not-understand-so-far which explains about Mapped Projections and Comprehensions.  If you want to understand it read that post, does a much better job than I can, that guy should write a book.  Basically it comes down to, that is how you map a row into your backing object and vice versa.  Its amazing concise and powerful.  Think about all the really verbose ways you've seen that done before, and it will really stop you in your tracks.  On the flip side try googling for "<>" or "_" or "def *".  Once you understand it, its awesome and super easy, but trying to find the information to explain it, that was sorta hard for me.  I thought I was a pretty good goolge user, sheesh

Ok so now we have our DAO which might look something like this:

1:  object MeetingDAO {  
2:   val table = new Meetings  
3:   def createMeeting(meeting: Meeting): Long = DB.withSession { implicit session: Session =&gt;  
4:    table.insert(meeting)  
5:   }  
6:   def listMeetings: List[Meeting] = DB.withSession { implicit session: Session =&gt;  
7:    Query(table).list  
8:   }  
9:   def deleteMeeting(meetingId: Long): Option[Meeting] = DB.withSession {  
10:    implicit session: Session =&gt;  
11:     val query = for {  
12:      meeting &lt;- table if meeting.id === meetingId  
13:     } yield meeting  
14:     val retVal = query.firstOption  
15:     query.delete  
16:     retVal  
17:   }  
18:  }  

That's all pretty straight forward, other than the implicit parameters and Option junk right ?  Ok, so Option I first found annoying, but now I think its awesome. Option basically eliminates NPE's and null checks from your code.  Its a way to type that something can have a value or not.  To access the real value you do someOptionVar.get, and you can test if it has a value with someOptionVar.isEmpty.  All the extra .get calls annoyed me at first, but then when I saw how all the null checks disappeared, but my code was still safe, I had a different opinion.

What else is going on there?  Oh, query.firstOption.  So in Scala a List is called a Seq.  query here is a Seq.   To get the first element as an Option, you can call firstOption. Then back in my calling code I can make the "null" check using the Option.


1:   val entity = MeetingDAO.deleteMeeting(meetingId.toLong)  
2:     if (entity.isEmpty) {  
3:      Home.flashing("warning" -&gt; s"Meeting '${meetingId}' was not found.")  
4:     } else {  
5:      Home.flashing("success" -&gt; s"Meeting '${entity.get.name}' has been deleted.")  
6:     }</span>  
I've just begun to touch the service with Slick, I haven't tried doing complicated queries or even relationships yet, so I suspect will have some more posts about that, when I get to it.  I'll save implicit parameters and implicit methods for another time as well.

No comments:

Post a Comment