読者です 読者をやめる 読者になる 読者になる

seratch's weblog in Japanese

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

Skinny Framework Getting Started 日本語版

Scala SkinnyFramework

この記事は Scala福岡2016 - connpass でのハンズオン向けの入門記事です。

JDK (Java SE Development Kit) インストール

http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

Oracle 社の Web サイトにアクセスして、License Agreement に同意してから自分のプラットフォームにあったインストーラをダウンロードして実行してください。

f:id:seratch2:20160523212824p:plain

インストール後はターミナルから java コマンドに PATH が通っていることを確認してください。java -version でエラーにならず以下のような出力が表示されれば OK です。

$ java -version
java version "1.8.0_71"
Java(TM) SE Runtime Environment (build 1.8.0_71-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.71-b15, mixed mode)
$

skinny ダウンロード

Windows / Linux の場合

https://github.com/skinny-framework/skinny-framework/releases/download/2.1.1/skinny-blank-app-with-deps.zip をダウンロードして解凍したディレクトリで skinny スクリプトを使って以降の作業をします。

http://skinny-framework.org/ にダウンロードボタンがあります。

f:id:seratch2:20160523213032p:plain

Mac OS X の場合

Mac OS XHomebrew を使っているなら

brew update
brew install skinny

だけで OK です。Homebrew で始める場合は skinny new コマンドが使えます。

skinny new hello-skinny
cd hello-skinny

と実行してください。

Homebrew を使わない場合は Windows / Linux と同様、skinny-blank-app-with-deps.zip をダウンロードして解凍します。

これ以降の作業は全て共通です。

プロジェクトの動作確認

ここからは skinny スクリプトがあるディレクトリで作業します。Windowsコマンドプロンプトで作業する場合は ./skinnyskinny で読み替えてください。

$ ./skinny run

[info] Loading project definition from /Users/kazuhirosera/tmp/hello-skinny/project
[info] Set current project to skinny-blank-app-dev (in build file:/Users/kazuhirosera/tmp/hello-skinny/)
2016-05-23 21:45:31.340:INFO::pool-11-thread-3: Logging initialized @8585ms
2016-05-23 21:45:31.461:INFO:oejs.Server:pool-11-thread-3: jetty-9.2.17.v20160517
2016-05-23 21:45:31.858:INFO:oejw.StandardDescriptorProcessor:pool-11-thread-3: NO JSP Support for /, did not find org.eclipse.jetty.jsp.JettyJspServlet
2016-05-23 21:45:32.091  INFO   --- [ool-11-thread-3] skinny.micro.SkinnyListener              : The cycle class name from the config: Bootstrap
2016-05-23 21:45:32.260 DEBUG   --- [ool-11-thread-3] skinny.micro.SkinnyListener              : Loaded lifecycle class: class Bootstrap
2016-05-23 21:45:32.309  INFO   --- [ool-11-thread-3] skinny.micro.SkinnyListener              : Initializing life cycle class: Bootstrap
2016-05-23 21:45:32.739 DEBUG   --- [ool-11-thread-3] scalikejdbc.ConnectionPool$              : Registered connection pool : ConnectionPool(url:jdbc:h2:file:./db/development;MODE=PostgreSQL;AUTO_SERVER=TRUE, user:sa) using factory : <default>
2016-05-23 21:45:33.288:INFO:oejsh.ContextHandler:pool-11-thread-3: Started o.e.j.w.WebAppContext@77261de5{/,[file:/Users/kazuhirosera/tmp/hello-skinny/src/main/webapp/],AVAILABLE}
2016-05-23 21:45:33.310:INFO:oejs.ServerConnector:pool-11-thread-3: Started ServerConnector@62b55b27{HTTP/1.1}{0.0.0.0:8080}
2016-05-23 21:45:33.311:INFO:oejs.Server:pool-11-thread-3: Started @10556ms
[success] Total time: 2 s, completed May 23, 2016 9:45:33 PM
1. Waiting for source changes... (press enter to interrupt)

スタックトレースが出力されず 1. Waiting for source changes... (press enter to interrupt) で止まれば OK です。 この状態で http://localhost:8080/ で Web サーバ(Jetty)が立ち上がっています。停止させたい場合は Enter や Ctrl + C を押すとアプリが停止します。

それでは IntelliJ IDEA を使いたい人向けの解説を続けます。セットアップの必要ないエディタを使う方は読み飛ばしてください。Eclipse は筆者が Scala では使っておらずお勧めもできないため省略させていただきます。

ファイルの説明

http://skinny-framework.org/documentation/getting-started.html#project-structure

