sohatach's blog

http://github.com/soha

Skinny Frameworkを使ってみる。その2

このところ業務が忙しくなかなか思うように時間取れてませんでしたが、
少し落ち着いてきたので改めてSkinny Frameworkの学習再開。

とあるWebアプリを作ることを目標にしていますが、
scaffoldするところまではいいものの、Scalatraの知識も全くない私には、
その後自動生成されたControllerクラスをどう修正したらいいのかさっぱりわからない。。。
なのでまずは、Controllerの書き方を理解し整理します。

まずは公式のskinny-framework-exampleを参照してみる

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

特に解説はないようですが、以下の実装を見る限り、MembersControllerというのがscaffoldされたもので、
CompaniesControllerの方が具体的にソースを記載した場合のやり方と思われます。
Controllersの方は、URL呼び出しの起点となるファイルのようで、Railsで言うところのroutes.rbみたいなもののようです。
https://github.com/skinny-framework/skinny-framework-example/blob/master/src/main/scala/controller/Controllers.scala
https://github.com/skinny-framework/skinny-framework-example/blob/master/src/main/scala/controller/MembersController.scala
https://github.com/skinny-framework/skinny-framework-example/blob/master/src/main/scala/controller/CompaniesController.scala

Controllers.scalaには、CompaniesControllerの処理については細かく記述されていますが、
MembersControllerについては一切記述が無いため、MembersControllerの方は、おそらくデフォルト動作をするものと思われます。
CompaniesControllerの記述を見てみるも、sinatraもscalatraの知識もない私には何が書いてあるのかすぐには理解できませんした。。。
調べて理解した(と思っている)ものを記載します。

Controllerからのリクエスト処理の一連の流れ(indexへのアクセス例)

val indexUrl = get("/companies")(showResources).as('index)

例えばこの記載などは、ブラウザから/companiesのパスのURLにアクセスされると
showResourcesメソッド(CompaniesControllerにて定義)が呼ばれるようです。
「get("/companies")」がURLのパス、続く「(showResources)」が対応するメソッドのようです。
そのshowResourcesの中で

render(s"/companies/index")

https://github.com/skinny-framework/skinny-framework-example/blob/master/src/main/scala/controller/CompaniesController.scala#L31
が呼ばれていますので、
/src/main/webapp/WEB-INF/views/companies/index.html.ssp
https://github.com/skinny-framework/skinny-framework-example/blob/master/src/main/webapp/WEB-INF/views/companies/index.html.ssp
によりブラウザにHTMLが返されるところまではわかりました。

また「.as('index)」の意味するところとしては、Railsのroutes.rbと同じと考えると
このURLに名前をつけているということだと思われます。
(シングルクォートで囲っているから、Scala的にはシンボルになる。)
http://skinny-framework.org/documentation/controller-and-routes.html
のReverse Routesのところに

val showUrl = get("/members/:id")(show).as('show)

として定義しておくと、テンプレート上で以下の通り呼び出せるよと書いてありました。
Controllers.members.showUrlのところ。Rails同様後ろにUrlは付ける必要があるもよう。

a(href={s.url(Controllers.members.showUrl, "id" -> member.id)}) Show detail

・Play ドキュメントを Skinny で書くと - HTTP routing
http://seratch.hatenablog.jp/entry/2013/12/11/075312

ちなみにRailsの場合のURLルーティングについては、以下のサイトが大変参考になりました。
・Rails3 routes.rb まとめ
http://akkunchoi.github.io/rails3-routes.html

Controllerからのリクエスト処理の一連の流れ(showへのアクセス例)

続いてControllers.scala

val showUrl = get("/companies/:id") {
params.getAs[Long]("id").map(id => showResource(id)).getOrElse(haltWithBody(404))
}.as('show)

引数を1つ持つshowResourceメソッドについては、CompaniesController.scalaに定義されてます。

def showResource(id: Long) = {
set("item", Company.findById(id).getOrElse(haltWithBody(404)))
render(s"/companies/show")
}

set("item", Company.findById...
としてパラメータより取得したidをもとにCompanyのオブジェクトを設定しています。
/src/main/webapp/WEB-INF/views/companies/show.html.ssp
https://github.com/skinny-framework/skinny-framework-example/blob/master/src/main/webapp/WEB-INF/views/companies/show.html.ssp
を見ると

<%@val item: model.Company %>

して

${item.id}

などとitem変数の値をテンプレートの中で使用しています。

getOrElse()
の引数にhaltWithBody(404)というのを指定しています。
これがどこで定義されているのかちょっとよくわからなかったのですが、
表示されているHTMLの内容を見ると
src/main/webapp/WEB-INF/views/error/404.html.ssp
が読み込まれているように思います。

set("item", Company.findById(id).getOrElse(haltWithBody(404)))
この場合は、item変数に何が入るのだろう???

params.getAs[Long]("id").map(id => showResource(id)).getOrElse(haltWithBody(404))
アクションメソッドのこちらの方は、showResource(id)がNoneを返したときに、
アクションメソッドの戻りとして、haltWithBody(404)の結果が返されるというのはわかるのですが。。。

実際に試してみました。

haltWithBody(404)
//render(s"/companies/show")

item変数に値を入れず、renderも呼ばなくても、同じ404 Not Foundのページが表示されたため、
haltWithBody(404)が呼ばれることで途中の処理をすっ飛ばし、エラー画面へ遷移するようです。
ググッたところ、特に本メソッドに関する説明は見つからなかったので、
Scalatraが持つメソッドなのかSkinny Frameworkが定義しているものなのかよくわかりませんでした。
こうなったらソースを当たるしかないようです。
まあhaltってついているので単語の意味からおおよそ動きは予想できますが。

ソースを検索したところ、SkinnyControllerBase.scalaにて定義されていましたので、Scalatraが持つメソッドではないようでした。
https://github.com/skinny-framework/skinny-framework/blob/bb8b818ffba9518c7dc40a7c2a2b650e1d70d6d5/framework/src/main/scala/skinny/controller/SkinnyControllerBase.scala#L75
結局のところ、haltメソッド(こちらはScalatraのメソッド)を呼んでいました。

protected def haltWithBody[A](httpStatus: Int)(implicit format: Format = Format.HTML): A = halt(status)

haltメソッドについて記載があるドキュメント
http://www.scalatra.org/2.2/guides/http/routes.html

Scalaのimplicitについて非常にわかりやすい解説
http://d.hatena.ne.jp/gakuzo/20111204/1323007376


うーん、実際の開発の方が全然進んでいない。
まだまだ私は勉強が足りないなと痛感。。。わからない中書いているため文章も無駄に長くなってる気がする。
しかしタイプセーフなRails(Skinny Framework)が使いこなせるようになればかなり有益なはず。
この後もしばらくはControllerまわりの理解を深めたいと思います。