Life is TraversableOnce

programming / Java / Scala / Rust

Skinny Tutorial #2 試し書き(2)

第2章の続きです。

Userリソースの確認

skinny runコマンドからJettyを起動し、localhost:8080/usersにブラウザから接続してみます。

少し話は逸れますが、Skinny Frameworkが提供する強力な機能のひとつに、skinny.Skinnyクラスの存在があります。

このクラスはフレームワークが提供するさまざまな変数への参照を保持しているので、たとえばロケールごとのメッセージを取得したいときや、リクエストパラメータを取得したいときなど、クラスを大量にインポートすることなく、必要なものを取りだすことができるようになっています。

TODO Skinnyクラスの詳細をちゃんと確認すること

scaffoldで作成したSSPファイルからも、ボタンや入力フォームのタイトル表示などに、このskinny.Skinnyクラスのインスタンスを利用し、ロケールごとのメッセージ定義ファイルmessages.confに定義された文言を参照しています。

ここで本題のデモアプリケーションの確認に戻りますが、わたしの環境では、user.nameキーに対応するメッセージとして、Windowsのログオンアカウント名が表示される現象が発生しました。

f:id:letten:20150907232101j:plain

導入のさいに何か見落としているか、あるいは、sbtがホームディレクトリとしてログオンアカウント名のフォルダを探しに行っていた記憶があるので、そのあたりで覚えたものが表示されているのかもしれませんね。

今回は、原因究明を急ぎたいものでもないので、messages.confuser.nameキーを別のキーに変更し、SSPファイルの対応箇所を書き換えて、正常な表記となることを確認しました。

user {
  ...
  id="ID"
  dispname="Name" // <= name から変更
  email="Email"
}

f:id:letten:20150907232653j:plain

Micropostの作成

CRUD操作が可能であることを確認し、Bootstrapのすばらしい自動生成GUIを堪能したところで、駆け足ですが、このままMicropostリソースの作成に進みます。

この課題の肝は、他のリソースとの関係性を持つリソースの実装方法を確認することでしょう。

まずはUserの場合と同様に、scaffoldでひな形を作成します。

skinny g scaffold microposts micropost content:String userId:Int

マイグレーションを適用し、ブラウザから挙動を一通り確認して、まずはMicropostに最大文字数の制限をかけてみます。 バリデーションはリソースに対応するコントローラで実装されているので、初期値を書き換えて、contentフィールドの最大長を140文字にしてみましょう。

class MiropostsController extends SkinnyResource with ApplicationController
...
  override def createForm = validation(createParams,
    paramKey("content") is required & maxLength(140),
    paramKey("user_id") is required & numeric & intValue
  )
...
  override def updateForm = validation(updateParams,
    paramKey("content") is required & maxLength(140),
    paramKey("user_id") is required & numeric & intValue

f:id:letten:20150907234316j:plain

入力チェック機構が働いていますね!

バリデーションの使用方法を確認したところで、いよいよリソースの関連付けを記述します。 MicropostからUserへの依存を表現するために、MicropostのエンティティであるケースクラスにOption[User]型のフィールドを追加し、初期値にNoneを指定します。関連付け自体は、コンパニオンオブジェクトであるDAOにbelongsToメソッドとして定義します。

case class Micropost(
  id: Long,
  content: String,
  userId: Int,
  user: Option[User] = None,    // <= 追加
  createdAt: DateTime,
  updatedAt: DateTime
)

object Micropost extends SkinnyCRUDMapper[Micropost] with TimestampsFeature[Micropost] {
  belongsTo[User](User, (m ,u) => m.copy(user = u))    // <= 追加

同様に、Userモデルのエンティティには関連する複数の記事を表すフィールドとして、Seq[Micropost]型のmicropostsを追加、DAOには次のようなhasMany定義を作成します。

case class User(
  ...
  //  以下を追加
  microposts: Seq[Micropost] = Nil,
  ...
)

object User extends SkinnyCRUDMapper[User] with TimestampsFeature[User] {
...
  //  これ以下を追加
  lazy val micropostsRef = hasMany[Micropost](
    many = Micropost -> Micropost.defaultAlias,
    on = (u, m) => sqls.eq(u.id, m.userId),
    merge = (user, microposts) => user.copy(microposts = microposts)
  )

これらの関連付けを表すメソッドは、skinny.orm.feature.AssociationsFeatureトレイトで提供されています。わたしのデモアプリケーションでは、公開されているSkinny ORMのテストクラスを参考に実装しています。

まだちゃんと理解していないので断言はできませんが、どちらも外部キーとして他のテーブル(リソース)を参照し、関連先のエンティティをメンバとして保管できるようにしているようです。関連付けが一対一の場合はOption、多対一の場合はSeqに納められます。

以上でRailsチュートリアルの二章を、駆け足ですが体験することができました。 関連付けの挙動は、別途機会をもうけてconsoleから確認してみたいと思います。

次の投稿では、テスト駆動開発を本格的に始めるために、Skinnyが採用しているテスティングフレームワーク、ScalaTestの調査ができれば……と考えております。

続く