.
├── README.md # 自動生成された README です、プロジェクトに合わせて書き換えてください
├── bin
│   └── sbt-launch.jar # ./skinny や ./sbt が使う sbt の launcher です
├── build.sbt # これと project/Build.scala、project/plugins.sbt が sbt の設定ファイルです
├── project # sbt が使用するディレクトリです
│   ├── Build.scala # メインのビルド設定ファイルです
│   ├── build.properties # sbt 自体のバージョンを指定します、この記事時点で 0.13.11 が最新です
│   └── plugins.sbt # sbt プラグインを追加する場合はここに追加します
├── sbt      # ./skinny が使用する sbt 起動スクリプト
├── sbt.bat  # 同上、Windows 向け
├── skinny     # skinny スクリプト
├── skinny.bat #  同上、Windows 向け
├── src # Scala/Java のお作法的に src の下にソースコードや設定を置いていきます
│   ├── main # こちらが実アプリのディレクトリ
│   │   ├── resources # 設定ファイルなどを置く場所
│   │   │   ├── application.conf # アプリケーションの設定ファイルです
│   │   │   ├── logback.xml # skinny が使用する logback というログライブラリの設定ファイルです
│   │   │   └── messages.conf # 入力チェックエラーメッセージなどをここで指定します、i18n(国際化)対応
│   │   ├── scala # scala ソースコードの置き場所
│   │   │   ├── Bootstrap.scala # skinny アプリケーションが最初に呼び出すクラスです、名前は決め打ちです
│   │   │   ├── controller # controller を置く場所です、自由に変更してもコンパイルが通るなら問題ありません
│   │   │   │   ├── ApplicationController.scala # デフォルトの親 controller です、Ruby on Rails にならった命名ですが、リネームしても問題ありません
│   │   │   │   ├── Controllers.scala # ルーティングはここで指定してください
│   │   │   │   └── RootController.scala # http://localhost:8080/ はこの controller を呼び出します
│   │   │   ├── lib # util クラスなど置き場に困るようなコードはここに置いてください
│   │   │   ├── model # Rails でいう model としての置き場ですが、service や repository などが好みであれば変えても問題ありません
│   │   │   │   └── package.scala # デフォルトでこの package 全体で共有したいものがあればここに書きます
│   │   │   └── templates # Scalate というデフォルトのテンプレートエンジンが期待する package と class です
│   │   │       └── ScalatePackage.scala # Scalate の設定
│   │   └── webapp # Servlet の規約で置かれているディレクトリ
│   │       └── WEB-INF
│   │           ├── assets
│   │           │   ├── build.sbt # Scala.js 用の設定ファイルです、Scala.js を使わないなら不要です
│   │           │   ├── coffee # CoffeeScript を使って開発したい場合はここに *.coffee を置きます
│   │           │   ├── jsx    # React を使って開発したい場合はここに *.jsx を置きます
│   │           │   ├── less   # LESS を使って開発したい場合はここに *.less を置きます
│   │           │   ├── scala  # Scala.js を使って開発したい場合はここに *.coffee を置きます
│   │           │   └── scss   # LESS を使って開発したい場合はここに *.sass/scss を置きます
│   │           ├── layouts
│   │           │   └── default.ssp # デフォルトのレイアウトテンプレートです
│   │           ├── views
│   │           │   ├── error # HTTP ステータス 40x/50x のときに表示されるエラーページです、デフォルトでは ssp が使われます
│   │           │   │   ├── 403.html.jade
│   │           │   │   ├── 403.html.mustache
│   │           │   │   ├── 403.html.scaml
│   │           │   │   ├── 403.html.ssp
│   │           │   │   ├── 404.html.jade
│   │           │   │   ├── 404.html.mustache
│   │           │   │   ├── 404.html.scaml
│   │           │   │   ├── 404.html.ssp
│   │           │   │   ├── 406.html.jade
│   │           │   │   ├── 406.html.mustache
│   │           │   │   ├── 406.html.scaml
│   │           │   │   ├── 406.html.ssp
│   │           │   │   ├── 500.html.jade
│   │           │   │   ├── 500.html.mustache
│   │           │   │   ├── 500.html.scaml
│   │           │   │   ├── 500.html.ssp
│   │           │   │   ├── 503.html.jade
│   │           │   │   ├── 503.html.mustache
│   │           │   │   ├── 503.html.scaml
│   │           │   │   └── 503.html.ssp
│   │           │   └── root
│   │           │       └── index.html.ssp # RootController が render("/root/index") を呼び出していますが、このファイルが読み込まれます
│   │           └── web.xml # Servlet の設定ファイルです
│   └── test # テストコード、テスト用の設定ファイルの置き場です
│       ├── resources
│       │   ├── factories.conf # FactoryGirl を使った fixture 用のファイルです
│       │   └── logback.xml # テスト時に使用されるログ設定です
│       └── scala # テストソースコードの置き場です
│           ├── controller
│           │   └── RootControllerSpec.scala # MockController を使った controller のテストです
│           └── integrationtest
│               └── RootController_IntegrationTestSpec.scala # Jetty を起動した HTTP リクエストによるインテグレーションテストです
└── task
    └── src
        └── main
            └── scala
                └── TaskRunner.scala # db:migrate などのタスク実行設定がされているタスクランナーです

IntelliJ IDEA の設定

この説明は IntelliJ IDEA 2016.1.2 を前提としています。違うバージョンの場合、挙動が違う場合があるのでご注意ください。

まず Open を選んで、先ほど用意した skinny プロジェクトのディレクトリにアクセスします。

f:id:seratch2:20160523234750p:plain

このようにディレクトリ自体が青色のアイコンになっていれば skinny プロジェクト(というより sbt プロジェクト)として認識されています。これ以降の手順を進めてください。

以下のスクリーンショットでは hello-skinny となっていますが、zip を解凍した方は skinny-blank-app となっていますので、読みかえてください。

もし普通のディレクトリのように肌色で表示されていたら、ターミナルから ./skinny idea を実行してから IntelliJ IDEA の Open を試してください(一度 IDEA を再起動してから Open を試した方がいいかもしれません)。

f:id:seratch2:20160523234813p:plain

このように sbt プロジェクトとして import する設定があらわれます。デフォルトで OK ですので、このまま進めてください。

f:id:seratch2:20160523234823p:plain

このように処理が始まるのでしばらく待ちます。

f:id:seratch2:20160523234833p:plain

このように 4 つのプロジェクトを読み込むかどうか聞かれますが、このまま OK を押してください。

f:id:seratch2:20160523234846p:plain

おそらく No Scala SDK in module と表示されていて、Scala ソースコードコンパイルエラー表示になっているかと思います。Setup Scala SDK というリンクから設定してください。

f:id:seratch2:20160523234907p:plain

このようなダイアログで OK を押します。Scala SDK が未設定の場合は洗濯して設定します。このスクリーンショットでは 2.11.7 になっていますが 2.11.8 が選べるならその方が望ましいですが 2.11.x ならどれでも大丈夫です。

f:id:seratch2:20160523234918p:plain

しばらく待って src/main/scala/controller/RootController.scala などをクリックして開いてみて赤いコンパイルエラー表示がなくなっていればセットアップ完了です。

f:id:seratch2:20160523234930p:plain

