seratch's weblog in Japanese

About Scala, Java and Ruby programming in Japaense. If you need English information, go to http://blog.seratch.net/

私の 2013 年

こういうのをブログに書いたことはないのですが、今年は結構活動した年だったので、自分の中で整理するためにまとめてみました。誰得ですが公開しますね ^ ^; なお、本業に関連するものについては書ける内容からピックアップして取り上げています*1

1 月

ScalikeJDBC Cookbook を発売

正月休みに Kindle Direct Publishing で本を出してみたいなぁと思い立ち、手探りで色々やってなんとか出版までやってみました。今でも時々売れています。買っていただいた皆様、ありがとうございます。

http://seratch.hatenablog.jp/entry/2013/01/03/192020

本の内容は GitHub にあるので間違いなど見つけたらお気軽に pull request ください。

https://github.com/scalikejdbc/scalikejdbc-cookbook

英語版も出そうと思っていたけど、scalikejdbc.org のドキュメントを充実させる方が先かなぁという感じになっています*2

Scala Conference in Japan 2013 の準備

2013 年に入ってから、いよいよ逆算してあれをやっておかないと間に合わない、というのが増えてきてバタバタしてきた頃です。この頃から私は割とかかりきりの状態になりました。平日はあまり進捗しないのが常だったので、定期ミーティングの頻度を増やして放置されているタスクがないか確認するようになりました。それ以外の時間も co-meeting 上の議論や諸々の事務作業をしていたことが多かった気がします。

JPA、QueryDsl との格闘

Spring Data JPA と QueryDsl が魅力的だったので*3、新規案件での実戦投入に向けて機能を作り込んでみました。ただ、既存 DB との連携が多くて障壁があったことと、やはりハマりポイントが多い JPA は私のやりたいことにフィットしないという判断で、最終的にはその実装をすべて捨てて別の実装に切り替えました。

QueryDsl は APT を使った好例という感じで Java であれば今でもなかなかよい選択肢なのではないかと思います。また QueryDsl を使った経験は後の ScalikeJDBC の DSL 実装のモチベーションに強い影響を与えました。なので、最初は名前だけはかぶらないようにしようということで Query Interface と書いていましたが、ユーザの方々も Query DSL と呼ぶので途中から開き直って QueryDSL と表記するようになりました。

2 月

Scala Conference in Japan 2013 の準備

相変わらず co-meeting に張り付いていた記憶があります。本業も忙しくなってしまって、結構大変だった記憶があります。

MOONGIFT さんが Gistub を取り上げてくれた

急にスターが増えたなぁと思ったら MOONGIFT さんが取り上げてくださっていました。これを機に Twitter 上などで見た限り、いくつかの会社さんで実際に導入いただいていました。今も使っていただいているとうれしいですが。

社内で立てられるGistサーバ「Gistub」

http://www.moongift.jp/2013/02/20130205/

これについてはブログにも書きました

http://seratch.hatenablog.jp/entry/2013/02/07/001757

ltsv4s を書いた

突如、日本の IT エンジニアの間で空前の LTSV ブームが発生し、実際私の周辺でも LTSV 化が進んだりしましたが

http://ltsv.org/

Scala 実装がまだなかったので「Scala ならそれ parser combinator でできるよ」ということで書きました。

http://seratch.hatenablog.jp/entry/2013/02/09/103917

虎ノ門 Scala 会

虎ノ門近辺で勤務する Scala 好きなエンジニアで飲み会をしました。日本酒が美味しかったですね。某社の方々がこぞって「sbt つらい」と言っていたのが印象に残っています。

3 月

Scala Conference in Japan 2013 本番

3/2 に東工大の大岡山キャンパスにて Scala Conference in Japan 2013 が開催されました。至らない点もあったかと思いますが、運営の一員として参加いただいた皆様に改めてお礼申し上げます。

Scala Conference in Japan 2013

http://scalaconf.jp/

プログラミング言語「Scala」の日本初カンファレンスが開催、盛況で立ち見のセッションも

http://itpro.nikkeibp.co.jp/article/NEWS/20130329/467341/

これほどの規模のカンファレンスの準備となると(特に直前は)あまりにも多くの時間をとられてしまうことと、できれば発表者としても参加したいという気持ちから、次回以降は私は中心メンバーからは外してくださいと言っていますが、今後も無理のない範囲では協力させていただくつもりです。どうやら次回の構想は水面下で進んでいるようですので、期待して待ちましょう。

とはいえ、実際のところ、参加者の方々が思っている以上に運営側は手薄です(どのカンファレンスもそうかとは思うのですが)。運営チームの人数はどうあれ、カンファレンスの準備や運営に心身のリソースをガッツリさける人というのはやはり一部に限られますし。

