playとSquerylでフォームの内容をDBに反映させるまで
前回はDBの操作をひと通り行ったので、今回は前々回で定義したdb.Postを使ってフォームの内容をDBに反映させます。
前々回の記事
play frameworkでSqueryl Schema及びentity定義するまで - hikonori07’s blog
これから簡易ブログシステムを作成します。
その簡易ブログシステムのページ構成はこのようになっています。
・ブログ一覧
→indexページ
・投稿ページ
→postページ
・記事の更新
→updateページ
今回は上の3つのうちの1つ、postページを作成していきます。
・フォームの内容をDBのpostテーブルに反映させる
なおplay2.3とSquerylを使って作成していきます。
最初にviewの設定
app/views/post.scala.htmlファイル
@(form: play.api.data.Form[db.Post]) //@helper.formでフォームを宣言 @helper.form(action = routes.Application.addPost) { @helper.inputText(form("title")) @helper.inputText(form("content")) <input type="submit"/> }
helperを使ってHTMLフォームを出力するのは↓のページを参考に
Play2.0(Scala)のForm HelperでHTMLフォームを作る | mwSoft
conf/routesの設定
//フォーム画面 GET /post controllers.Application.post //フォーム内容を送信したときのアクション先として POST /addPost controllers.Application.addPost
フォーム内容のアクション先として指定しているaddPostを作成
app/controllers/Application.scalaファイル
def addPost = Action { implicit request => val result = postForm.bindFromRequest.fold( errors => { BadRequest }, post => { // inTransaction(AppDB.postTable.insert(new Post(post.title, post.content))) //上の記述をのせていましたが、下の記述でも大丈夫なようです inTransaction(AppDB.postTable.insert(post)) Redirect(routes.Application.index()) } ) result }
リクエストを受け取り参照する方法や、暗黙的な引数とする方法が↓で説明されています。
https://www.playframework.com/documentation/2.3.x/ScalaActions
bindFromRequestの使い方とimplicitしている理由
→「Controllerロジックの実装」に説明あり
http://www.colibrifw.org/chapter03/section05/databaseapp.html
また今回error時の処理も行う必要があるためbindFromRequest.foldを使用しています。
error時にはBadRequestを投げ、正常時にはinsert文を実行してリダイレクト処理を行っています。
またerrorsやpostは束縛され変数としてその後の処理で参照できるため、分かりやすい変数名をしっかりつけるようにしたほうがいいでしょう。
最初そのことを分からず、errorとsuccessにしていました。。。
フォーム入力画面の作成
indexページにはpostの一覧を表示する予定なので今回は、controllerにpostアクションを追加。
app/controllers/Application.scalaファイル
//フォームの定義 val postForm = Form( mapping( "title" -> text, "content" -> text )(Post.apply)(Post.unapply) ) //postFormを渡す def post = Action { Ok(views.html.post(postForm)) }
ひと通りできたところで、Loggerでフォームで渡された内容(request.body)を表示
request.bodyにはこんな内容が飛んできていました。
AnyContentAsFormUrlEncoded(Map(title -> ArrayBuffer(hello Rails), content -> ArrayBuffer(play with friends)))
Squerylで簡単なDBの操作をやってみた
今回はDBの操作(Select,Insert,Update,Delete)をSquerylを使って行ったのでメモ。
まず今回はDBの操作だけを目的としているため、フォームなどは使わずにあるページにアクセスしたらDBにある操作が実行されるという形式で行いました。
例えば、localhost:9000/indexというページを表示するとpostテーブルにinsert文が実行されるといった感じです。
また参考にしたページは↓です。
Select - Squeryl - A Scala ORM for SQL Databases
Insert, Update and Delete - Squeryl - A Scala ORM for SQL Databases
各SQL文を実行する前に↓をimportする
import models._ import org.squeryl.PrimitiveTypeMode._
ではまだデータがない状態なのでInsertから。
transaction {
schema_name.insert(new model_name(column1, column2))
}
これでDBに値が入っていたらOK
あとここで記述されているtransactionは一般的なトランザクションとほぼ同義です。
→transactionはブロック内でトランザクションを開始し、ブロックが終了するとコミットする。(ブロック内でもし例外が発生するとロールバックする)
引用:SquerylのSessionとTransaction - Develop with pleasure!
次はUpdateです。
//update transaction { update(schema_name)(p => where(p.id === 1) set(p.column1:= "Hello Squeryl !!") }
SQLがわかれば、大体なにをやっているかはわかりますよね。
今度はDeleteを見ていきます。
今回はidは決め打ちで。
//delete transaction { schema_name.deleteWhere(sch => sch.id === 1) }
最後にSelectですがちょっと引っ掛け!?
自分だけなんでしょうか。
//select transaction { from(schema)(s => where(sch.id === 1) select(sch)) }
これだけではselect文は実行されません。(定義したのみの状態)
参考にしていたページにはそれ以上のことは書いていない(探せないだけ?)。
select文を実行するためには、toListやheadOptionをつけてあげたらいいとのこと。
こんな感じ↓
//select transaction { val sql = from(schema)(s => where(sch.id === 1) select(sch)).headOption //ちゃんと実行できているかlogに出力 Logger.debug(sql.toString) }
これでできるようになりました。
ログにもちゃんと出力されるようになりました↓
[info] play - database [default] connected at jdbc:postgresql://~ [info] play - Application started (Dev) [debug] application - List(Post(hello world,今日は快晴です!!))
*ログの出力方法については別途記事作成します。
以上Squerylを使った簡単なDB操作にでした。
追記
transactionとinTransactionの違い
・transactionはブロック内でトランザクションを開始し、ブロックが終了するとコミットする。
トランザクションを実行する際は、例え既存のトランザクションコンテキスト内で呼ばれたとしても、いつも新規にトランザクションが生成される。
・inTransaction
inTransactionは実行中のトランザクションが無い場合に新規にトランザクションを生成する。既にトランザクションが存在する場合はそのトランザクションコンテキストが利用される。
この両者の使い分けって妨げられたくない処理がある時は、transactionを使えってことでいいのかな!?
Active Recordパターン
Active RecordっぽくDBの操作もできるようなので、その方法も書いてみます。
尚今回の使うpostのスキーマ定義は↓のようになっています。
package db { import org.squeryl.{Schema, KeyedEntity} case class Post( title: String, content: String) extends KeyedEntity[Long] { val id: Long = 0 } object AppDB extends Schema { val postTable = table[Post]("post") } }
//schemaオブジェクトのコンテンツをimport //dbパッケージのimportではないことに注意(汗) import AppDB._ transaction { new Post("Hello Scala", "scala scala").save }
updateも試しましたが苦労しました。
最初にDBからupdateしたいフィールドをとってくる必要があるって当たり前のことに気づかず。。。
updateはこんな感じで行います。
import AppDB._ transaction { //selectでテーブルから情報取ってきてる //今回は該当idがあることが前提なのでgetメソッド使用 val helloScala = from(AppDB.postTable)(p => where(p.id === 12) select(p)).headOption.get //copyメソッドでタイトルだけ違うインスタンス生成 val updatePost = helloScala.copy(title = "Hello !! Scala !!") //update処理 updatePost.update
ちなみに上ではgetメソッドを使用していますが、値が確実に返ってくるか分からない場合は下のようにmapを使います。
//idがあるかどうか分からない場合はmapを使って書いた方が良い from(AppDB.postTable)(p => where(p.id === 12) select(p)).headOption.map{ post => val newPost = post.copy(title = "Hello !! Scala !!") newPost.update }.getOrElse(/*Noneの場合の処理*/)
getメソッドを使った場合だと、値がNoneの場合errorになります。
↑の場合だとNoneのときはgetOrElseのあとの処理が返されます。
また下のリンクページにも書いているのですが、Entityに部分はこのように変更しないとupdateできません。
変更前だとidが常に0になるためです。insertの場合はそのままでOKなのですがupdateの場合はid=0では正常に動作しません。当たり前ですが。。。該当idをちゃんと渡せるようにするためには下のように変更してください。
//変更前 case class Post( title: String, content: String) extends KeyedEntity[Long] { val id: Long = 0 } //変更後 case class Post( id: Long, title: String, content: String) extends KeyedEntity[Long] { }
Play2.3でCRUDを使った一覧ページの作成してみる - hikonori07’s blog
もっと複雑なsql文を書きたいという人は最初にも説明しましたが、
Select - Squeryl - A Scala ORM for SQL Databases
Insert, Update and Delete - Squeryl - A Scala ORM for SQL Databases
これらのページを参考ください。当たり前ですがselectはさらに色んなバリエーションがありますね。
- 作者: Martin Odersky,Lex Spoon,Bill Venners,羽生田栄一,水島宏太,長尾高弘
- 出版社/メーカー: インプレスジャパン
- 発売日: 2011/09/27
- メディア: 単行本(ソフトカバー)
- 購入: 12人 クリック: 235回
- この商品を含むブログ (45件) を見る
- 作者: 中村修太
- 出版社/メーカー: 中村 修太
- 発売日: 2013/03/02
- メディア: Kindle版
- 購入: 1人 クリック: 27回
- この商品を含むブログ (4件) を見る
Scala逆引きレシピ (PROGRAMMER’S RECiPE)
- 作者: 竹添直樹,島本多可子
- 出版社/メーカー: 翔泳社
- 発売日: 2012/07/03
- メディア: 単行本(ソフトカバー)
- 購入: 6人 クリック: 54回
- この商品を含むブログ (13件) を見る
play frameworkでSqueryl Schema及びentity定義するまで
今回は前回の続き、Squeryl Schema及びentity定義をやっていこうと思います。
playとsqueryで簡単ブログ機能を作ってみます。
*DBには予めpostテーブルが作成されています。
こんな感じ。
id | integer | not null default nextval('s_post_id'::regclass)
title | character varying(30) |
content | text |
下のリンクページを見ながら作業をすすめていきます。
Getting Started with Play 2, Scala, and Squeryl
entityって?
entityと言われてどういうことか分からなかったので調べてみました。
エンティティとは 〔 実体 〕 【 entity 】 - 意味/解説/説明/定義 : IT用語辞典
→具体的にどういったデータがエンティティとして扱われるかは分野によって異なるため、注意が必要である。
うむ、これじゃ細かいとこ分かりません。。。
[SQL] 16. データベースの設計 1 | TECHSCORE(テックスコア)
→実世界に存在するものの中で、データベースとして表現すべき対象物をエンティティ (entity:実体) と呼びます。
playの標準設定システムを使ったsquerylのDB接続
大体理解できたところで先ほどのページへ、
conf/application.confの設定は前回終わったので、
app/Global.scalaファイルの作成を始めます。
import org.squeryl.adapters.{H2Adapter, PostgreSqlAdapter} import org.squeryl.internals.DatabaseAdapter import org.squeryl.{Session, SessionFactory} import play.api.db.DB import play.api.GlobalSettings import play.api.Application object Global extends GlobalSettings { override def onStart(app: Application) { SessionFactory.concreteFactory = app.configuration.getString("db.default.driver") match { case Some("org.h2.Driver") => Some(() => getSession(new H2Adapter, app)) case Some("org.postgresql.Driver") => Some(() => getSession(new PostgreSqlAdapter, app)) case _ => sys.error("Database driver must be either org.h2.Driver or org.postgresql.Driver") } } def getSession(adapter:DatabaseAdapter, app: Application) = Session.create(DB.getConnection()(app), adapter) }
上のファイルを追加したら、activator runした状態で
http://localhost:9000/にアクセスします。
コマンドに下のような表示がされればDB接続が上手くいったということになります。
[info] play - database [default] connected at jdbc:postgresql://ip_address/db_name
entityの作成
ここまできてようやくentityの作成です。
package db { import org.squeryl.{Schema, KeyedEntity} case class Post( title: String, content: String) extends KeyedEntity[Long] { val id: Long = 0 } object AppDB extends Schema { val postTable = table[Post]("post") } }
Twiwt:Blog / jugyo : Squeryl の使い方 - セットアップ, モデルの定義, テーブル作成
→KeyedEntity[Long] を extends しているのがポイントで、こうすると Long 型の id という名前のフィールドをプライマリキーとして扱うようになります。
上のように書いていたので、今回も同様idというLong型のフィールドを定義しています。
モデルは定義するだけではダメで、スキーマに登録する必要があります。
以下ではその処理を行っています。
object AppDB extends Schema { val postTable = table[Post]("post") }
ここまできたらDBにアクセスする準備は整いました。
次回は実際にDBを操作する方法いわゆるCRUDについて書いていきます。
- 作者: Martin Odersky,Lex Spoon,Bill Venners,羽生田栄一,水島宏太,長尾高弘
- 出版社/メーカー: インプレスジャパン
- 発売日: 2011/09/27
- メディア: 単行本(ソフトカバー)
- 購入: 12人 クリック: 235回
- この商品を含むブログ (45件) を見る
- 作者: 中村修太
- 出版社/メーカー: 中村 修太
- 発売日: 2013/03/02
- メディア: Kindle版
- 購入: 1人 クリック: 27回
- この商品を含むブログ (4件) を見る
playとSquerylの設定
playとSquerylを使う機会があったのでメモ。
ちなみにplayはplay2.3です。
play側のDBアクセス設定
ScalaDatabase
my_project/conf/application.confに↓の設定を行う
# Database configuration db.default.driver=org.postgresql.Driver db.default.url="jdbc:postgresql://ip_address/db_name" db.default.user=user_name db.default.password=pass_word
今回はORMにSquerylを使います。
Introduction - Squeryl - A Scala ORM for SQL Databases
ではこのページを参考に作業を進めていきます。
Getting Started - Squeryl - A Scala ORM for SQL Databases
sbtファイルの設定。
今回はDBはpostgreSQL、ORMにSquerylを使用するので、それぞれをSBTファイルに書き込みます。
built.sbtと書かれたファイルに↓のように記述
libraryDependencies ++= Seq( jdbc, "org.squeryl" %% "squeryl" % "0.9.5-7", "postgresql" % "postgresql" % "9.1-901.jdbc4" )
ちなみに上の各ライブラリのバージョンは、squerylの場合「squeryl sbt」みたいにググればでてきました。
Maven Repository: org.squeryl » squeryl_2.10 » 0.9.5-6
compileコマンドでerrorが出なかったらひとまずOK
[my-project] $ compile
*私はjdbcの記述を抜かしていたことでerrorが出ていました。
JDBCって今回初めて知り。。。(汗)
JDBC - Wikipedia
次の記事ではスキーマの定義をしていきます。
参考ページ
https://www.playframework.com/documentation/ja/2.2.x/NewApplication
Scalaでメソッドのオーバーロード
メソッドのオーバーロードとは?
オーバーロードとは同じメソッド名で異なる個数の引数や異なる型の引数をとるメソッドを複数定義することです。
上の説明だけでは分かりにくい部分もあるのでさっそくコードで見てみます。
scala> class Person(name:String, age:Int){ | def hello() = println("Hello " + name) | def hello(dialogue:String) = println("Hello " + name + "," + dialogue ) | def olderTha(n:Int):Boolean = { age > n } | } defined class Person scala> val tanaka = new Person("Tanaka", 28) tanaka: Person = Person@1a93a7ca scala> tanaka.hello Hello Tanaka scala> tanaka.hello("Nice to meet you!") Hello Tanaka,Nice to meet you!
Personクラスではhelloメソッドが2つ定義されていますが、呼び出し側の引数の与え方によって、それぞれ違うhelloメソッドが呼び出されていることが確認できます。
ちょっと気になったので調べてみました。
- 作者: Martin Odersky,Lex Spoon,Bill Venners,羽生田栄一,水島宏太,長尾高弘
- 出版社/メーカー: インプレスジャパン
- 発売日: 2011/09/27
- メディア: 単行本(ソフトカバー)
- 購入: 12人 クリック: 235回
- この商品を含むブログ (45件) を見る
- 作者: 中村修太
- 出版社/メーカー: 中村 修太
- 発売日: 2013/03/02
- メディア: Kindle版
- 購入: 1人 クリック: 27回
- この商品を含むブログ (4件) を見る
ScalaのOption型について
Scalaにはオプションの値のためのOption型がある。
今回はこのOption型についてブログに書く。
なぜnullではなくOptionなのか?
他の言語ではnullを使うことが多いけど、Scalaではnullは使わない。使うことはできるけど暗黙の了解というかOptionを使うことが推奨されている。
このおかげでnullチェックの煩わしさが減る。Option型の変数として示すことでnullになる可能性のあるということをコードの読み手に伝えることができる。
Optionとは?
Optionとは値があるかどうかわからない状態を表すための型です。
Optionのサブクラスには値があるときのSome(value)と、値がないことを表すNoneがあります。
Optionは値がnull以外の時あるときはSome(value)、値がない場合nullの場合はNoneを返します。
Optionの値を取り出す方法
Optionの値を取り出す例を見てみます。
scala> val something : Option[String] = Option("test") something: Option[String] = Some(test) //Optionのgetメソッド //getメソッドは値があるわかっているときに使う(逆に確かではない時はgetOrElseメソッドなどを使う) scala> val result = something.get result: String = test //OptionのisEmptyメソッド scala> val result = something.isEmpty result: Boolean = false //Noneのとき scala> val none : Option[String] = None none: Option[String] = None scala> none.get java.util.NoSuchElementException: None.get at scala.None$.get(Option.scala:322) at scala.None$.get(Option.scala:320) ... 32 elided //getOrElseメソッドでnullの場合を指定することも可能 scala> none.getOrElse("Null") res3: String = Null
Mapのgetメソッドで引数のキーに対応する値あるかどうか下のように確認できます。
scala> val fruitsColor = Map("apple" -> "red", "banana" -> "yellow", "mango" -> "orange") fruitsColor: scala.collection.immutable.Map[String,String] = Map(apple -> red, banana -> yellow, mango -> orange) //Mapのgetメソッド scala> fruitsColor get "apple" res1: Option[String] = Some(red) scala> fruitsColor get "melon" res2: Option[String] = None
OptionをパターンマッチでSomeまたはNoneにマッチさせることで値を取り出すことも可能です。
//パターンマッチでOptionの値を取得する scala> def something(x: Option[String]) = x match { | case Some(value) => value | case None => "empty" | } something: (x: Option[String])String scala> something(Option("test")) res1: String = test scala> something(None) res2: String = empty
Option確かに便利です。
他の言語でもこれ使えたらいいのに!
追記
Optionのforeachメソッドとmapメソッドも便利ということで調べてみました。
//Optionは基本値があるかないかの時に使う(同じ属性の値が複数の時はListを使用) scala> val myPhone = Option("iPhone5S") myPhone: Option[String] = Some(iPhone5S) //Optionのforeachメソッド scala> myPhone.foreach(println) iPhone5S //Noneの場合はなにもしません。 scala> None.foreach(println)
//mapメソッド scala> myPhone.map { x => x.size } res1: Option[Int] = Some(8) scala> myPhone.map { x => x.length } res2: Option[Int] = Some(8) scala> myPhone.map { x => x.toString } res3: Option[String] = Some(iPhone5S) scala> None.map { x => x.toString } res4: Option[String] = None
参考記事
ScalaのOptionステキさについてアツく語ってみる - ( ꒪⌓꒪) ゆるよろ日記
- 作者: Martin Odersky,Lex Spoon,Bill Venners,羽生田栄一,水島宏太,長尾高弘
- 出版社/メーカー: インプレスジャパン
- 発売日: 2011/09/27
- メディア: 単行本(ソフトカバー)
- 購入: 12人 クリック: 235回
- この商品を含むブログ (45件) を見る
- 作者: 中村修太
- 出版社/メーカー: 中村 修太
- 発売日: 2013/03/02
- メディア: Kindle版
- 購入: 1人 クリック: 27回
- この商品を含むブログ (4件) を見る
scalaのパターンマッチについて
今回はscalaのパターンマッチについてメモ
ケースクラスとパターンマッチの組み合わせはだいぶ強力なことができるということで、ちゃんと覚えておくためのブログに残しておきます。
パターンマッチで使うmatch式はこんな感じになります。
<セレクター式> match { <選択肢> }
・match式のマッチング方法
match式では上から順番にマッチするかどうかがチェックされ、どれか1つの条件にマッチした場合、それ以降の条件に対するマッチングは行われません。
パターンマッチのパターンはいくつかありますが、その中からいくつかをまとめてみました。
1,定数パターン
2,変数パターン
3,ワイルドカードパターン
4,コンストラクタパターン
5,型付きパターン
・定数パターン
定数パターンは自分自身としかマッチしません。
ようは決め打ちの値。定数としては任意のリテラルを使えます。(string,boolean,intなど)
scala> def const(x: Any) = x match { | case 1 => "number" | case true => "true" | case "constant" => "String" | case Nil => "empty" | } const: (x: Any)String scala> const(1) res0: String = number scala> const("constant") res1: String = String scala> const(100) res2: String = something
・変数パターン
scala> def expr(x: Any) = x match { | case 0 => "ZERO" | case something => "not ZERO:" + something | } expr: (x: Any)String //マッチした変数を参照することも可能 scala> expr("hello") res1: String = not ZERO:hello
・コンストラクタパターン
前回のケースクラスについての記事で、ケースクラスに対してもパターンマッチができると書きました。下がその例です。
scala> case class Iphone(color:String, price:Int) defined class Iphone scala> def caseClassMatch(p: Iphone) = p match { | case Iphone( _, 900)=>{ | println("price is $900") | } | case Iphone( "green", _)=>{ | println("Color is green") | } | } caseClassMatch: (p: Iphone)Unit scala> val myIphone = Iphone("red", 900) myIphone: Iphone = Iphone(red,900) scala> caseClassMatch(myIphone) price is $900
ケースクラスのコンストラクタに対してパターンマッチングされていることが確認できると思います。
*ケースクラスのコンストラクタの引数にさらにケースクラスがある場合も、match式でネストされたケースクラスにもパターンマッチが可能ということ。
型でマッチさせることも可能です。
5,型付きパターン
scala> def patternMatch(n:Any) = n match { | case n:Int => println("int") | case n:String => println("string") | case n:Boolean => println("boolean") | } patternMatch: (n: Any)Unit scala> patternMatch(1) int scala> patternMatch("Good") string
以上、他の言語よりも柔軟な使い方ができるscalaのパターンマッチでした。
追記
変数を束縛(bind)する方法
scala> case class Iphone(color: String, price: Int) defined class Iphone scala> def caseClassMatch(p: Iphone) = p match { | case Iphone(_, 900) => println("900 yen") | case Iphone("green", _) => println("green!") | case Iphone(color, _) => println("color is " + color) | } caseClassMatch: (p: Iphone)Unit scala> caseClassMatch(Iphone("red", 300)) color is red
変数を束縛するという表現がピンとこなかったけど、
ワイルドカードパターンと比べてみたら理解できたので書いてみる。
・ワイルドカードのパターン
任意の条件にマッチした場合、予め決めていた処理を行う。
・変数を束縛する変数パターン
ワイルドカードと同じように任意の条件にマッチする。ここまで一緒。
違うのはこれからで、パターン変数xに値が束縛されて参照が可能になります。
上の式だとcolorに値が束縛され、参照できるようになっています。
パターンがマッチしなかった場合どうなるか?
上の例でどのパターンにもマッチしなかった場合どうなるか?という指摘があったため追記です。
結論から言うとerrorになります。下記参照
scala> case class Iphone(color:String, price:Int) defined class Iphone scala> def caseClassMatch(p: Iphone) = p match { | case Iphone( _, 900)=>{ | println("price is $900") | } | case Iphone( "green", _)=>{ | println("Color is green") | } | } caseClassMatch: (p: Iphone)Unit scala> val myPhone = Iphone("red", 100) myPhone: Iphone = Iphone(red,100) scala> caseClassMatch(myPhone) scala.MatchError: Iphone(red,100) (of class Iphone) at .caseClassMatch(<console>:9) ... 32 elided