設定がおかしくなったら

  • IntelliJ IDEA を終了させる
  • .idea ディレクトリを削除する
  • ./skinny idea コマンドを実行する
  • IntelliJ IDEA を起動して対象のディレクトリを Open して import を試みる

を試してみてください。

最初のコード生成

以下のページにならって最初のコードを自動生成してみましょう。

Getting Started - Skinny Framework

./skinny g scaffold members member name:String activated:Boolean luckyNumber:Option[Long] birthday:Option[LocalDate]
./skinny db:migrate
./skinny run

を実行するだけです。一つ一つのコマンドについて説明してきます。

$ ./skinny g scaffold members member name:String activated:Boolean luckyNumber:Option[Long] birthday:Option[LocalDate]

[info] Running TaskRunner generate:scaffold members member name:String activated:Boolean luckyNumber:Option[Long] birthday:Option[LocalDate]

 *** Skinny Generator Task ***

  "src/main/scala/controller/ApplicationController.scala" skipped.
  "src/main/scala/controller/MembersController.scala" created.
  "src/main/scala/controller/Controllers.scala" modified.
  "src/test/scala/controller/MembersControllerSpec.scala" created.
  "src/test/scala/integrationtest/MembersController_IntegrationTestSpec.scala" created.
  "src/test/resources/factories.conf" modified.
  "src/main/scala/model/Member.scala" created.
  "src/test/scala/model/MemberSpec.scala" created.
  "src/main/webapp/WEB-INF/views/members/_form.html.ssp" created.
  "src/main/webapp/WEB-INF/views/members/new.html.ssp" created.
  "src/main/webapp/WEB-INF/views/members/edit.html.ssp" created.
  "src/main/webapp/WEB-INF/views/members/index.html.ssp" created.
  "src/main/webapp/WEB-INF/views/members/show.html.ssp" created.
  "src/main/resources/messages.conf" modified.
  "src/main/resources/db/migration/V20160523235117__Create_members_table.sql" created.

[success] Total time: 8 s, completed May 23, 2016 11:51:17 PM

この時点で MVC のファイルが生成されて、ルーティング情報も設定済です。どのようになっているか skinny routes で確認してみましょう。:id は path パラメータで URL の一部が controller にパラメータとして渡されます。:id の値は後述の members テーブルの id です。:extjsonxml でアクセスできます。