なので 2013 で運営をやっていなかった方で「手伝いたい」「自分で色々提案して実現したい」という気持ちをお持ちの方は、ぜひ @kmizu さんや @gakuzzzz さんにコンタクトしてみてください。きっとよい経験になりますし、やってよかったと後から思えるはずです。

ScalikeJDBC 1.5.0 リリース

Type Dynamic 呼び出しを macro でチェックしてコンパイルエラーにする機能が追加されました。公開されていたコードを参考にさせていただいたり stackoverflow で回答いただいたりして @pab_tech さんのおかげでこぎつけたリリースでした。

http://seratch.hatenablog.jp/entry/2013/03/28/210928

elasticsearch 導入

本業で elasticsearch を導入しました。一つ一つのデータ件数はさほど多くないのですが、今まで Solr を個別に立てていたのを一カ所に集約できればということで、実際、いくつかのサービスが elasticsearch の利用を開始することになりました。この辺のことはここに書いておきましたので、興味があればご覧ください。

http://seratch.hatenablog.jp/entry/2013/09/03/234712

4 月

年度始めということもあり、結構仕事が忙しかったようです。GitHub での活動は細々やってはいましたが。

5 月

「RubyKaigi 2013」で LT

LT をさせていただきました。rspec-kickstarter という RSpec ソースコードのひながたを自動生成してくれる gem について話しましたが、今改めて録画を観てみるとかなり固いですね・・w

http://rubykaigi.org/2013/lightning_talks#seratch

これは元々の話し方の問題もあるんですが、実は入念に keynote のカンペを準備していたのが当日の環境で使えなかった*4ので、かなり余裕がなかったのが大きいです。。あと、英語プレゼンだったので事前に練習をしている中で厳しさを感じてだいぶ内容を削ったのですが、今見るとちょっと削りすぎた気がします。今客観的に観るとかなりアッサリした発表になってしまっているなぁと思います。

GitHub が提供してくれた懇親会では @sonots さんや多くの方とお話しして、初めての RubyKaigi でしたが、楽しむことができました。

自分でいうのもなんですが rspec-kickstarter は結構便利で、私は普段の開発でかなり必須ツールというくらいに使っています。よかったら試してみてください。

https://github.com/seratch/rspec-kickstarter

「Atlassianユーザーグループ」で発表

コードレビューツールとして Crucible を使っているのですが、それについてプレゼンさせていただきました。後にここで知り合った方の会社との交流会なども行いました。またそういう機会がつくれればいいですね。

http://www.zusaar.com/event/735003

http://seratch.hatenablog.jp/entry/2013/05/26/115810

ScalikeJDBC 1.6.0 リリース

QueryDSL が追加されました。これで現在の機能がすべて出そろった感じですね。もっと前から提供していた気がしていましたが、半年前でしたか。

http://seratch.hatenablog.jp/entry/2013/05/12/001057

社内の Tech Talk 開始

私が時々 #m3dev というハッシュタグでつぶやいているときがありますが、社内で M3 Tech Talk という社内 LT 大会のようなものをこの頃から始めました。隔週で今年は 16 回開催、エンジニア約 40 名の体制で毎回 3 つの発表がそろっていたのでなかなか成功したといえると思います。来年もできるだけ続けていきたいですね。

公開可能なものは資料を公開しているのでこちらからご覧ください。

http://m3dev.github.io/

「カンファレンスカンファレンス」に参加

scalaconfjp の運営側ということで参加しました。他の方々と色々とカンファレンスについて情報交換・・もしましたが、割と普通に雑談していた気も。登壇いただいた皆様のお話は普段あまり語られないことが多かったので、参考になりましたね。

http://connpass.com/event/2253/

6 月

「Play もくもく会」に参加

@kara_d さんがやっている Play もくもく会に参加して、ちょっとした発表と ScalikeJDBC を宣伝してきました。ScalikeJDBC を使っている方にも実際にお会いできたし、よい機会でした。来年も機会があれば参加したいと思っています。

http://playframeworkja.doorkeeper.jp/events/4219/

息子が 2 歳になりました

順調に育っています。

7 月

Typesafe Activator テンプレートをつくった

Typesafe 社が Activator というのを提供していて 3rd party が簡単にテンプレートを登録できるようになっているのですが、あまり作っている人がいなかったので試しにやってみました。

http://seratch.hatenablog.jp/entry/2013/07/02/005454

それから半年経って、テンプレートも若干増えてはいますが、いまいちエコシステムが確立されないというか Typesafe 以外の人が面白がってる感じが足りない気がしますね。つくること自体は簡単なので、興味のある方はやってみるとよいのではないでしょうか。

「JetBrains Night #jbnight」に参加

これは一参加者として参加しました。JetBrains CEO 来日に合わせて GREE さんで開催されました。あの新製品っていつ頃公開なのかな。

