SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

近未来の技術トレンドを先取り! 「Tech-Sketch」出張所

ScalaのWebアプリケーションフレームワーク「Play Framework」入門
~(2)CRUD操作を行うWebアプリケーションの作成 前編

近未来の技術トレンドを先取り! 「Tech-Sketch」出張所 第13回

  • X ポスト
  • このエントリーをはてなブックマークに追加

簡単アプリの開発(2)

(2)ソースコードのジェネレート

 「登録」ボタンをクリックした時の処理を実装する前に、DBアクセス用オブジェクト「Tables」を作成します。「Tables」は、Slickのコードジェネレータを用いて作成することにします。Slickのコードジェネレータはバージョン「2.0」でリリースされた機能で、DBスキーマからソースコードをジェネレートすることができます。

 SlickのコードジェネレータはScalaのAPIから実行する方法と、sbtから実行する方法の2通りがあります。今回は、先ほど作成したイベント登録画面に「Generate」ボタンを追加し、ScalaのAPI経由でジェネレートを実行します。本来は開発環境でのみボタンを表示するなどの考慮が必要ですが、説明を簡単にするため、今回はイベント登録画面からジェネレートが実行できるようにします。

 「scala.slick.model.codegen.SourceCodeGenerator」の「main」メソッドを呼び出すことでジェネレータを実行することができます。「main」メソッドにはDB接続ユーザを指定する方法としない方法の2種類の呼び出し方法が準備されています。

(1)ユーザ/パスワードを指定しない場合
scala.slick.model.codegen.SourceCodeGenerator.main(
  Array(slickDriver, jdbcDriver, url, outputFolder, pkg)
)
(2)ユーザ/パスワードを指定する場合
scala.slick.model.codegen.SourceCodeGenerator.main(
  Array(slickDriver, jdbcDriver, url, outputFolder, pkg, user, password)
)

 ジェネレータの実行に必要なパラメータは次のとおりです。「設定例」には、先ほど作成したDBスキーマからジェネレートする場合の設定内容を記述しています。

設定項目 説明 設定例
slickDriver Sclickドライバー名をフルパスで指定 scala.slick.driver.H2Driver
jdbcDriver JDBCドライバー名をフルパスで指定 org.h2.Driver
url DB接続URL jdbc:h2:tcp://localhost:9092/demo;SCHEMA=TECHAPP
outputFolder 生成コード出力先 app
pkg 生成コードのパッケージ models
user DB接続ユーザ名 sa
password DB接続パスワード <空文字>

 今回は「TECHAPP」スキーマからソースコードをジェネレートしますが、現バージョンでは「url」内に記述したスキーマ名(SCHEMA=TECHAPP)は有効になりませんので、呼び出し部分をカスタマイズしてジェネレートすることにします(スキーマを指定しない(H2であれば「PUBLIC」スキーマを使用する)場合は、(2)の方法で呼び出すことができます)。

 ジェネレータはDBスキーマから取得したテーブルの情報を元にソースコードを生成します。まず、テーブルの情報を取得する処理を「Model」に実装します。「models」パッケージを作成し、「Events.scala」を次のように実装します。

[Model]Events.scala(/app/models)

package models

import play.api.db.DB
import play.api.Play.current
import scala.slick.driver.H2Driver.simple._
import scala.slick.jdbc.meta.{ MTable, createModel }
import scala.slick.driver.JdbcDriver

object Events {

  /** DBコネクション */
  val database = Database.forDataSource(DB.getDataSource())

  /** Modelの取得 */
  def model = database.withSession { implicit session =>
    val tables = MTable.getTables(None, Some("TECHAPP"), None, None).list
    createModel(tables, JdbcDriver)
  }

}

 「Database.forDataSource(DB.getDataSource())」とすることで、「application.conf」に設定したDBスキーマへのコネクションを取得することができます。「createModel」メソッドでジェネレートに必要な「scala.slick.model.Model」を取得し、戻り値として返します。「scala.slick.jdbc.meta.MTable」の「getTables」でテーブルのリストを取得しており、その第二引数でスキーマを指定することができます。オプション型で指定する必要がありますので、「Some("TECHAPP")」とします。

 コントローラに「generate」メソッドを追加し、ジェネレータの実行処理を実装します。

[Controller]EventCreate.scala(/app/controllers/event)

package controllers.event

import play.api._
import play.api.mvc._
import play.api.data.Form
import play.api.data.Forms._
import scala.slick.model.codegen.SourceCodeGenerator
import models.{ EventForm, Events }

object EventCreate extends Controller {

  /** イベントフォーム */
  val eventForm = Form(
    mapping(
      "eventId" -> text,
      "eventNm" -> text)(EventForm.apply)(EventForm.unapply))

  /** 初期表示 */
  def index = Action {
    Ok(views.html.event.eventCreate(eventForm))
  }