GET  /?
GET /assets/css/*
GET /assets/js/*
GET /members
POST    /members
GET /members.:ext
POST    /members.:ext
GET /members/
POST    /members/
DELETE  /members/:id
GET /members/:id
PATCH   /members/:id
POST    /members/:id
PUT /members/:id
DELETE  /members/:id.:ext
GET /members/:id.:ext
PATCH   /members/:id.:ext
POST    /members/:id.:ext
PUT /members/:id.:ext
GET /members/:id/edit
GET /members/new

手順に戻ります。./skinny db:migrate でこのファイル DB に必要な members テーブルを作成します。

create table members (
  id bigserial not null primary key,
  name varchar(512) not null,
  activated boolean not null,
  lucky_number bigint,
  birthday date,
  created_at timestamp not null,
  updated_at timestamp not null
)

Skinny ではデフォルトでファイルベースのデータベースと連携するよう設定されていますが、MySQL などに変更も可能です。

$ ./skinny db:migrate

[info] Running TaskRunner db:migrate

2016-05-23 23:51:34.150 DEBUG   --- [     run-main-0] scalikejdbc.ConnectionPool$              : Registered connection pool : ConnectionPool(url:jdbc:h2:file:./db/development;MODE=PostgreSQL;AUTO_SERVER=TRUE, user:sa) using factory : <default>
2016-05-23 23:51:34.165  INFO   --- [     run-main-0] o.f.core.internal.util.VersionPrinter    : Flyway 4.0.1 by Boxfuse
2016-05-23 23:51:34.577  INFO   --- [     run-main-0] o.f.c.i.dbsupport.DbSupportFactory       : Database: jdbc:h2:file:./db/development (H2 1.4)
2016-05-23 23:51:34.722  INFO   --- [     run-main-0] o.f.core.internal.command.DbValidate     : Successfully validated 1 migration (execution time 00:00.020s)
2016-05-23 23:51:34.742  INFO   --- [     run-main-0] o.f.c.i.metadatatable.MetaDataTableImpl  : Creating Metadata table: "PUBLIC"."schema_version"
2016-05-23 23:51:34.766  INFO   --- [     run-main-0] o.f.core.internal.command.DbMigrate      : Current version of schema "PUBLIC": << Empty Schema >>
2016-05-23 23:51:34.766  INFO   --- [     run-main-0] o.f.core.internal.command.DbMigrate      : Migrating schema "PUBLIC" to version 20160523235117 - Create members table
2016-05-23 23:51:34.792  INFO   --- [     run-main-0] o.f.core.internal.command.DbMigrate      : Successfully applied 1 migration to schema "PUBLIC" (execution time 00:00.050s).
[success] Total time: 9 s, completed May 23, 2016 11:51:34 PM
$

ここまでできていれば ./skinny run を実行して 1. Waiting for source changes... (press enter to interrupt) が表示されたら、http://localhost:8080/members にアクセスしてみてください。このような CRUD 画面が自動生成されているはずです。

f:id:seratch2:20160528134757p:plain

「New」ボタンを押すとこのように validation も設定済の入力画面が表示されます。

f:id:seratch2:20160523235553p:plain

テストも自動生成されているので実行してみましょう。

./skinny db:migrate test
./skinny test

で、以下のように出力されます。

$ ./skinny test
[info] RootControllerSpec:
[info] RootController
[info] - shows top page
[info] MembersController_IntegrationTestSpec:
[info] - should show members
[info] - should show a member in detail
[info] - should show new entry form
[info] - should create a member
[info] - should show the edit form
[info] - should update a member
[info] - should delete a member
[info] RootController_IntegrationTestSpec:
[info] - should show top page
[info] MemberSpec:
[info] MembersControllerSpec:
[info] MembersController
[info]   shows members
[info]   - shows HTML response
[info]   - shows JSON response
[info]   shows a member
[info]   - shows HTML response
[info]   shows new resource input form
[info]   - shows HTML response
[info]   creates a member
[info]   - succeeds with valid parameters
[info]   - fails with invalid parameters
[info] - shows a resource edit input form
[info] - updates a member
[info] - destroys a member
[info] Run completed in 13 seconds, 149 milliseconds.
[info] Total number of tests run: 18
[info] Suites: completed 5, aborted 0
[info] Tests: succeeded 18, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 23 s, completed May 23, 2016 11:58:23 PM

生成されたコードを理解する

Controller

Controller のコードは差分のみが実装された非常にシンプルなものになっているのでどうすればいいか戸惑うのではないかと思います。

// $ cat src/main/scala/controller/MembersController.scala
package controller

import skinny._
import skinny.validator._
import _root_.controller._
import model.Member

class MembersController extends SkinnyResource with ApplicationController {
  protectFromForgery()

  override def model = Member
  override def resourcesName = "members"
  override def resourceName = "member"

  override def resourcesBasePath = s"/${toSnakeCase(resourcesName)}"
  override def useSnakeCasedParamKeys = true

  override def viewsDirectoryPath = s"/${resourcesName}"

  override def createParams = Params(params).withDate("birthday")
  override def createForm = validation(createParams,
    paramKey("name") is required & maxLength(512),
    paramKey("lucky_number") is numeric & longValue,
    paramKey("birthday") is dateFormat
  )
  override def createFormStrongParameters = Seq(
    "name" -> ParamType.String,
    "activated" -> ParamType.Boolean,
    "lucky_number" -> ParamType.Long,
    "birthday" -> ParamType.LocalDate
  )

  override def updateParams = Params(params).withDate("birthday")
  override def updateForm = validation(updateParams,
    paramKey("name") is required & maxLength(512),
    paramKey("lucky_number") is numeric & longValue,
    paramKey("birthday") is dateFormat
  )
  override def updateFormStrongParameters = Seq(
    "name" -> ParamType.String,
    "activated" -> ParamType.Boolean,
    "lucky_number" -> ParamType.Long,
    "birthday" -> ParamType.LocalDate
  )

}

ベタに書いた Controller や SkinnyResource の実装を見てみてわからない点があれば私やチューター係の人に聞いてみてください。

ルーティング定義は src/main/scala/controller/Controllers.scala で有効になっています。ルーティング定義のサンプルはこの辺にあります。

Model

ここでいう Model は Ruby on Rails にならってデータベースアクセスが可能なモジュールというニュアンスです。実際に service レイヤーを設けて entity / DAO を分離して開発することもできますが Skinny のデフォルトのやり方は Ruby on Rails にならったこのスタイルです。データベースアクセスは以下のようにして REPL(Scala コードを実行する対話環境)で試してみましょう。

$ ./skinny console

REPL が起動したら、まずはデータが入っていない状態で全件取得してみましょう。

scala> Member.findAll()

  [SQL Execution]
   select m.id as i_on_m, m.name as n_on_m, m.activated as a_on_m, m.lucky_number as ln_on_m, m.birthday as b_on_m, m.created_at as ca_on_m, m.updated_at as ua_on_m from members m order by m.id; (0 ms)

  [Stack Trace]
    ...
    skinny.orm.feature.FinderFeatureWithId$class.findAll(FinderFeature.scala:57)
    model.Member$.findAll(Member.scala:17)
    ...

res1: List[model.Member] = List()

レコードを insert してみましょう。

scala> Member.createWithAttributes('name -> "Alice", 'activated -> false)

  [SQL Execution]
   insert into members (name, activated, created_at, updated_at) values ('Alice', false, '2016-05-24 00:17:34.008', '2016-05-24 00:17:34.008'); (0 ms)

  [Stack Trace]
    ...
    skinny.orm.feature.CRUDFeatureWithId$class.createWithNamedValues(CRUDFeature.scala:213)
    model.Member$.skinny$orm$feature$TimestampsFeatureWithId$$super$createWithNamedValues(Member.scala:17)
    skinny.orm.feature.TimestampsFeatureWithId$class.createWithNamedValues(TimestampsFeature.scala:24)
    model.Member$.createWithNamedValues(Member.scala:17)
    skinny.orm.feature.NoIdCUDFeature$class.createWithAttributes(NoIdCUDFeature.scala:122)
    model.Member$.skinny$orm$feature$CRUDFeatureWithId$$super$createWithAttributes(Member.scala:17)
    skinny.orm.feature.CRUDFeatureWithId$class.createWithAttributes(CRUDFeature.scala:277)
    model.Member$.createWithAttributes(Member.scala:17)
    ...

res3: Long = 2

この状態でレコード件数をカウントしてみます。1 件になっているはずです。

scala> Member.count()

  [SQL Execution]
   select count(1) from members; (4 ms)

  [Stack Trace]
    ...
    skinny.orm.feature.CalculationFeature$class.count(CalculationFeature.scala:30)
    model.Member$.count(Member.scala:17)
    ...

res5: Long = 1

このように where 句を指定することもできます。

scala> Member.where('name -> "Alice").where('activated -> false).apply()

  [SQL Execution]
   select m.id as i_on_m, m.name as n_on_m, m.activated as a_on_m, m.lucky_number as ln_on_m, m.birthday as b_on_m, m.created_at as ca_on_m, m.updated_at as ua_on_m from members m where m.name = 'Alice' and m.activated = false; (0 ms)

  [Stack Trace]
    ...
    skinny.orm.feature.QueryingFeatureWithId$EntitiesSelectOperationBuilder.apply(QueryingFeature.scala:326)
    ...

res8: List[model.Member] = List(Member(2,Alice,false,None,None,2016-05-24T00:17:34.008+09:00,2016-05-24T00:17:34.008+09:00))

より詳しい操作についてはドキュメントを参考にしてみてください。

ORM - Skinny Framework

View

表示する部分は上記のコマンドの場合、SSP (Scala Server Pages) で生成されています。JSP や Velocity に似たものでループや分岐など必要な処理を素直に書くことができます。

公式ドキュメントや Scalate のドキュメントを参照してください。

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

またこちらは Play Framework を使ったサンプルになりますが view template は認証ページなどのみにとどめてサーバから JSON のみを返して JavaScript で処理するようにしても良いでしょう。

https://github.com/skinny-framework/skinny-orm-in-play

やってみよう

TODO 管理アプリを作ってみる

task テーブルを設計して scaffold して TODO 管理アプリを scaffold してみましょう。JavaScript + JSON API の構成にして TodoMVC にしてもよいですね。

一応、こちらでサンプルをつくってみましたので、迷ったら参考にしてみてください。

GitHub - seratch/scala-fukuoka-hands-on-demo: Skinny Workshop at Scala 福岡 2016

Typetalk 連携のアプリを作ってみる

以下は今回の会場を提供してくださっているヌーラボ様の Typetalk の OAuth 2.0 と連携するサンプルアプリケーションです。

github.com

これをベースに Typetalk に投稿するアプリをつくったり、他のサービスでもログインできるようにしてみたりしてみてはどうでしょう?

see also: http://skinny-framework.org/documentation/oauth.htmlskinny-framework.org

Redmine のデータベースから reverse-scaffold してみる

以下は Redmine のデータベースから reverse scaffold してみた結果です。生成してから全く手を加えずにちゃんと動いています。

github.com

Skinny の reverse-scaffold は FK(外部キー)がない場合は勝手に association を生成しません。上記の model クラスに適切な belongsTo や hasMany を定義してみて ./skinny console で join クエリの動作を確認してみるのも良いでしょう。また、何か他の既存データベースをつかって生成してみてもよいでしょう。

Scala.js を試してみる

Skinny では ./skinny scalajs:watch を実行するだけで Scala.js を使った開発を始めることができます。Scala.js に興味がある方はこれを機にぜひ触ってみてください。

Assets Support - Skinny Framework

FAQ

  • java.net.BindException: Address already in use というエラーが出たら、すでに別の terminal で ./skinny run していないか確認してください
  • Unsupported major.minor version 51.0 のようなエラーは JDK のバージョンが古くないか java -version で確認してください

OSS Gateワークショップ 3/26 にメンターとして参加した #oss_gate

こちらのるびまを読んで、そういえばブログを書きましょうと話があったのをすっかり忘れて書いてなかったことに気づいたので先日の OSS Gate に参加したときのことを書いてみます。

Rubyist Magazine - Rubyist Hotlinks 【第 36 回】 須藤功平さん

私が参加したのは先日の 3/26 に開催されたOSS Gateワークショップの第二回です。半蔵門にあるコデアルさんのオフィスをお借りしての開催でした。

oss-gate.doorkeeper.jp

参加したきっかけは、知人の方が立ち上げから参加していて、興味を持ちそうな私を誘ってくれたことがきっかけです。最初に参加しようとしていた立ち上げミーティングには参加できなかったのですが、2 度目の正直ということで今回参加できました。

・・というところで、ちょっと色々と忙しくて、この記事を完成させる時間がなく、とりあえず Twitter でざっと発言しておいたのが以下。

私自身は色々と他にもやりたいこともあるので、深くコミットはできないかなと思っていますが、、できるだけ応援していきたいと思っています。これを読んで興味を持ってくれた方がいれば、ぜひ参加してみてください。

2015 年の振り返り(ガンバライジング限定)

このブログに書くか微妙なところですが、まあいいかと。

今年の 2 月くらいから 4 歳の息子とガンバライジングというアーケードゲームで遊んでいるので、それについて今年を総括してみます。 なお、本業については今年結構色々やっててまとめるの大変そうだし、特にやりませんw

ガンバライジングとの出会い

今年に入って Twitter の私の日本語アカウント(@seratch_ja)にちょくちょく仮面ライダーネタが出てくるようになりました。これでも自重してるんですが...

そもそも私と仮面ライダーについては以下のような感じ。

  • 子供生まれるまで平成仮面ライダーについて全く知らなかった
  • 子供とリアルタイムで見ているのは鎧武(ガイム)、ドライブ、ゴーストの 3 シリーズ

大人になってからもリアルタイムで仮面ライダーを追っている人は結構いますが、私は完全に息子の影響でした。

とはいえ、ガンバライジングについては最初は「アーケードゲームとかお金かかるし、カード集めてもねぇ」という感じだったのですが。

息子の仮面ライダーに対する異常なまでの情熱

うちの息子は 2 〜 3 歳の頃、数字などを教えてもどうにも興味を示さず、いつになったら数字をスムーズに言えるようになるの・・状態だったのですが、ふとあるとき、他のことだと数字に対するグダグダが嘘のように異常に覚えが良いことに気づきます。そう、それが仮面ライダーでした。

フリマで買ってきた昔の仮面ライダーの絵本や Hulu でたった一度見ただけのライダーも「これ何て書いてあるの?」といちいち聞いてきて、都度教えてあげるとそれをすぐに記憶しているようで、私が何度見ても覚えられないのを尻目に次からは「え、これはキバに決まってるじゃない」などと言ってきました。

そうこうしているうちに 3 歳も後半になってアンパンマンをそろそろ卒業かなというタイミングで、二人で出かけていたときに買ったマクドナルドのハッピーセットにガンバライジングカードが付いていたのです。

www.ganbarizing.com

そういえば、このショッピングセンターに筐体があったなぁと思い、息子の仮面ライダー熱のこともあり、試しにやってみたのが始まりでした。そして、まんまと戦略に・・

ガンバライジングの遊び方

ゲームセンターに行くとポケモン、妖怪ウォッチ、ドラゴンボールなどカードバトル系のアーケードゲームで子供達がたくさん遊んでいるのを見かけることがあるかと思いますが、ガンバライジングはその仮面ライダー版です。

妖怪ウォッチやドラゴンボールほどは人気がないらしく、うちの近所だと基本的には並ばずに遊べる点が大人としてはありがたいです(アキヨドなどもっと人の多いところではそれなりに並んだりします)。

どういうものかはこの辺を見ると全部書いてあるのですが

私なりに前提知識がない人向けにざっくり書くと

  • 100 円を入れると 1 回遊べる + ランダムでライダーカードが 1 枚出てくる
  • ライダーカード 1 枚につき 1 人の仮面ライダーがゲーム内にロードされる
  • 3 枚のカードをロードして 3 対 3 のストリートファイト的な戦いをする
  • 戦いはスロットマシンで 〜100 までの攻撃力を 3 人分確定させて合計値(+エフェクト)で争う
  • ゲージがたまるとライダーカードを裏返せるイベント(バースト)が発生し、そのライダーがパワーアップする
  • IC カードを読み込ませて経験値やアビリティを蓄積してくとだんだん強くなっていく
  • 仮面ライダーゴーストからは変身ベルトで使うゴーストアイコンというおもちゃとも連動するようになった(これでちょっとプレイする子供が増えた気がします)

という感じです。ルールは比較的シンプルなので 3 歳児にも遊びやすかったようです。ただ本来は 3 歳でやってる子はそんなにいなくて、おそらく文字がある程度読める幼稚園から小学生がターゲットかなという内容です。

ガンバライジングカードの見方

現在放映中の仮面ライダーは「仮面ライダーゴースト」ですが、ガンバライジングでは放映中のライダーだけでなく過去の仮面ライダーのカードも出てくるようになっています。

公式サイトのカード一覧を見ると、どう見ればいいのか一瞬戸惑うかもしれませんが、とりあえずは「レアリティ」を見ればよいです。

例えば「LR」は「Legend Rare(レジェンドレア)」の略で、現行バージョンではもっともレア度が高いカードです。基本的にレア度の高いカードはカードバドルの中でも強いカードとして機能します。

カードのレアリティはカードの右下に「LR」などの略称が記載されているので現物を見るとすぐにわかるようになっています。カードショップで買い物するときなどは、レアリティとそのカードがどのシリーズのものかを見るとよいでしょう。例えば、現行の「バッチリカイガン 2 弾」だと「K2」となっていて、その前の仮面ライダードライブ時代の「ナイスドライブ {1-6} 弾」シリーズでは「D6」のように表記されています。

今年のプレイを振り返る

今年、私は息子と 150 回程度ガンバライジングをプレイしました。

1 回につきカードは最大 5 枚まで買える仕組みになっていますが、新しいシリーズが始まったときはたまに多めに買ったりする程度にとどめ、基本的には 1 枚だけ買って 2 回プレイするという形でした。

また、ガンバライジングは一度のプレイ時間が長いのが特徴で、オンライン対戦*1だと 10 分を超えることも珍しくありません。

これらの理由から、たとえ大人がハマっても大金を使うことが非常に難しい点がよいと思います。 11 ヶ月で 150 回なので、課金した金額は月 2000 円満たない程度といってよいでしょう。

高いか安いかは人によって判断分かれそうですが、子供とだいぶ楽しく遊んだので高くはないかなと・・たぶん。

LR/CP/VR カード

誰得かつガチ勢の人が見たら鼻で笑われそうなカードリストですが、入手したカードを棚卸ししてみました。

カードショップでも多少は買いましたが、基本的には上記のプレイの中で入手したものです。レジェンドレアは真ん中の「仮面ライダーゴースト ムサシ魂(K1-007)」だけで、あとは CP(キャンペーン)、VR(バーサスレア)です。

f:id:seratch2:20151231144438j:plain

バースト面はこんな感じ。

f:id:seratch2:20151231144444j:plain

個人的には魔進チェイサーがフォトジェニックなので好きですね。

SR カード

ちょっと上記だけだと寂しかったので、その下のグレードである SR(スーパーレア)のカードから一部をピックアップしてみました。

この SR 程度のカードであればカードショップに行けば 100 円で買えたりしますので、子供とガンバライジング始めたけどいいカードがなかなか出ないなという方は一度カードショップに行ってみるとよいのではないでしょうか。

f:id:seratch2:20151231144456j:plain

ちなみにカードショップは色々回ったわけではないですが、正直他のカードゲームほど在庫を持っているお店が多くないのが現状です。

私が行った中で一番良かったのは中野ブロードウェイの「まんだらけカード館」さんでした。ここにはかなり多くのカードがそろっています。

中野ブロードウェイ公式サイト

その他買ったもの

まずガンバライジングを始めるなら IC カードを買いましょう。いろいろ種類がありますが、私は緑色のやつを使っています。まだまだ 400 回のセーブ回数に達しないので、一枚目です。

http://www.ganbarizing.com/play/about-ic.php

ガンバライジングでは、プレイ中にカードをこすったり裏返したりするので、無防備な状態で扱っているとすぐにカードが擦り傷だらけになってしまいます。レアなカードだけでも保護フィルムに入れてあげましょう。

http://www.yodobashi.com/ec/product/100000001001014832/index.html?gad1=&gad2=g&gad3=&gad4=56278881131&gad5=7419906215107049486&gad6=1o2&xfr=pla&gclid=CP-gg4y9hcoCFVgkvQodKsIKmA

カードが増えてくると、すぐに取り出せるようにカードバインダーに整理して入れてあげる必要も出てきます。私はもっと安いやつにしていますが、純正のやつはカッコいいですね。

http://www.amazon.co.jp/gp/aw/d/B012CT49N0/

ちなみにカードバインダーの中のページは買い足せばどんどん増やしていけるのですが、4 穴式と 6 穴式という互換性のない二つの製品があるので買い間違えないように注意しましょう。私は最初に買ったとき、そんなトラップがあるとは思わず、まんまと間違えました。

よく間違える人がいるらしく、親切な店員さんであれば 4 穴だけど合ってる?と聞いてくれたりします。

まとめ

ということで、ガンバライジング限定で私の 2015 年を振り返ってみました。

もし興味がある人がいましたら、カンファレンスやミートアップイベントなどでお会いしたときにでもガンバライジング談義を持ちかけてくださいw

まあ来年も今年ほどやるかというと他のことにもっと時間使わないといけないなと思ってますが、、息子が飽きないうちはある程度は付き合ってあげようかなと*2

*1:ちなみに、このオンライン対戦は本当にリアルタイムで対戦しているわけではなく、同時間帯にオンライン対戦に参加しているプレイヤーのデータを使ったコンピュータとの戦いです。同じ場所に筐体が 2 台あって連携し合っていればその場でリアルタイム対戦もできます。

*2:建前的には私は付き合ってあげているのです

Skinny Meetup Tokyo 2 を開催しました #skinnyjp

Scala Skinny Framework

Skinny Framework 2.0 をリリースして一ヶ月弱、@yusuke さんのご厚意により、東池袋サムライズムのオフィスをお借りして Skinny Meetup Tokyo の第 2 回を開催させていただきました。

skinnyjp.doorkeeper.jp

当日使ったスライドはこちら。

www.slideshare.net

英語版はこちら。

www.slideshare.net

関連する tweetTogetter にまとめました。

togetter.com

当日の内容

Meetup の流れは、まず、私の方から Skinny が 2.0 で変わったこと(とちょっとだけさだまさしコンパイラのデモ)をお話しました。次に、社内の情報共有ツールの実装に Skinny Framework を使ってくださっているという @roundrop さんから、その社内情報共有ツールについての紹介、初めての Scala 開発で Skinny Framework を使ってどうだったかという所感についてお話いただきました。具体的には以下のインタビューで触れられている Siita というツールです。

www.atware.co.jp

数年 Scala をやっている身からするとすっかり忘れかけていたような、初めて Scala をやったときに困った点などもバランスよくまとめていただいていたので、おそらくこれから Scala、Skinny をやってみようという方々に大変参考になったのではないかと思います。この発表のスライドは後日公開いただけるとのことでしたので、楽しみにしております。 (追記)公開していただいたので、以下に埋め込みました。

www.slideshare.net

サムライズム最高

当初は一人千円いただいてピザとドリンクを準備予定でしたが、サムライズム @yusuke さんのご厚意に甘えさせていただきまして、なんと無料で懇親会を行うことができました。再度、お礼申し上げる次第です。

tweet とこのブログで紹介させていただく程度のことしかできませんが、以下のようにサムライズムでは OSS コミュニティも応援されているそうです。また IntelliJ IDEA や JRebel の業務導入を検討されている企業様におかれましては、ぜひ日本の商慣行にも柔軟に対応しているサムライズム社でご購入いただければと思います!

Skinny Micro

今回、初めて Skinny Micro についてちゃんと説明させていただきましたが、Scalatra を fork してリファクタリング、機能追加したものでかなり手軽で使いやすいライブラリになったのではないかと思っています。

Skinny Micro

どれくらい手軽かというと、以下に示すこれだけのコードで Web アプリをつくれるくらい手軽です。この Web アプリは Jetty サーバで起動して curl -v 'localhost:4567/say-hello?name=Martin' というリクエストに対して Hello, Martin! というレスポンスボディを返します。

https://github.com/skinny-framework/skinny-micro/tree/master/scalas-samples

#!/usr/bin/env scalas
// or ./scalas Hello.scala
/***
scalaVersion := "2.11.7"
libraryDependencies += "org.skinny-framework" %% "skinny-micro-server" % "1.0.0"
*/
import skinny.micro._
object HelloApp extends WebApp {
  get("/say-hello") {
    s"Hello, ${params.getOrElse("name", "Anonymous")}!\n"
  }
}
WebServer.mount(HelloApp).port(4567).start()

Heroku にすぐにデプロイできるサンプルや基本的な使い方を列挙したサンプルもありますので、ご参照いただければと思います。

https://github.com/skinny-framework/skinny-micro-usage-example

https://github.com/skinny-framework/skinny-micro-heroku-example

まだ公開して日が浅く GitHub スター数がちょっと少なめなので、気に入った方はぜひ stargazer になっていただけると嬉しいです。

github.com

Skinny の今後

Skinny Framework

Skinny 2 は大きな非互換を入れたわけでなく Scalatra からの移行という大きなイベントがあったのでメジャーバージョンを上げたという事情がありました。既に 1.3 でフレームワーク本体で備えるべき機能はそれなりに揃ってきているという認識で、大きくコードベースを変えていくことは考えていません。しかし、当然ながらソフトウェアに完成ということはありませんし、また利用者からするとメンテナンスされ続けていることは非常に重要なことですので、これからも手を入れ続け、マイナーバージョンでのリリースは続けていきます。

Skinny Framework の売りとしては、以下のような内容に共感いただける現場・用途には非常にマッチすると考えています。改めて明文化してみましたが、これは公式サイトにも明記しておくべきですね。

  • 今後も Scala 界における Servlet での Web アプリケーション開発フレームワークデファクトを目指し続けます
  • 今後も(既にコモディティ化している)Rails のイディオムや基本的なコンセプトに近い立場を保つことで、その下地がある開発者にとって敷居の低いフレームワークであり続けます
  • 既に 1.3 で当初予定していた最低限の機能セットは実現できたと考えており、安定フェーズとしてプロジェクトを運営していきます
  • 外的要因(Scala 本体など)の大きな変化以外での非互換な変更は基本的に入れない方針で運営していきます
  • Servlet に依存していないサブモジュール(ORM や Validator、HTTP クライアントなど)は今後も独立したライブラリとして利用可能であることを保証し続けます
  • 日本語圏に閉じず、世界中のさらなるユーザ拡大に努めてガラパゴス化のリスクを避ける取り組みを今後も続けます

特に、運用に入ってからのメンテコストを抑えたい、新しいメンバのキャッチアップのしやすさを重視したいという目的の場合、期待に答えられるフレームワークだと思っておりますので、選択肢の一つとしてご検討いただければと思います。

まだ時期やプランの詳細は未定なのですが、来年は Skinny Framework を業務の現場で利用いただきやすくなるようなサポートにもチャレンジできないかと検討しているところですので、引き続きよろしくお願いいたします。

ScalikeJDBC / Skinny Framework グッズ 2015 春モデル

scala

SUZURI 最高

去年から GMO ペパボさんの suzuri.jp を利用して Skinny Framework のロゴをプリントしたマグカップを販売しています。

これまでに 13 個お買い上げいただいております。ありがとうございます!*1

Skinny Framework 2015 Spring

さて、春ですし 2015 春モデルと称して新しく T シャツもつくってみました。

お子さんが生まれた方はぜひロンパースを。

暖かくなってきたので時期的にちょっと微妙ですが、スウェットもつくってみました。

ScalikeJDBC 2015 Spring

待望の? ScalikeJDBC グッズもつくりました。ScalikeJDBC を使って開発されている方もどうぞ。

みんなで買えば送料がお得

販売しているグッズ一覧はこちらで見ることができます。

https://suzuri.jp/seratch

複数口で買うと送料がお得になります。お買い上げいただける際はぜひ開発チーム、部署、会社単位でまとめてご注文ください!

*1:トリブンは 0 円設定なので、私の懐には 1 円も入れておりませんが

Future についての私見

scala

qiita.com

この記事を読んで、記事の本筋からはそれますが、前から思っていたことをつぶやいたのでまとめておきます。

「それ、今まで通り同期処理で Web ページ返す処理つくるだけで全然いいよね」という要件なのに、非同期縛りをやるのはちょっと不毛な気がするな...という。超高負荷な環境で CPU 使用抑えたい、同時接続が多いんで、とか何かしらの強いモチベーションがあることが大事で、それ以外は適材適所がよいと思うのです。

もちろん新しいアプローチにトライするのは楽しいことだし、そういうコミュニティの盛り上がりは素晴らしいことなので水を差したいわけでは全くないんだけど、仕事でやる開発において「Play とか Scala が盛り上がってる。Rails でやってた案件を今度は Scala でやろう。」みたいなことだとまず幸せにならないなと。小規模な案件は実はただもう少し静的型が欲しいだけなんじゃないかなって思ったりしています。

Java を Scala の中で使うこと

Scala

ScalikeJDBC は手軽なはず

ScalaでORマッパーというとSlickやScalikeJDBC等色々あるが、ほんとにちょっとしたツールを作りたいだけならばもっと手軽にやりたいと思うはず。

