2014年4月26日土曜日

Play FrameworkのSlickでevolutionを使ったテーブル管理をしてみた

今回はPlay FrameworkにSlickを入れて、evolutionを使ったテーブル管理をやってみました。

バージョン
Play Framework 2.2.2
slick 2.0.1
play-slick 0.6.0.1

build.sbtに下記を追加する。
  "com.typesafe.slick" %% "slick" % "2.0.1",
  "org.slf4j" % "slf4j-nop" % "1.7.6",
  "com.typesafe.play" % "play-slick_2.10" % "0.6.0.1"

application.confでevolutionを有効にし、slickでテーブルを作る対象をmodels.*で指定する。
evolutionplugin=enabled
slick.default="models.*"

上で指定したパッケージ配下にmodelを作成
package models
 
import play.api.db.slick.Config.driver.simple._
import java.sql.Date
import models.dao.DAO
 
case class Twitter(id: Int, word: String, ins_date: Date, upd_date: Date, ng_flg : Boolean)
  
class Twitters(tag: Tag) extends Table[Twitter](tag,"twitter"){
 
  def id = column[Int]("id",O.PrimaryKey,O.AutoInc)
  def word = column[String]("word",O.NotNull)
  def ins_date = column[Date]("ins_date",O.NotNull)
  def * = (id,word,ins_date) <> (Twitter.tupled,Twitter.unapply)
}
 
object Twitters extends DAO{
    
  def insert(word:String,ins_date:Date)(implicit s: Session) = {
    Twitters map{
      r => (r.word,r.ins_date)
    } +=(word,ins_date)
  }
  
  def findByWord(word:String)(implicit s: Session):List[Int] = {
    Twitters filter {
      _.word === word
    } map {
     _.id
    } list()
  }
}

DAOは分けてこう書いておくとよいと見かけたので、この形で作成。
ちなみにTableを継承してないclassがslickの対象パッケージにいるとエラー出るので別の階層にしないといけない。
package models.dao
 
import models.h2.Twitters
import scala.slick.lifted.TableQuery
 
private[models] trait DAO{
  val Twitters = TableQuery[Twitters]
}

ひととおり揃えたのでplay runして実行させるとエラーが出た。。。
エラーメッセージ
Found a matching object: modules.Twitter for modules.Twitter but it is not a Slick Table and a wildcard was not specified"

テーブルとして見てもらえてないようなメッセージ。
application.confの指定を*でなく指定する形の以下にしてみたもののだめ。
slick.default="models.Twitters"

結局、この形で記述してみたらうまくいきました。(h2はDBにh2database使ってるため)
最初のだとdaoとTwitter.scalaが同一ディレクトリにいるのがだめなようで、
指定した場所にTable以外のものが入ってないようにしないといけないようです。
slick.default="models.h2.*"


参考URL
http://akiomik.hatenablog.jp/entry/2014/02/23/033444
http://windymelt.hatenablog.com/entry/2014/03/30/191152
https://github.com/freekh/play-slick/blob/6b49ec019e00158c96df12d673856c53830ed1e6/code/src/main/scala/play/api/db/slick/ddl/TableScanner.scala
http://nilfigo.hatenablog.com/entry/2013/09/10/173552

2014年4月24日木曜日

Play FrameworkのAkkaでスケジューラ処理

Play FrameworkのAkkaでスケジューラ処理をさせました。
バージョン
Play Framework 2.2.2

GlobalSettingsを継承して、onStartをoverrideさせることで実現できました。
package common

import akka.actor._
import scala.concurrent.duration._
import play.api._
import play.api.libs.json._
import play.api.libs.concurrent._
import play.api.Play.current
import play.api.libs.concurrent.Execution.Implicits._
import service.TwitterService
import play.Logger
 
object Global extends GlobalSettings {
  
  override def onStart(app: Application){
   val actor = Akka.system.actorOf(Props(new TwitterService), "twitterservice")    
    Akka.system.scheduler.schedule(0.milliseconds, 1.minutes, actor, "tick")
  }  
}
TwitterServiceは↓に載せてあります。twitter4jを使って現在のtrendを取得する処理です。
Akkaに処理を渡す形になるのでActorを継承する必要があります。
Akka.system.scheduler.schedule(0.milliseconds, 1.minutes, actor, "tick")
は起動して0ミリ秒から1分置きにactorを動かして、actorに"tick"を送ります。
package service
 
import twitter4j.Twitter
import twitter4j.TwitterFactory
import twitter4j.Trend
import play.api.libs.concurrent.Akka
import akka.actor._
import akka.actor.Actor._
import org.slf4j.LoggerFactory
import play.Logger
import twitter4j.conf.ConfigurationBuilder
 
class TwitterService extends Actor{
   
  def receive = {
    case i:Int => {
      Logger.error("aa")
      println("aa")
    }
    case msg:String => {
      Logger.error(msg)
      println(msg)
      getTrend(23424856)
    }
    case _ => {
      Logger.error("tetete")
      println("tetete")
      getTrend(23424856)
    }
  }
  
  def getTrend(place:Int):Array[Trend] = {
    
    var twitter = twitterFactory.getInstance()
    var trendsResources = twitter.trends()
    var trends = trendsResources.getPlaceTrends(place)
    
    for(trend:Trend <- trends.getTrends()){
     println(trend.getName())
    }
  }  
}
def receiveにcase文で3つ処理を書きましたが、
"tick"を送っているので、case msg:Stringの処理が実行されます。

application.confのapplication.globalでGlobal.scalaの配置位置を指定できます
# Global object class
# ~~~~~
# Define the Global object class for this application.
# Default to Global in the root package.
# application.global=Global
application.global=common.Global

これでplay runで実行すると、1分置きにtwitterのtrendを取得しに行く処理が動きます。

参考URL
http://www.playframework.com/documentation/2.2.x/ScalaGlobal
http://stackoverflow.com/questions/20924195/play-framework-2-2-scala-onstart-doesnt-work