  /** コード生成 */
  def generate = Action {
    val slickDriver = "scala.slick.driver.H2Driver"
    val outputFolder = "app"
    val pkg = "models"
    val model = Events.model
    new SourceCodeGenerator(model).writeToFile(slickDriver, outputFolder, pkg)

    Ok(views.html.event.eventCreate(eventForm))
  }
}

 「Events.scala」に作成した「Events.model」を呼び出し、「scala.slick.model.Model」を取得します。そして、「scala.slick.model.codegen.SourceCodeGenerator」の「writeToFile」でソースコードを出力します。slickDriverは「scala.slick.driver.H2Driver」を使用し、作成しているアプリケーション内にソースコードをジェネレートさせるように出力先フォルダは「app」とします。パッケージは「models」として生成するようにします。

 ジェネレータ実行後は再度イベント登録の初期表示画面を表示するようにしておきましょう。

 そして、イベント登録画面からジェネレートが実行できるよう、「generate」メソッドを呼び出すためのリンクをイベント登録画面に追加しておきましょう。

[View]eventCreate.scala.html(/app/views/event)

<a href="@controllers.event.routes.EventCreate.generate()" class="btn">
  generate
</a>

 routesファイルにジェネレート用のURLを定義します。

routes(/conf)

GET     /event/generate/         controllers.event.EventCreate.generate

 それでは、イベント登録画面を表示し、「generate」ボタンをクリックしてソースコードのジェネレートを実行してみましょう。

 ジェネレートを実行すると「models」パッケージに「Tables.scala」というファイルが生成されているはずなので、開いてみましょう。Eclipseからはプロジェクトを「リフレッシュ」するとファイルが見えるようになります。

Tables(/models)

package models
// AUTO-GENERATED Slick data model
/** Stand-alone Slick data model for immediate use */
object Tables extends {
  val profile = scala.slick.driver.H2Driver
} with Tables

/** Slick data model trait for extension, choice of backend or usage in the cake pattern. (Make sure to initialize this late.) */
trait Tables {
  val profile: scala.slick.driver.JdbcProfile
  import profile.simple._
  import scala.slick.model.ForeignKeyAction
  // NOTE: GetResult mappers for plain SQL are only generated for tables where Slick knows how to map the types of all columns.
  import scala.slick.jdbc.{GetResult => GR}
  
  /** DDL for all tables. Call .create to execute. */
  lazy val ddl = Event.ddl
  
  /** Entity class storing rows of table Event
   *  @param id Database column ID AutoInc, PrimaryKey
   *  @param eventId Database column EVENT_ID 
   *  @param eventNm Database column EVENT_NM  */
  case class EventRow(id: Int, eventId: String, eventNm: String)
  /** GetResult implicit for fetching EventRow objects using plain SQL queries */
  implicit def GetResultEventRow(implicit e0: GR[Int], e1: GR[String]): GR[EventRow] = GR{
    prs => import prs._
    EventRow.tupled((<<[Int], <<[String], <<[String]))
  }
  /** Table description of table EVENT. Objects of this class serve as prototypes for rows in queries. */
  class Event(_tableTag: Tag) extends Table[EventRow](_tableTag, Some("TECHAPP"), "EVENT") {
    def * = (id, eventId, eventNm) <> (EventRow.tupled, EventRow.unapply)
    /** Maps whole row to an option. Useful for outer joins. */
    def ? = (id.?, eventId.?, eventNm.?).shaped.<>({r=>import r._; _1.map(_=> EventRow.tupled((_1.get, _2.get, _3.get)))}, (_:Any) =>  throw new Exception("Inserting into ? projection not supported."))
    
    /** Database column ID AutoInc, PrimaryKey */
    val id: Column[Int] = column[Int]("ID", O.AutoInc, O.PrimaryKey)
    /** Database column EVENT_ID  */
    val eventId: Column[String] = column[String]("EVENT_ID")
    /** Database column EVENT_NM  */
    val eventNm: Column[String] = column[String]("EVENT_NM")
  }
  /** Collection-like TableQuery object for table Event */
  lazy val Event = new TableQuery(tag => new Event(tag))
}

 Tablesトレイトに「Event」という名前のクラスと「EventRow」という名前のケースクラスが作成されています。各クラスにはテーブルのカラムに対応するプロパティが生成されており、それぞれのプロパティの型はテーブルのカラムの型を元に生成されています。「Integer型」の「ID」は「Int型」に、「VARCHAR型」の「イベントID」は「String型」にマッピングされます。「DATE型」の場合は「SQLDATE型」にマッピングされます。また、テーブルのカラムにNULL値が許容される(NOT NULL制約が付与されていない)場合は「Option型」になります。

 今回は、特定のスキーマを利用してのジェネレートが必要だったため、ScalaのAPI経由で実行する方法を利用しました。sbtから実行すれば、コンパイルのタイミングでジェネレートタスクを実行するようにもできます。実行方法は、GitHub上の「slick-codegen-example」や「Typesafe Activator」の「"Using Slicks default code generator」テンプレートが参考になります。

次のページ
簡単アプリの開発(3)

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
近未来の技術トレンドを先取り! 「Tech-Sketch」出張所連載記事一覧

もっと読む

この記事の著者

前出 祐吾(TIS株式会社)(マエデ ユウゴ)

TIS株式会社 コーポレート本部 戦略技術センター所属。これまで社内向けWebアプリケーションフレームワークの開発やJenkinsの活用を中心に様々な手段で開発の効率化を図ってきた。現在は、さらに革命的な効率化を目指し、Scalaを活用した次世代開発基盤の研究&開発に従事している。システム開発は、泥...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/7816 2014/06/18 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング