Play ドキュメントを Skinny で書くと - Manipulating Results, Session and Flash scopes, Content negotiation
これは Play framework 2.x Scala Advent Calendar 2013 の 13 日目です。
http://www.adventar.org/calendars/114
なお、この記事で触れる Play は 2.2.1、Skinny は 0.9.20 です。両者とも最新バージョンでは API や仕様が変更になっている場合があります。
Manipulating Results
http://www.playframework.com/documentation/2.2.x/ScalaResults
Changing the default Content-Type
val textResult = Ok("Hello World!") val xmlResult = Ok(<message>Hello World!</message>)
これは Skinny の場合もコードは同じですが
def hello = Ok("Hello World!") def hello = Ok(<message>Hello World!</message>)
Content-Type: text/html;charset=UTF-8 として応答します。text/plain や application/xml として応答したい場合は contentType を指定してください。
Play では as で続けて指定することで Content-Type を変えることができますが
val xmlResult = Ok(<message>Hello World!</message>) val htmlResult2 = Ok(<h1>Hello World!</h1>).as(HTML)
Skinny(Scalatra)では、以下のように setter で指定します。
beforeAtction() { contentType = "text/plain" } def hello = Ok("Hello World!") def hello = { contentType = "application/xml" Ok(<message>Hello World!</message>) }
Manipulating HTTP headers
val result = Ok("Hello World!").withHeaders( CACHE_CONTROL -> "max-age=3600", ETAG -> "xx")
上記の Play のコードと同じことは
def hello = { response.setHeader("Cache-Control", "max-age=3600") response.setHeader("ETag", "xx") "Hello World!" }
のようにします。
Setting and discarding cookies
val result = Ok("Hello world").withCookies( Cookie("theme", "blue")) val result2 = result.discardingCookies(DiscardingCookie("theme"))
Skinny は Scalatra の SweetCookies でスッキリ書けます。
http://www.scalatra.org/2.2/api/index.html#org.scalatra.SweetCookies
cookies += "theme" -> "blue" cookies -= "theme"
Changing the charset for text based HTTP responses.
object Application extends Controller { implicit val myCustomCharset = Codec.javaSupported("iso-8859-1") def index = Action { Ok(<h1>Hello World!</h1>).as(HTML) } } def HTML(implicit codec: Codec) = { "text/html; charset=" + codec.charset }
似たようなアプローチもできますが、あまり必要にも見えないので Skinny ではシンプルにやるのがよいのではないでしょうか。beforeAction でデフォルトを指定しておいて、それと異なる時だけ指定すればよいと思います。
object Application extends Controller { override def charset = "iso-8859-1" def setContentTypeAsHTML() = { contentType = s"text/html; charset=${charset}" } def index = { setContentTypeAsHTML() Ok(<h1>Hello World!</h1>) } }
Session and Flash scopes
http://www.playframework.com/documentation/2.2.x/ScalaSessionFlash
How it is different in Play
Play はサーバ側にセッションを保持せず Cookie に値を保存しています。
Scalatra は Servlet のセッションを扱うので JSESSIONID にひもづいた HttpServletSession です。Servlet のセッションに状態を持つ場合は複数台の Servlet コンテナを運用する際は Sticky Session にすることになります。この辺は何か別のアプローチのサポートも考えたいのですが、まだ未着手です*1。
Reading a Session value
def index = Action { implicit request => session.get("connected").map { user => Ok("Hello " + user) }.getOrElse { Unauthorized("Oops, you are not connected") } }
Skinny では Scalatra の RichSession を使います。
http://www.scalatra.org/2.2/api/index.html#org.scalatra.servlet.RichSession
def index = { session.get("connected").map { user => Ok(s"Hello ${user}") }.getOrElse { Unauthorized("Oops, you are not connected") } }
これはコードの見た目はほとんど一緒ですね。
Storing data in the Session
Ok("Welcome!").withSession("connected" -> "user@gmail.com")
Skinny は Response に with でつなげる必要はありません。これは connected だけの session になる例なので、このようになります。
session.clear() session += "connected" -> "user@gmail.com" Ok("Welcome!")
次に session に値を追加する例ですが
Ok("Hello World!").withSession(session + ("saidHello" -> "yes"))
Skinny では clear がなくなるだけです。
session += "saidHello" -> "yes" Ok("Hello World!")
最後に attribute を削除するのは
Ok("Theme reset!").withSession(session - "theme")
Skinny では、このようになります。
session -= "theme" Ok("Theme reset!")
簡単ですね。
Discarding the whole session
Ok("Bye").withNewSession
セッションの中身を掃除するには clear() で attributes を破棄します。
session.clear
Ok("Bye")
Flash scope
def index = Action { implicit request => Ok { flash.get("success").getOrElse("Welcome!") } } def save = Action { Redirect("/home").flashing( "success" -> "The item has been created") }
Scalatra の flash は更新可能な Map なのでそのように操作するだけです。Rails でもおなじみの flash.now もあります。
def index = { flash("success") = "Welcome!" Ok() } def save = { flash += "success" -> "The item has been created" }
Body parsers
アーキテクチャが異なるので Skinny(Scalatra)に Body Parser はありません。request から body を取得するには
val body: String = request.body val is: InputStream = request.inputStream
あたりを操作する感じになります。
Action composition
アーキテクチャが異なるので Skinny(Scalatra)に 合成可能な Action という概念はありません。
Content negotiation
val list = Action { implicit request => val items = Item.findAll render { case Accepts.Html() => Ok(views.html.list(items)) case Accepts.Json() => Ok(Json.toJson(items)) }
Format を implicit parameter で render に渡すとよしなに分岐してレンダリングしてくれます。
def list()(implicit format: Format = Format.HTML) = withFormat(format) { // respondTo で対応していなかったら 406 応答 set("items", Item.findAll()) render(s"/items/list") // HTML, JSON, XML }
以上です。明日は @daneko0123 さんです。
*1:Scalatra 側でいいソリューションが出てきたら、自作しなくてすむので嬉しいですが
Play ドキュメントを Skinny で書くと - HTTP routing
これは Play framework 2.x Scala Advent Calendar 2013 の 11 日目です。
http://www.adventar.org/calendars/114
毎日こんな感じになりますけど、本当にそれでいいんでしょうか?
なお、この記事で触れる Play は 2.2.1、Skinny は 0.9.20 です。両者とも最新バージョンでは API や仕様が変更になっている場合があります。
HTTP routing
http://www.playframework.com/documentation/2.2.x/ScalaRouting
The built-in HTTP router
Play は conf/routes に以下のように記述しますが
GET /clients/:id controllers.Clients.show(id: Long)
Skinny では、以下のようにします。前のエントリで Controller を object にせずに class にしていましたが Skinny では routing とひもづけるまでは object にはしません(あくまで推奨するスタイル)。
// src/main/scala/controller/Controllers.scala object Controllers { object clients extends new Clients with Routes { val showUrl = get("/clients/:id")(show).as('show) } } // src/main/scala/ScalatraBootstrap.scala class ScalatraBootstrap exntends SkinnyLifeCycle { override def initSkinnyApp(ctx: ServletContext) { Controllers.clients.mount(ctx) } }
Controllers.scala をつくるのは任意で、別の名前のものや違ったやり方で管理しても構いません。テスタビリティを考慮しながら自由にルールを決めてください。Path パラメータの id は params.getAs[String]("id") のようにして show メソッドの中で取得します。
「showUrl って何?」と思われた方もいるかと思いますが、これは Scalatra の Reverse Routes です。あとで説明します。
http://scalatra.org/2.2/guides/http/reverse-routes.html
The routes file syntax
Skinny は Scala コードで記述するので省略。
The HTTP method
Skinny は Scalatra をそのまま使用します。
https://github.com/scalatra/scalatra/blob/2.2.x_2.10/core/src/main/scala/org/scalatra/CoreDsl.scala
The URI pattern
Static path
GET /clients/all controllers.Clients.list()
Scala のコードで記述します。
object Controllers { object clients extends new Clients with Routes { val listUrl = get("/clients/all")(list).as('list) } }
Dynamic parts
GET /clients/:id controllers.Clients.show(id: Long)
Scala のコードで記述します。Path パラメータはメソッド引数ではなく params 経由で取得します。
object Controllers { object clients extends new Clients with Routes { val showUrl = get("/clients/:id")(show).as('show) } }
Dynamic parts spanning several /
Play のこのコードは "name" というパラメータに /files/ 配下のパスをバインドするのですが(例: "images/logo.png")
GET /files/*name controllers.Application.download(name)
Skinny の場合・・というか Scalatra の仕様ですが、以下のようにすると "splat" という名前(固定)で取得できます。複数ある場合は multiParams で受け取ると Seq で全て取得できます。
def download = { val name = params.getAs[String]("splat") ... } get("/files/*")(download)
Dynamic parts with custom regular expressions
Play の以下のサンプルは「[0-9]+」という正規表現にマッチしたらそのキャプチャを id として引き渡しますが
GET /items/$id<[0-9]+> controllers.Items.show(id: Long)
Skinny(Scalatra)では "captures"(固定)というパラメータで取得できます。複数ある場合は multiParams で受け取ると Seq で全て取得できます。
def show = { val id = params.getAs[Long]("captures") Item.findById(id).map { ... } } get("/items/([0-9]+)".r)(show)
Call to the Action generator method
Play の Path パラメータとクエリストリングの場合の conf/routes 例で
# Extract the page parameter from the path. GET /:page controllers.Application.show(page) # Extract the page parameter from the query string. GET / controllers.Application.show(page)
処理メソッドはともにこうなりますが
def show(page: String) = Action { loadContentFromDatabase(page).map { htmlContent => Ok(htmlContent).as("text/html") }.getOrElse(NotFound) }
Skinny の場合はこのようになります。
object Controllers { object application extends new Application with Routes { val showPathParamUrl = get("/:page")(show).as('show1) val showQueryParamUrl = get("/")(show).as('show2) } } def show = params.getAs[Int]("page").map { page => loadContentFromDatabase(page).getOrElse haltWithBody(404) }.getOrElse haltWithBody(404)
Parameter types
以下のような型にバインドする場合に Play は型にマッチしなかった場合、Bad Request で応答しますが
GET /clients/:id controllers.Clients.show(id: Long)
Skinny ではそもそも params から取得するので、利用者の実装によって挙動は制御されます。
def show = params.getAs[Long]("id").map { id => // 正常系 }.getOrElse haltWithBody(404) // 400 でも 404 でも自由
Parameters with fixed values
# Extract the page parameter from the path, or fix the value for / GET / controllers.Application.show(page = "home") GET /:page controllers.Application.show(page)
Skinny では Scalatra の params の API を使います。params.getAs は Option 型を返すので getOrElse などで代用可能です。
Parameters with default values
# Pagination links, like /clients?page=3 GET /clients controllers.Clients.list(page: Int ?= 1)
こちらも Skinny では Scalatra の params の API を使って制御します。
Optional parameters
# The version parameter is optional. E.g. /api/list-all?version=3.0 GET /api/list-all controllers.Api.list(version: Option[String])
こちらも(ry
Routing priority
Play では先に定義されたルーティングルールが優先されますが Skinny(Scalatra)では逆に後から定義されたものが優先されます。
Reverse routing
Play で以下のような hello という処理メソッドがあり conf/routes で何らかのルーティング情報とひもづいている場合
object Application extends Controller { def hello(name: String) = Action { Ok("Hello " + name + "!") } }
このように reverse route を解決できます。
// Redirect to /hello/Bob def helloBob = Action { Redirect(routes.Application.hello("Bob")) }
Skinny(Scalatra)では、このようになります。Controller だけでなく Scalate の view template でも同様に呼び出すことが出来ます。
class Application extends Controller { def hello(name: String) = Action { Ok("Hello " + name + "!") } } object Controllers { object app extends Application with Routes { val helloUrl = get("/hello/:name")(hello).as('hello) } } def helloBob = redirect(url(Controllers.app.helloUrl, "name" -> "Bob"))
Play ドキュメントを Skinny で書くと - Actions, Controllers and Results
これは Play framework 2.x Scala Advent Calendar 2013 の 10 日目です。
http://www.adventar.org/calendars/114
ご存知の方もいらっしゃるかと思いますが、私は Skinny Framework というのをつくっています。これは Servlet ベースのフルスタックな Web アプリ開発フレームワークで Web アプリ部分については「Scalatra を便利にする」というスタンスで機能拡張しています。
今回は Play Framework のドキュメントの内容を Skinny の場合だとどう書くかを説明しながら両者の比較をしてみたいと思います。なお Skinny Framework のバージョンは 0.9.20 です。Skinny はまだ 1.0 がリリースされていないフレームワークなので(1.0 は 2014/3 までにリリース予定)、ここでサポートしていない機能が今後追加されたり、また場合によっては API が変更になる場合があります。
http://www.playframework.com/documentation/2.2.x/ScalaHome
なお、明日も明後日もこのアドベントカレンダーはずっと空いているので、もしも誰も入ってこないとなると、毎日こんな感じになりますからね。覚悟してください。
Actions, Controllers and Results
http://www.playframework.com/documentation/2.2.x/ScalaActions
What is an Action?
val echo = Action { request => Ok("Got request [" + request + "]") }
これを Skinny で書くとこのようになります。Action がないだけですね。
def echo = Ok("Got request [" + request + "]")
これは Scalatra の ActionResult を使っています。ドキュメントはこちら。
このケースだと ActionResult を省略して
def echo = "Got request [" + request + "]"
とだけ書いても OK です。
Building an Action
Platy の Action とは違って Scalatra の ActionResult は
case class ActionResult( status: ResponseStatus, body: Any, headers: Map[String, String])
という構造なので特に難しいことはないと思います。これを生成する factory として Ok とか NotFound とかがあるだけです。
Controllers are action generators
Play では Controller のコードはこんな感じになりますが
package controllers import play.api.mvc._ object Application extends Controller { def index = Action { Ok("It works!") } }
同じ内容が Skinny ではこうなります。単に文字列を返すと 200 OK で指定された文字列を body として応答します。Skinny では object ではなく class になっていますが、これはルーティングのところで説明することになります。
package controller import skinny._ class Application extends SkinnyController { def index = "It works!" }
Play ではこのようにパラメータをメソッド引数として取得できますが
def hello(name: String) = Action { Ok("Hello " + name) }
0.9.20 時点で Skinny では同じことはできません。params から取得します。
def hello = "Hello " + params.getAs[String]("name").getOrElse("Anonymous")
Simple results
def index = Action { SimpleResult( header = ResponseHeader(200, Map(CONTENT_TYPE -> "text/plain")), body = Enumerator("Hello world!".getBytes()) ) }
これは Skinny では
def index = { status = 200 contentType = "text/plain" "Hello world!" }
となります。
val ok = Ok("Hello world!") val notFound = NotFound val pageNotFound = NotFound(<h1>Page not found</h1>) val badRequest = BadRequest(views.html.form(formWithErrors)) val oops = InternalServerError("Oops") val anyStatus = Status(488)("Strange response type")
はそれぞれ、ほぼ同じように書くなら以下のようになります。
// val ok = Ok("Hello world!") val ok = Ok("Hello world!") // val notFound = NotFound val notFound = NotFound() // val pageNotFound = NotFound(<h1>Page not found</h1>) val pageNotFound = NotFound(<h1>Page not found</h1>) // val badRequest = BadRequest(views.html.form(formWithErrors)) set("formWithErrors" -> formWithErrors) status = 400 render("/form") // val oops = InternalServerError("Oops") val oops = InternalServerError("Oops") // val anyStatus = Status(488)("Strange response type") status = 488 "Strange response type"
Redirects are simple results too
リダイレクトを意味する API のデフォルトが Play では 303 ですが Scalatra では 302 という違いがあります*1。
// 303 redirect def index = Action { Redirect("/user/home") } // 301 redirect def index = Action { Redirect("/user/home", MOVED_PERMANENTLY) }
これを Skinny でやると以下のようになります。ScalatraBase にある redirect メソッドは 302 でリダイレクトします。Scalatra は redirect と ActionResult を提供していて Skinny が redirect301 のようなメソッド 3 つを提供しています。
// 301 redirect def index = redirect301("/user/home") def index = MovedPermanently("/user/home") // 302 redirect def index = redirect("/user/home") def index = redirect302("/user/home") def index = Found("/user/home") // 303 redirect def index = redirect303("/user/home") def index = SeeOther("/user/home")
“TODO” dummy page
def index(name:String) = TODO
これは存在しないですが
def index = ???
とでもしておけばいいのではないでしょうか。
明日は?
以上、「Actions, Controllers and Results」のページでした。Scalatra をご存知の方はお分かりかと思いますが、半分以上は Scalatra の機能です。Skinny は Scalatra をより便利にするというスタンスなのでこのような形になります。
明日も担当者が現れなかったら・・・続きをやります。
Advent Calendar で公開しなかった場合も続きは普通の記事として公開しますので、割り込みをお待ちしております。
http://www.adventar.org/calendars/114
*1:指定がないときは 302 が妥当な気がしますが
3 分でできる Play2 で Skinny ORM を使う手順 #play_ja
これは Play framework 2.x Scala Advent Calendar 2013 の 8 日目です。
http://www.adventar.org/calendars/114
ご存知の方もいるかと思いますが、私は Skinny Framework というフレームワークをつくっています。これのコンポーネントは基本的に Skinny Framework 以外でも使えるようにつくられていて、その一つである Skinny ORM がある程度使える ORM として育ってきました。まだドキュメントはそれほど充実していませんが、こちらをご覧ください。
http://skinny-framework.org/documentation/orm.html
この Skinny ORM は ScalikeJDBC という DB ライブラリをより ORM 的に使えるようにするために、ScalikeJDBC を土台につくられています。ScalikeJDBC と Play2 の連携は以前から実装されていて、実績もあります。
Skinny ORM を Play ユーザの皆さんにもぜひ使っていただきたいので、今回は導入までの手順を紹介します。
Play アプリをつくる
この時点では Play 2.2.1 が最新です。私のように最近 play コマンド使ってないなーという方は brew upgrade play しておきましょう。
play new play-with-skinny-orm cd play-with-skinny-orm
build.sbt を書き換える
build.sbt をこのように書き換えてください。
name := "play-with-skinny-orm" version := "1.0-SNAPSHOT" libraryDependencies ++= Seq( "org.skinny-framework" %% "skinny-orm" % "0.9.29", "org.scalikejdbc" %% "scalikejdbc-play-plugin" % "1.7.1", "com.h2database" % "h2" % "1.3.174" ) play.Project.playScalaSettings
conf/play.plugins
Play に ScalikeJDBC ベースのコネクションマネージメントを伝えるために、ScalikeJDBC の Play プラグインを追加します。
10000:scalikejdbc.PlayPlugin
conf/application.conf
Play の DB 設定を更新します。今回の説明の都合上、H2 をファイルベースの DB に変えてください。
db.default.driver=org.h2.Driver db.default.url="jdbc:h2:file:play" db.default.user=sa db.default.password=""
conf/db/migration/V1__Create_companies.sql
DB マイグレーション用ファイルをつくってください。V1__ のアンダースコアは二つなので気をつけてください。Flyway のファイルです。今回は手動でマイグレーションを実行します。
create table company ( id bigserial not null primary key, name varchar(64) not null, url varchar(128), created_at timestamp not null, updated_at timestamp, deleted_at timestamp );
app/models/Company.scala
Skinny ORM では SkinnyCRUDMapper という trait を継承すると基本的な CRUD 操作はすぐに使えるようになります。
package models import scalikejdbc._, SQLInterpolation._ import skinny.orm._, feature._ import org.joda.time.DateTime case class Company( id: Long, name: String, url: Option[String] = None, createdAt: DateTime, updatedAt: Option[DateTime] = None, deletedAt: Option[DateTime] = None) object Company extends SkinnyCRUDMapper[Company] with TimestampsFeature[Company] with SoftDeleteWithTimestampFeature[Company] { override val defaultAlias = createAlias("c") override def extract(rs: WrappedResultSet, c: ResultName[Company]): Company = new Company( id = rs.long(c.id), name = rs.string(c.name), url = rs.stringOpt(c.url), createdAt = rs.dateTime(c.createdAt), updatedAt = rs.dateTimeOpt(c.updatedAt) ) }
app/controllers/Application.scala
説明を簡略化するためにただ toString しています。
package controllers import play.api._ import play.api.mvc._ import models.Company object Application extends Controller { def index = Action { Ok(Company.findAll().toString) } }
手動で DB マイグレーション
今回はさらっと試すだけなので play console でマイグレートしてしまいましょう。
skinny.DBSettings.initialize() skinny.dbmigration.DBMigration.migrate()
例外がでなければ成功です。こんな感じで model が使えるようになりました。console から試してみてください。
skinny.DBSettings.initialize() import models._ Company.count() Company.createWithAttributes('name -> "Typesafe") Company.findAll()
console で実行した結果を貼付けておきますね。
$ sbt console [info] Starting scala interpreter... [info] Welcome to Scala version 2.10.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_25). Type in expressions to have them evaluated. Type :help for more information. scala> skinny.DBSettings.initialize() scala> import models._ import models._ scala> Company.count() res1: Long = 0 scala> Company.createWithAttributes('name -> "Typesafe") res2: Long = 1 scala> Company.findAll() res3: List[models.Company] = List(Company(1,Typesafe,None,2013-12-08T20:03:36.771+09:00,None,None))
play run
最後に play run で Play アプリにちゃんと組み込まれたか確認します。
play run
ブラウザから http://localhost:9000/ にアクセスして正常に表示されれば OK です。
最終的にはこのようなファイル構成となりました。
. ├── README ├── app │ ├── controllers │ │ └── Application.scala │ ├── models │ │ └── Company.scala │ └── views │ ├── index.scala.html │ └── main.scala.html ├── build.sbt ├── conf │ ├── application.conf │ ├── db │ │ └── migration │ │ └── V1__Create_companies.sql │ ├── play.plugins │ └── routes ├── logs │ └── application.log ├── play.h2.db ├── project │ ├── build.properties │ └── plugins.sbt ├── public │ ├── images │ │ └── favicon.png │ ├── javascripts │ │ └── jquery-1.9.0.min.js │ └── stylesheets │ └── main.css └── test ├── ApplicationSpec.scala └── IntegrationSpec.scala 14 directories, 19 files
明日は unokazuhiko さんです。その次の日からまた空いてますけどね・・・
Typesafe Activator について #scalajp #play_ja
Advent Calendar!
この記事は Play framework 2.x Scala Advent Calendar 2013 の 4 日目です。
http://www.adventar.org/calendars/114
Activator については前に書いたことがある
7 月に Typesafe Activator のテンプレートをつくってみたというブログ記事を書きました。使い方はこの記事の内容で十分かと思います。
http://seratch.hatenablog.jp/entry/2013/07/02/005454
それから半年くらい経ち、色々と変化がありましたね。
Typesafe Activator が OSS になった
8 月に Typesafe Activator の実装が公開されました。
https://www.typesafe.com/blog/typesafe-activator-is-now-open-source
https://github.com/typesafehub/activator
これにより issue を登録してフィードバックしたり、実装を修正して pull request を送ったりといったことが可能になりました。
Typesafe Activator 1.0 リリース
9 月に vesrion 1.0 がリリースされました。この時点で 29 個のテンプレートが登録されていたようです。
http://typesafe.com/blog/announcing-activator-10-create-reactive-apps-in-minutes
ちなみに今日時点での最新バージョンは 1.0.8、登録テンプレートは 41 個ありました。
Typesafe Activator をつくりたい人へ
7 月のブログ記事には「どうやって Typesafe Activator のテンプレートつくるか」が書かかれていませんでした。というのも、こちらのページにある内容だけなので
http://typesafe.com/activator/template/contribute
あえて日本語で説明しなくても、という感じだったのですが、簡単に触れておきます。特に難しいことはないのですが、いくつか注意点を。
activator.properties の name を適当につけない
activator.properties というファイルにメタデータを指定します(なぜ Typesafe Config じゃないの・・とちょっと思った)。前にも書きましたが、この name の文字列がそのまま unique key にされてしまうので、適当につけるのはやめましょう。私はあとから hello-scalikejdbc にしたくなったけど、面倒で scalikejdbc-activator-template のままになっています。。
ライセンス表記が必要
何らかの OSS ライセンスを明記する必要があります。プロジェクトのルートディレクトリに LICENSE または LICENSE.md というファイル名で配置してください。
https://github.com/scalikejdbc/hello-scalikejdbc/pull/1
現時点でリポジトリの移動に対応していない
Typesafe Activator のテンプレートは上記の contribute ページから GitHub リポジトリの URL を指定して追加/更新するのですが、12/4 時点でリポジトリの owner が変わった場合に対応できていません。例えば、私が作っている
https://github.com/scalikejdbc/hello-scalikejdbc
は、以前は https://github.com/seratch/hello-scalikejdbc だったのですが owner が seratch として登録されているので scalikejdbc が owner の URL で更新しようとすると permission がねえぞゴラッみたいなエラーになります。回避策としては古い URL でリクエストすると受け付けてくれるようです。
恒久的には手動対応しかないんですかねぇということで、他に問い合わせ先がなかったので issue にしてみました。(追記)対応してくれました。
https://github.com/typesafehub/activator/issues/180
まとめ
感覚として Typesafe Activator のテンプレートを作ったことによってユーザが増えたとかそういう印象はあまりないのですが zip をダウンロードして初回起動までは特に知識がなくてもできるので Scala 初心者の方に体験してもらうにはよいツールではないでしょうか。ぜひ試してみてください。
明日が空いていますけど・・
誰かいませんか?
http://www.adventar.org/calendars/114
FYI:
どうしても困ったら Skinny Framework の宣伝を書きにいきますので、お気軽にご依頼ください(ぇ
— Kazuhiro Sera (@seratch) December 1, 2013
いなかったら本当にやりますよ・・
怖い Scala で LT しました #fud_scala
10 月の怖くない Scala 勉強会に対応する形で 11/28 に「怖い Scala」というイベントが開催されました。主催の @yamashiro さん、会場提供いただいたグリー株式会社さん、どうもありがとうございました。
http://connpass.com/event/4112/
これはまさかりを持った @kmizu さんです。このように非常に恐ろしい雰囲気の中、開催された勉強会でした。
LT をしました
ここで「Skinny Framework 進捗どうですか?」というタイトルの LT をしてきました。
Skinny Framework についてはこちらをご覧ください。
https://github.com/skinny-framework/skinny-framework
https://twitter.com/skinnyframework
何か遅かったらしいね?
@yuroyoro さんの ISUCON3 の予選アプリチューニング前状態を Skinny に移植した Scala アプリのスコアがパッとしなくて、LT のときに「先ほど出てきたあまり速くないフレームワークです」と自虐ネタにしましたが
Scala at ISUCON3 2013/11/28 怖いScala @yuroyoro https://dl.dropboxusercontent.com/u/261418/scala_at_isucon3/index.html#/
@yuroyoro さんの資料にもある通り、これに尽きるのかなと思います。
一般的なWebアプリケーション(IO bounds on DB/not CPU bounds)では Scala(Playframework)に性能上のアドバンテージはない。
Play2 + postgresql-async/mysql-async だったり Finatra + finagle-mysql だったりだと、また違うかもしれません。*1
Skinny は Scalatra(Servlet) + JDBC です。「一般的にはどうなの?」というと、以下の Scalatra とほぼ同等と思っていただければよいと思います。そのうち、このベンチマークにも載せてもらうよう pull request したいですね。
http://www.techempower.com/benchmarks/
Skinny Framework に関わりませんか?
LT の中でも触れましたが Skinny Framework の開発は私一人だけでやり続けない方が良いものになるだろうと思っています。仕事で使ってくださる方々もその方が安心できますよね。
とはいえ、開発を一緒にやってくれる方を探すのはそう簡単ではないので、開発自体は相変わらずほぼ一人という状態が続くかもしれませんが、提案やバグレポートなどをしてくれる方が増えていくと非常に嬉しいです。日本語でももちろん OK なので、お気軽にご連絡ください。
Skinny Framework 導入
を gist に書きました。よかったらご覧ください。はてなブログには gist を埋め込める機能がありますが、コード例のところがきれいに表示できなかったのでリンクを貼るだけにしておきます。
https://gist.github.com/seratch/7382298#file-getting_started_ja-md