Snippetの作成
では、Modelを利用してデータの検索や登録を行うSnippetを作成します。
プロジェクトディレクトリ以下の 「src/main/scala/demo/twitterclone/snippet/」ディレクトリに、「Twit.scala」というファイル名で新しいファイルを作成します。
内容は、リスト7の通りです。
package demo.twitterclone.snippet import _root_.scala.xml.{NodeSeq, Text } import _root_.net.liftweb.http.{ RequestVar, S } import _root_.net.liftweb.http.SHtml._ import _root_.net.liftweb.util.Helpers._ import _root_.net.liftweb.util.{Box, Full} import _root_.demo.twitterclone.model._ /** * ついったーのようなものの画面で使用するSnippet */ class Twit { /** * <lift:Twit.post>タグで呼び出されるSnippet関数 * 入力フォームを生成する */ def post( xhtml:NodeSeq ):NodeSeq = { // User.currentUserで現在ログインしているユーザーを取得。 val user = User.currentUser // Messageモデルを作成する val message = Message.create.user( user ) // ユーザー名 val name = userName( User.currentUser ) // submitされた時点で呼び出され、メッセージの内容を // データベースに保存する関数 def addMessage:Unit = message.validate match { // パターンマッチで、入力チェックの結果エラーが // 発生していない場合のみ登録 case Nil => message.save ; S.notice("メッセージを投稿しました。") // エラーが発生してる場合はメッセージを表示 case x => S.error( x ) } // bind関数を利用して、引数のXHTML内で<twit:...>で始まるタグの // 内容を置き換える bind("twit", xhtml, "name" -> name, "status" -> message.status.toForm, "submit" -> submit( "投稿する", () => addMessage ) ) } /** * <lift:Twit.show>タグで呼び出されるSnippet関数 * 投稿されたメッセージを表示する */ def show( xhtml:NodeSeq ):NodeSeq = { // <xml:Group>で複数のタグをグルーピング <xml:Group>{ // Messageモデルから全件検索して、 // それぞれのレコードをbind関数を利用してXHMTLに変換 Message.findAll.flatMap{ msg => bind("twit", xhtml, "message" -> msg.status.is , "user" -> userName( msg.user.obj ), "dateOf" -> msg.dateOf.is.toString ) } }</xml:Group> } /** * Userモデルオブジェクトからユーザー名を取得するユーティリティ関数 */ def userName( user:Box[User] ) = user.dmap( "Guest" ){ user => user.shortName } }
このTwitクラスではpost関数とshow関数の2つのSnippetを定義しています。以下、この2つの関数について解説します。
フォームの表示とメッセージの登録を行うpost関数
以下のリスト6は、post関数の抜粋です。どのように出力を生成しているかについて解説します。
def post( xhtml:NodeSeq ):NodeSeq = { // User.currentUserで現在ログインしているユーザーを取得。 val user = User.currentUser *1 // ユーザー名 val name = userName( User.currentUser ) *2 // Messageモデルを作成する val message = Message.create.user( user ) *3 // submitされた時点で呼び出され、メッセージの内容を // データベースに保存する関数 def addMessage:Unit = message.validate match { *4 // パターンマッチで、入力チェックの結果エラーが // 発生していない場合のみ登録 case Nil => message.save ; S.notice("メッセージを投稿しました。") *5 // エラーが発生してる場合はメッセージを表示 case x => S.error( x ) *6 } // bind関数を利用して、引数のXHTML内で<twit:...>で始まるタグの // 内容を置き換える bind("twit", xhtml, *7 "name" -> name, "status" -> message.status.toForm, *8 "submit" -> submit( "投稿する", () => addMessage ) *9 ) }
このpost関数で行っている処理は、大きく分けて以下の3つです。
(1)ログインしているユーザーの取得と、Messageモデルの作成
*1では、Liftで用意されているUserモデルを利用して、現在ログインしているユーザーを取得しています。このUserモデルから、画面に出力する名前を生成しているのが、*2の処理です。
*3では、投稿されたメッセージに対応するMessageモデルのインスタンスを生成しています。ここでは、*1で取得したUserモデルを、Messageモデルで外部キーとして定義されているuserプロパティに設定しています。
ここで生成されたMessageモデルですが、フォームが送信されたときに動作するaddMessage関数から参照されています。
(2)フォームから送信された際に、Messageモデルを登録するaddMessage関数の定義
addMessage関数は、フォームが送信されたときに呼び出される関数で、Messageモデルのデータベースへの登録を行います。
*4で、Messageモデルのvalidate関数を呼び出して、入力チェックを行います。validate関数は、エラーがある場合はList[FieldError]を返し、エラーがない場合はNilを返します。
入力チェックの結果、エラーがない場合は、Messageモデルのsave関数を呼び出して、データベースへの登録を行います(*5)。登録後、net.liftweb.http.Sオブジェクトのnotice関数で、画面に表示するメッセージを設定しています。
エラーがある場合は(*6)、net.liftweb.http.Sオブジェクトのerror関数に、入力チェックの結果返されたList[FieldError]オブジェクトを渡して、出力するエラーメッセージの設定をしています。
addMessage関数は、post関数内に定義されており、post関数内の変数を参照することができます。
*7のbind関数の処理で、addMessage関数は、クロージャとしてリクエストをまたいで利用することができるようになっていますので、*2で生成したMessageモデルも、クロージャに取り込まれてフォームが送信された時点でも利用できます。
このように、リクエストをまたいでクロージャを利用することができるのは、LiftのFunctionMappingという機能で実現されています。詳しくは、本連載の第2回を参照してください。
(3)bind関数を用いて、フォームとして出力するXHTMLを生成する
*7では、bind関数を用いて、post関数の引数に渡されたXHTMLに含まれる「twit」という名前空間のタグを置き換えて、フォームを生成しています。
*8で、<twit:status/>タグに対して、message.status.toFormを設定しています。Liftのモデルの各プロパティには、toForm関数が用意されており、この関数を呼び出すことでモデルのプロパティに応じたTextFieldなどのHTMLタグを出力させることができます。
Messageモデルのstatusプロパティは、MappedTextarea型ですので、toFormの出力は<textarea>タグになります。また、フォームが送信された際に、<textarea>タグに入力されている文字列は、自動的にMessageモデルのstatusプロパティに設定されます。
*9は、net.liftweb.http.SHtmlオブジェクトのsubmit関数を利用して、submitボタンを生成しています。submit関数の第1引数はボタンの表示名、第2引数は「() => Any」型の関数オブジェクトです。ここで渡した関数オブジェクトが、フォームが送信された際に実行されます。ここでは、(2)で定義しているaddMessage関数を渡していますので、submitボタンでフォームが送信されるとaddMessage関数が呼び出されます。
メッセージを表示するshow関数
リスト9は、show関数の内容です。このshow関数は、それまで投稿されたメッセージをMessageのMetaMapperオブジェクトがもつfindAll関数を呼び出して、全件を取得しています。
取得した結果は、List[Message]型なので、flatMap関数でMessageモデルのインスタンスが持つデータを、bind関数でXHTMLに変換しています。
def show( xhtml:NodeSeq ):NodeSeq = { // <xml:Group>で複数のタグをグルーピング <xml:Group>{ // Messageモデルから全件検索して、 // それぞれのレコードをbind関数を利用してXHMTLに変換 Message.findAll.flatMap{ msg => bind("twit", xhtml, "message" -> msg.status.is , "user" -> userName( msg.user.obj ), "dateOf" -> msg.dateOf.is.toString ) } }</xml:Group> }