ScalaでDBを使った小物ツールをサクッと作れるJDBI - 気まぐれラボラトリィ

手軽ですよということを示すためにちょっとしたサンプルを書きました。ScalikeJDBC や Skinny ORM のミニマムな導入方法としても参考になるかなと思います。

https://github.com/seratch/jdbi-scalikejdbc-skinny-orm-example/blob/master/src/main/scala/app.scala

JDBI については私も結構好きなんですが Java であることの限界が見えてしまっている感はあります。SQLアノテーションに書くわけですが、改行もできないですし、アノテーションに渡す文字列はベタ書きするしかないので、こういう簡単なケースではなく、それなりの SQL を書くとなるとかなり苦しいと言わざるをえません。

また Java ライブラリ一般に言えることとして @BeanProperty とか Java っぽいアノテーション駆動な処理が入るコードでは Scala の恩恵を受けづらいことが多いと思います。コンパイルの重さを考慮するとそういうケースでは Groovy の方がよかったりするかもしれません。

JavaScala の中で使うこと

今更という気もしますが、ちょうどよい機会なので Scala の中で Java を使うことについて意見を書いておこうかなと思います。

いざとなれば Java の既存コードをそのまま使えるのは Scala のよいところなのですが、あくまで「いざとなれば」というオプションであり、かなりニッチな分野であればまだしも、RDB アクセスのようなありふれたユースケースではファーストチョイスが Java の利用とは考えない方が幸せになれると思います。

自分自身を振り返っても Scala を始めた頃は「手慣れた Java のライブラリを使ってやった方が楽なのでは?」と思ったりしたものですが、結局 JavaScala の架け橋となるところをケアしながら書かないといけないので簡潔にはなりにくく、落とし穴もあったりします。

例えば null は Scala の世界では「なかったこと」になっていて Option にしてあげないといけないですが、そういうことをやってくれる Java のライブラリはまずないので JavaAPI からの戻り値を自分で Option で包んであげないといけなかったりします。また Scala のコレクション型のオブジェクトは Java の世界からはうまく扱えないので Java の世界に渡す前に必ず scala.collection.JavaConverters なりで変換してあげる必要がありますし、逆に java.util.List などが Java から返ってくれば毎回 #asScala を呼んで変換する必要があります。

どういうときに Java を混ぜるか

ということで Scala であえて Java API を使う場合というのは、既に JavaSDK が提供されている場合だったり、よほど Scala で実装されたライブラリで選択肢がないケース(枯れてない、難解すぎる、機能が足りない、メンテされてないなど)に限るのがよいかと思います。

「どうしてもこの Java ライブラリを流用したい」というケースはあるかと思いますが、それほどのものなのであれば自分で Scala ラッパーを書いてみることも検討してもよいでしょう。OSS として公開すれば一定の需要もあるかもしれません。

以上、あるべき論で Scala らしいコードを書くべきということではなく「(ニッチなケースでなければ)普通は Scala ネイティブなやり方をした方が結果的に楽ですよ」という話でした。