http://www.zusaar.com/event/844003

「第1回 かわいいKotlin勉強会 懇親会 #jkug」に参加

ドリコムさんで。これも参加者として。スーツ率の高さに驚きました*5。 あと、この手の勉強会にしては女性が多かったのも印象的でした。Kotlin コミュニティはなかなか興味深いですね。

http://www.zusaar.com/event/934003

「Heroku Meetup #9 Summer & Beer !!」に参加

Heroku は趣味で使っているくらいですが、これも参加者として。この月は勉強会にたくさん参加してますね。 シャノンさんのエンジニアの方々と特に色々話させていただいた記憶があります。この場でまた虎ノ門 Scala 会をやろうという話になりました。

http://herokujp.doorkeeper.jp/events/4687

8 月

AWScala 盛り上がる

Java の AWS SDK を Scala から使うのは結構つらいよね、特に REPL から使うとか無理ゲー」という感があったので API を理解するのもかねて Java SDK のラッパーを作り始めました。

https://github.com/seratch/AWScala

@OE_uia さんも面白がってくれて EC2 の実装を提供してくれました。

「第1回 ElasticSearch勉強会」に参加

参加者として。@cbirchall が発表していました。懇親会では @johtani さんやロンウィットの方々とお話しできてよかったです。

http://atnd.org/event/E0018616/0

「PlayFramework関西ビギナーズ 第2回 #play_kb」にエアー参加

大阪で Play の初心者向け勉強会が開催されているのを Twitter 上で観測したので、ブログ記事を書いて参加しました。

http://seratch.hatenablog.jp/entry/2013/08/03/151251

虎ノ門 Scala 会(飲み会)開催

第二回を開催しました。某 G 社の方々も参加されましたし、虎ノ門の枠にとらわれない感じになりました。Scala じゃない人もいたような気がします。

9 月

歌舞伎座.tech #1」で LT

歌舞伎座.tech で LT しました。ScalikeJDBC-Async の紹介と Skinny Framework のお披露目でした。歌舞伎座タワーはよい場所ですね。D 社の皆様も ScalikeJDBC や Skinny をお試しいただければ幸いです。

http://connpass.com/event/3278/

http://seratch.hatenablog.jp/entry/2013/09/25/215834

IAM 認証フェデレーション proxy をつくった

これは本業の方ですが「AWS のマネージメントコンソールへのアクセス管理が面倒で困っちゃうな」という感じになっていたので IAM の認証フェデレーションを利用した proxy をつくりました*6。これで IAM ユーザをたくさんつくったりする必要がなくなったので非常によかったです。認証時に各ユーザが AWS アカウントごとに用意したカスタムポリシーを有効化できるように作り込んだ点がポイントだったかなと思います*7

もう少し汎用化できれば OSS にしてもいいかなと思ったりはしていますが、同じようなことやってる人がいたら情報交換してみたいですね。

「Async Hack-a-thon」に会場提供&参加

@gakuzzzz さんが主催してくれたハッカソンに会場係兼参加者として。

最初は ScalikeJDBC-Async を使ってベンチマークとってドヤ顔するつもりでしたが DB 接続部分以外がボトルネックになって、想定した結果が得られなかったので、途中で方針転換して ScalikeJDBC のクエリ実行結果を取得できるフックポイントを実装して Fluentd 経由で送る、みたいなデモをした気がします。一応、非同期・・かな、と。

http://www.zusaar.com/event/991003

scct を fork した

Scala のカバレッジ計測ツールとしては現時点では最もクオリティの高い scct ですが、作者は完全にメンテナンスしないモードに入っていて、issue も PR も放置されている状態が続いていました。

私にとっては scct は以下のような問題があって

  • scct を使っているライブラリは意図せずに scct に compile スコープで依存してしまう*8
  • sonatype/maven central で配布されていない
  • バージョンがずっと 0.2-SNAPSHOT のまま

issue 登録してみたけど、案の定全くリアクションもなく、今後本家で対応される気配が感じられなかったので、自分で使う用のために fork して上記の点を修正したものです。

https://github.com/seratch/scct

https://github.com/seratch/sbt-scct

すると後継を目指して scct という orgnization を作ってる人がいて、どうせなら一緒にやろうみたいなことになったけど結局あまり貢献はできず(私は他のことに時間を使いたかった)、ただ一応私がやっていた sonatype への publish 設定だけは流用されたようで、そのうちあっちを使えればいいなぁと思っていたら、最近見たら、なんだかよくわからない状態に・・。

うまくいくといいですけどね。

10 月

Skinny Framework 0.9.x リリース開始

Skinny Framework は来年 3 月までに 1.0 を出すと宣言していますが、それを強く意識するために 0.9.x というバージョニングポリシーに変えました。0.9.x はまだプレビュー版なところがあるので、細かくリリースしています。また設計ミスなどによる改善点が見つかれば細かい API や挙動が変更になることが多々あります。

「怖くないScala勉強会」で発表

「めんどくさくない Scala」というタイトルで Scala での開発全般について私の考えを述べたのと ScalikeJDBC、Skinny Framework の紹介をしました。結構力を入れて準備したプレゼンでした。

http://seratch.hatenablog.jp/entry/2013/10/20/085453

DTS さんではこのような一般公開の勉強会は初めての試みとのことでしたし、当日のキャンセルも結構多くて @garbagetown さんはじめとする運営の皆様はなかなか大変だったかと思いますが、勉強会の内容は充実していたと思います。懇親会では初めて @takezoen さんとお話ししたり、よい機会でした。

http://connpass.com/event/3420/

「2013年秋 Web開発最前線テックトーク」に参加

参加者として。じげんさんが主催。会社のすぐ近くだったので参加しやすかったです。@naoya_ito さんのプレゼンが非常に参考になりました。

http://atnd.org/events/44191

JS 無双な日だったので @takezoen さんの Scala アピールが劣勢で大変そうでしたね。Slick のテーブル定義のコードを見て「やってみたい」と思ってもらえるかというとちょっと厳しいかなというのは感じました。

11 月

Skinny Framework 日本語導入資料

を gist で公開しました。元々は自分用に実装すべき要件を日本語で整理していたものだったのですが、これが日本での普及のきっかけになればということで。

https://gist.github.com/seratch/7382298#file-getting_started_ja-md

「怖いScala」で LT

GREE さんにて。「Skinny Framework 進捗どうですか?」というタイトルで Skinny Framework の紹介をしました。この日の発表でもそうでしたが @j5ik2o さんが「ScalikeJDBC は DDD の DB アクセス部分に使いやすい」と発信してくださっていて、とてもありがたいですね。

http://connpass.com/event/4112/

ScalikeJDBC 1.7.0 リリース、ウェブサイト公開、Twitter アカウント

ScalikeJDBC の organization を com.github.seratch から org.scalikejdbc に変更しました。

また Middleman を使って http://scalikejdbc.org/ をつくりました。内容は GitHub の wiki にあった情報を集約しました。ドキュメントは Scala のライブラリにしては充実している方ですが、もう少し親切な記述を増やしたいとは思っています。

https://github.com/scalikejdbc/scalikejdbc.github.io

またリリースなどをお知らせするために Twitter アカウントをつくりました。ぜひフォローしてくださいね。

https://twitter.com/scalikejdbc

Skinny Framework ウェブサイト公開、Twitter アカウント

こちらも ScalikeJDBC のやり方をそのまま流用しただけですが、ウェブサイトを公開しました。

http://skinny-framework.org/

https://github.com/skinny-framework/skinny-framework.github.io

こちらもリリースなどをお知らせするために Twitter アカウントをつくりました。試しに Scala に関心のありそうなユーザの方々をフォローして認知していただくという PR 活動をしてみました。あまりやりすぎるとよくないとは思いますが、ある程度認知はしていただけたのかなという感触でした。

https://twitter.com/skinnyframework

AWScala 0.1.0 リリース

AWScala はいったん一段落した感があったので、区切りとして 0.1.0 としてリリースしました。

12 月

アドベントカレンダー

に便乗して結構ブログ記事を書きました。何だか忙しくなってしまって Scala 版の方、途中でやってる余裕なくなったので穴を埋めるのあきらめました。ちょっと残念でした。Scala コミュニティはまだそんなに大きくないので、アドベントカレンダーは Scala と Play で分けずに Scala の一つくらいがちょうどよさそうですね。

http://www.adventar.org/calendars/104

http://www.adventar.org/calendars/114

まとめ

今年は主に Scala コミュニティでいろいろとやらせていただきました。本業では JSON API の設計・実装・運用を結構な数やったのと複数の Rails アプリを開発・運用したのが主な内容でした。Rails 3 -> 4 のアップグレードとか結構たくさんやった気がします。あと AWS でもいくつかトライした年でした。

いつもそうなのですが、来年の目標は特に立てていなくて、そのときの流れに合わせてその都度納得のいく行動ができればいいかなと思っています。直近では Skinny の 1.0.0 にたどり着けるよう引き続きやっていきます。

それでは、よいお年を。

*1:という、仕事もしてたんですよアピール

*2:ドキュメントはもし手伝ってくれる方がいると非常にうれしいです...

*3:O'Reilly本も買って読んだり

*4:ゆっくり確認する時間があれば設定を変更できたのでしょうけど...

*5:エンタープライズ受け?

*6:Rails アプリで Ruby の AWS SDK を使って実装しました

*7:たとえば development 環境ではほぼ何でもできる状態で使いたいんだけど、本番は最低限のオペレーションだけにしておきたいとか

*8:https://github.com/mtkopone/scct/issues/54

Play Framework 2 徹底入門を読んで #play_ja

Play Framework に関する初めての本格的な日本語書籍となる「Play Framework 2 徹底入門」を献本いただいたので、読ませていただきました。索引まで含めると 529 ページの力作です。著者の方々、大変お疲れ様でした。

タイトルに「Java ではじめるアジャイル Web 開発」とある通り、この書籍では基本的には Play2 Java がターゲットとなっており Scala での開発は 9 章のみで触れられています。

私は Scala 版はドキュメント、ソースコードを読んだり、実際にアプリケーションを書いたり、プラグインをサポートしたり、とそれなりに経験があるのですが、Java の方はしっかりと使ったことがなく、把握していなかった内容が多かったこともあり、楽しんで読ませていただきました。

とにかく初心者に優しく

まず一言で言うと、本書はとにかく「Play1、Scala どちらも未経験という方にも分かるよう、可能な限り親切に書かれた入門書」という印象を受けました。あとは Web アプリケーション開発自体の経験がそれほど多くない方も読者層として想定された書き方になっています。これだけ丁寧に書かれているのですから、500 ページを超えるボリュームになってしまうのも納得です。

スクリーンショットやサンプルつきの手順解説はもちろんのこと、COLUMN コーナーで読者がハマりそうなところ、疑問に思いそうなところを先回りして説明されているあたりに現場で Play2 Java を使ってきた先達である著者の気配りが感じられます。sbt の PermGen のサイズ、Windows での文字化けなど「あるある」ですよね。

Play2 JavaScala の知識を全く知らずには使えませんが、かといって「Scala 勉強してから来い」は Java で利用できる点を魅力に感じるユーザにとってはハードルが高すぎますね。

本書では Scala 初心者の方に少しずつ Play2 Java を使う上で必要となる Scala の知識を丁寧に説明していくことで、初心者の方がそこでつまづいてしまわないように注意が払われています。一方で、あまり最初の方で詳細について長い説明をしすぎないようになっていて、この辺は構成にかなり気を使われたのではないかと思います。

特に Scala Template と Routes は Play 独自の仕様なので、かなり丁寧に説明されています。特にテンプレートファイルが一旦 Scalaソースコードとして出力されてからコンパイルされるあたりは、本書の想定読者にとっては馴染みのないアプローチかと思うので、本書のような丁寧な説明は助かると思います。

4.1 で Play2 Java 選定がマッチする開発チームの条件を以下のように定義されています(この中でいくつか当てはまるなら Play2 Java は妥当な選択肢という意味です)。

  • シンプルな MVC 構成の Web アプリケーションを Java で作りたい。
  • Java 経験者で構成されたチームである、もしくは Java が得意なパートナーが多い。
  • Java を使用することが前提の開発案件である。
  • 分散並行処理を楽に行いたい。
  • 専用サーバーでの運用が前提で、クラウドでの利用も検討されている。
  • Play Framework に関して関心のあるメンバーがいる。
  • Scala に関心がある。

書籍内でも説明がありますが、最後の「Scala に関心がある。」というのは将来的に Scala を使っていきたいという志向の Java エンジニアを想定しています。Play2 Java を使った開発では Scala を全く意識せずに使えるというわけではないので、得意な Java で開発しつつ徐々に Scala のアプローチに慣れていくことになります。裾野を広げるという意味でいいことなのかもしれません。

ということで、本書は上記のような Java エンジニア読者層への入門書として最後までブレずに書かれているので「まさに自分が上記の条件に該当する」という方には非常にオススメできると思います。例えば「使ったことないし、よくわかってないけど Play2 Java 案件をやることになった」という方は、本書を手元に置いておくときっと何度も助けられるのではないでしょうか*1

Java 版もなかなかいいところあるのでは

正直、ほとんど初めて真剣に Java 版の Play2 に向き合ったのですが(すみません)、Java 版にもなかなかいいところあるかなというのが個人的には発見でした。

例えばフォームの入力値バリデーションは JSR-303 Bean Validation をラップした処理が Form.java に作り込まれていますが、拡張も含めて誰でもメンテナンスしやすいでしょうし、標準仕様なので場合によっては他の Java アプリとロジックを共有できるかもしれません。

本書にとって残念なことはかなり力を入れて解説している EBean が「開発がアクティブでない」という理由で次の 2.3.0 以降 Play の標準スタックから外れる見込みである点ですね*2。執筆者の立場からすると「ちょ、おまwww」という感じではないかと。お察しいたします。

私自身、EBean をしっかり使いこんだことはなかったのですが、本書で読む限りだと EBean は一般的に ORM に期待されるところには応えているんじゃないかなという印象は持ちました。既に EBean を使って作り込んでいる場合はとりあえずそれはそれでよいのかもしれません*3。今後、新しく開発するものはどうするかは Play 2.3 以降の動向を見つつの判断になりそうですね。

あと、テストコードはできるだけ簡単に書けるようにフレームワークが機能を提供すべきで、そうでないとテストが書かれないという事態に陥りがちかなと思いますが、Play2 Java ではある程度簡潔に書けますし JUnit なのでとっつきやすそうです。7.1 ではテストについて実際のサンプルもまじえながら説明されていますので、参考になると思います。

Scala 寄りの見方をすると Play2 は Scala の技術をベースにしているので Scala でやった方がよいという意見になるのですが、本書を読んでみて感じたのはやはり Java 版の方が初心者の方には相当にハードルが低いのだろうということでした。Play2 Java を使うことで高い生産性・品質を確保でき、開発チームのモチベーションの面でもよい効果が出ているという状況であれば、無理に Scala でなくてもよいのかもしれません。

ともあれ、この日本語書籍の存在が 2014 年の日本における Play2 Java の普及を後押しするとよいですね。

Play2 Java での開発をより快適に

ただ Play2 Java を使った場合でも Scala Template など直接 Scala を扱うことがなくなるわけではなく、Scalaコンパイル時間の長さに悩まされるケースがないわけでもないと思います*4

とりあえず開発マシンはそれなりの性能のものを使いましょうというのが前提になりますね。

本書ではなるべく標準の機能を使うように解説されていますが、テンプレートエンジンは別のものに差し替えるという判断もあってもよいかもしれません*5Java での開発なのに Scalaコンパイル待ちがネックになって利用が見送られるのは残念なので、この辺はコミュニティでアイデアを出し合えればいいですね。

宣伝: ScalikeJDBC も登場

ちなみに応用編の 9 章でのみ Scala による開発について触れられているのですが、そこで ScalikeJDBC を一言紹介してくださっていました。どうもありがとうございます。

年末年始に Play2 Java

を試してみてはいかがでしょうか?

See also

【献本御礼】「Play Framework 2徹底入門」は思っていた以上に実践的 - TEPPEI STUDIO http://teppei.hateblo.jp/entry/2013/12/22/065758

追記

空いていたので Play framework 2.x Java and 1.x Advent Calendar 2013 の 25 日目とさせていただきました(一日過ぎてるけど)。

http://www.adventar.org/calendars/104

*1:現状、そういうケースがどれくらいあるのか分かりませんが 2014 年は増えるといいですね

*2:https://github.com/playframework/playframework/issues/1518

*3:使い込んだことはないのであまり断言はできません

*4:https://groups.google.com/forum/#!topic/play_ja/Mv01m6T41iI

*5:http://d.hatena.ne.jp/xuwei/20131224/1387848493

Play ドキュメントを Skinny で書くと - The template engine

これは Play framework 2.x Scala Advent Calendar 2013 の 18 日目です。

http://www.adventar.org/calendars/114

なお、この記事で触れる Play は 2.2.1、Skinny は 0.9.20 です。両者とも最新バージョンでは API や仕様が変更になっている場合があります。

The template engine

http://www.playframework.com/documentation/2.2.x/ScalaTemplates

Play2 は独自の Scala Template を持っており、最近では、この実装は Twirl という名前で単独のライブラリとして切り出されています。

https://github.com/spray/twirl

Twirl は Spray のプロジェクトの一部なのですが、最近 Spray も Typesafe のプロダクトになったので、この辺も将来的には統合されていくとかどうとか(あまり動向追ってない)。

Twirl は Play に依存していないので Scalatra など別のフレームワークとも組み合わせて利用することができます。みんな大好き GitBucket は Scalatra + Twirl ですね。

https://github.com/takezoe/gitbucket

一方、Skinny は Scalate を標準のテンプレートエンジンとしてサポートしています。

http://skinny-framework.org/documentation/view-templates.html

http://scalate.fusesource.org/

Scalate も基本的には事前に Scala コードに変換してコンパイルするアプローチですが、Skinny ではなるべくコンパイル待ち・リスタート待ちを減らしたフィードバックの早い開発を実現するために、デフォルトだと開発時はインタプリタ的に実行する設定になっています。

これのデメリットとして、全てのテンプレートに対して検証がされないだけでなく、初回アクセスのパフォーマンスも悪くなるので skinny package で本番向けにビルドする際は事前コンパイルするようになっています。

なお FreeMarker、Thymeleaf のサポートも拡張として存在しますが、最も Scala の表現力を活かせるのはやはり Scalate になります。Java 向けのテンプレートでは Scala メソッド呼び出しでかなり制限があるので、アプリケーションの性質によっては厳しくなりそうです。この辺は用途に合わせて判断されるのがよいかと思います。

それでは早速 Play のドキュメントのサンプルコードを見ていきます。

Overview

@(customer: Customer, orders: List[Order])

<h1>Welcome @customer.name!</h1>
<ul>
@for(order <- orders) {
  <li>@order.title</li>
}
</ul>

上記のような例は Scalate の SSP(Scala Server Pages) だとこのようになります。少し書き方が違うだけですね。

http://scalate.fusesource.org/documentation/ssp-reference.html

<%@ val customer: Customer %>
<%@ val orders: List[Order] %>

<h1>Welcome ${customer.name}!</h1>
<ul>
#for (order <- orders)
  <li>${order.title}</li>
#end
</ul>

Scalate は SSP 以外にも Scaml、Jade、Mustache をサポートしています。私は Jade がお気に入りなので、この記事では Jade のサンプルを多く紹介します。

Jade は Scaml をより簡潔に書けるようにしたものなので Scaml と Jade のリファレンスを併せて見るとほぼやりたいことが網羅されています。ここでは Jade を中心に説明しますが Scaml はタグを %li のように書く以外は Jade とほぼ同じになります。

http://scalate.fusesource.org/documentation/scaml-reference.html

http://scalate.fusesource.org/documentation/jade.html

-@val customer: Customer
-@val orders: List[Order]

h1
  Welcome #{customer.name}!
ul
  -for (order <- orders)
    li 
      =order.title

Play Scala Template(Twirl)だと、コンパイル済のコードを controller から参照しますが*1

val content = views.html.Application.index(c, o)

Skinny では render メソッドに文字列で指定します。

render("/Application/index")

Play では app/views/Application/index.scala.html というファイルが存在する前提ですが Skinny では src/main/webapp/WEB-INF/views/Application/index.html.ssp が存在することが前提となります。

Syntax: the magic ‘@’ character

Play Scala Template(Twirl)では変数や for/if 式の始まりが @ からになりますが Scalate の場合は SSP と Scaml/Jade、Mustache でそれぞれ異なります。この辺は Scalate のドキュメントをご覧ください。

http://scalate.fusesource.org/

Template parameters

Play では、テンプレートが受け取るパラメータをこのように指定しますが

@(customer: Customer, orders: List[Order])

@(title: String = "Home")

@(title: String)(body: Html)

それぞれ Jade だと

-@val customer: Customer
-@val orders: List[Order]

-@val title: String = "Home"

-@val title: String
-@val body: String

のようになります。

Iterating

<ul>
@for(p <- products) {
  <li>@p.name ($@p.price)</li>
}
</ul>

Jade だと以下のようになります。インデントでスッキリと書くことが出来ますね。

ul
  -for (p <- products)
    li  #{p.name} (#{p.price})

If-blocks

@if(items.isEmpty) {
  <h1>Nothing to display</h1>
} else {
  <h1>@items.size items!</h1>
}

Jade だと以下のようになります。こちらもインデントでスッキリと書くことが出来ますね。

- if (items.isEmpty)
  h1 Nothing to display
- else
  h1 #{items.size} items!

Declaring reusable blocks

@display(product: Product) = {
  @product.name ($@product.price)
}
<ul>
@for(product <- products) {
  @display(product)
}
</ul>

@title(text: String) = @{
  text.split(' ').map(_.capitalize).mkString(" ")
}
<h1>@title("hello world")</h1>

JSP に何でも書いちゃうノリで SSP でゴリゴリ実装すれば同じことは出来ますが、わざわざ紹介するようなやり方でもないですね...

<% def display(product: Product) = s"${product.name} (${product.price})" %>
%for (product <- products)
  <%= display(product) %>
%end

ユーティリティの class/object を用意して利用すれば特に問題はなさそうです。

Declaring reusable values

@defining(user.firstName + " " + user.lastName) { fullName =>
  <div>Hello @fullName</div>
}

私の認識が正しければ { .. } 内に限定して値を渡すことはできないですね。そこにこだわらなければ Jade だとこんな感じでやればよいと思います。

-@val fullName: String = user.firstName + " " + user.lastName
div Hello #{fullName}

Import statements

@import utils._

SSP だと

<% import utils._ %>

Jade だと

- import utils._

のようになります。

Play だと build.sbt にあらかじめ

templatesImport += "com.abc.backend._"

とデフォルトの import を設定できますが Skinny では以下のクラスに

src/main/scala/templates/ScalatePackage.scala

  /** Returns the Scala code to add to the top of the generated template method */
   def header(source: TemplateSource, bindings: List[Binding]) = """
import com.abc.backend._
import com.abc.util._
  """

のように指定します。

Comments

@*********************
* This is a comment *
*********************@

Scaml/Jade では

-#
  This is a comment
  Next line is also comment

のように -# のあと、インデントします。

Escaping

<p>
  @Html(article.content)
</p>

デフォルトでエスケープされる点は同じです。エスケープしたくない場合は

p
  != article.content

または

p
  = unescape(article.content)

と指定してください。

Scala templates common use cases

http://www.playframework.com/documentation/2.2.x/ScalaTemplateUseCases

Layout

Play では views/main.scala.html で以下のようにしますが

@(title: String)(content: Html)
<!DOCTYPE html>
<html>
  <head>
    <title>@title</title>
  </head>
  <body>
    <section class="content">@content</section>
  </body>
</html>

Skinny は src/main/webapp/WEB-INF/layouts/default.jade で同じことは以下のように記述します。拡張子を変えて ssp/scaml/mustache で記述しても問題ありません。また、このテンプレートを読み込む先と利用するエンジンが異なっていても構いません(views/root/index.html.ssp でこの jade を使用するのも OK)。

-@val title: String
-@val content: String
!!! 5
html
  head
    title #{title}
  body
    section.content #{content}

詳細はこちらをご覧ください。

http://scalate.fusesource.org/documentation/user-guide.html

@(title: String)(sidebar: Html)(content: Html)
<!DOCTYPE html>
<html>
  <head>
    <title>@title</title>
  </head>
  <body>
    <section class="sidebar">@sidebar</section>
    <section class="content">@content</section>
  </body>
</html>

このテンプレートをこんな感じで使うと。

@sidebar = { <h1>Sidebar</h1> }
@main("Home")(sidebar) { <h1>Home page</h1> }

Skinny ではまずテンプレートがこうなり(Jade の場合)

-@val title: String
-@val sidebar: String
-@val content: String
!!! 5
html
  head
    title #{title}
  body
    section.sidebar #{sidebar}
    section.content #{content}

使う側はこのようになります。

-attributes("title") = "Home"
-attributes("sidebar") = capture {
  h1 Sidebar
-}
h1 Home page

Jade だと取っ付きづらく感じられる方もいるかもしれませんので SSP の例もあげるとこのようになります。

<% attributes("title") = "Home" %>
<% attributes("sidebar") = capture { %>
  <h1>Slidebar</h1>
<% } %>
<h1>Home Page</h1>

capture などについては User Guide をご覧ください。

http://scalate.fusesource.org/documentation/user-guide.html

Tags (they are just functions, right?)

views/tags/notice.scala.html のようなファイルに関数を定義しておけば taglib 的に使えるよ、という感じのものですが Scalate では普通に object つくってそれを import して使えばいいという感じのようです。

Includes

テンプレートの中で普通に render を呼べば他のテンプレートを include できます。例えば Jade のファイルと同じ階層に hello.ssp がある場合、以下のように指定するだけです。

= render("hello.ssp")

moreScripts and moreStyles equivalents

Play では @routes.Assets.at のように呼び出すと assets のパスを解決してくれますが Scalate では ContextPath を考慮した形でパスを設定してくれる uri/url というメソッドが使えます。以下は Jade の例です。

script(type="text/javascript" src={uri("/assets/js/jquery-2.0.3.min.js")})
link(rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.1/css/bootstrap.min.css")

以下と同じことは capture でできますね。

@scripts = {
    <script type="text/javascript">alert("hello !");</script>
}

明日・・・

ぶっちゃけ明日の記事は書いてないですし、ちょっとそろそろアレな感じです。明日、誰もいなかったら途切れてしまうかも・・

http://www.adventar.org/calendars/114

誰かの参加をお待ちしていますね・・って、私は一体何なのだろう。

*1:逆に言えば view がまだなかったら controller のコンパイルが通らない

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 を便利にする」というスタンスで機能拡張しています。

http://skinny-framework.org/

今回は 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 を使っています。ドキュメントはこちら。

https://github.com/scalatra/scalatra/blob/2.2.x_2.10/core/src/main/scala/org/scalatra/ActionResult.scala

このケースだと 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 つを提供しています。

https://github.com/skinny-framework/skinny-framework/blob/develop/framework/src/main/scala/skinny/controller/feature/ExplicitRedirectFeature.scala

// 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 の連携は以前から実装されていて、実績もあります。

http://scalikejdbc.org/

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 操作はすぐに使えるようになります。

https://github.com/skinny-framework/skinny-framework/blob/develop/orm/src/main/scala/skinny/orm/feature/CRUDFeature.scala

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 さんです。その次の日からまた空いてますけどね・・・

http://www.adventar.org/calendars/114