4.サーバーサイドバリデーションとサーバサイド関数

ログインのような簡単なFORMでも、入力値検証コード、さらには検証エラーバック時の再表示などを考えると、いろいろ煩雑になりますが、Liftは簡単な実現できるしくみがあります。

その前に、前回の LoginFormEasy.render で定義していたローカル変数 login/password に注目してください。

class LoginFormEasy {
  def render = {
  
    var login :String = ""
    var password:String = ""

    def process = {
      User.authenticate( login, password ) match {
        case Full(user) =>
          S.notice( "ようこそ %s さん".format(user.nickname) )
          S.redirectTo("mypage")
        case _ =>
          S.error( "ログインできません" )
      }
    }

    "name=login" #> SHtml.text( login, login = _ ) &
    "name=password" #> SHtml.password( password, password = _ ) &
    "type=submit" #> SHtml.button( <span>Join!</span>, process _ )
  }
}

これらの変数がいつ初期化されて、いつ値が設定されるのか、ちょっと混乱してしまいそうです。そもそもrenderメソッドはどのタイミングで実行されるのか。これらをはっきりさせておくと応用が効くと思います。結論としては、

1.ページが表示されたとき renderが実行
2.submit時、各input要素の第2引数の関数が実行。
  その後、submitコールバック(process)が実行。
3.process内からページ遷移(S.redirectTo)しなかった場合、再度renderが実行。

ここで問題になるのが、"ログインできません"のルートを通るとき、3.再度renderが実行されてしまうため、login/passwordは初期化されてしまい、ユーザーが入力した値を再表示できません。

そこで、RequestVarを使って変数スコープを変えるとうまくいきます。もちろん、Sessionスコープの値保持に利用する、SessionVarもあります。

class LoginFormServerValidation {

  object login extends RequestVar("")
  object password extends RequestVar("")
  
  def render = {
    
    def process : Unit = {
      
    //simple validation logic
      if( login.length < 1 ) S.error("login is empty")
      if( password.length < 1 ) S.error("password is empty")
      if( S.errors.size > 0 ){
          return // render restart
      }
      
      User.authenticate( login, password ) match {
        case Full(user) =>
          S.notice( "ようこそ %s さん".format(user.nickname) )
          S.redirectTo("mypage")
        case _ =>
          S.error( "ログインできません" )
       // render restart
      }
    }
    "name=login" #> SHtml.text( login, login(_) ) &
    "name=password" #> SHtml.password( password, password(_) ) &
    "type=submit" #> SHtml.button( <span>Join !</span>, process _ )
  }
}

普通は変数出し入れしてどうのこうの面倒な処理が、とても簡単に書けてしまいました。


それでもなお、気持ち悪い!

liftの気持ち悪さは、そもそも前述の

2.submit時、各input要素の第2引数の関数が実行。
  その後、submitコールバック(process)が実行。

にあるのではないでしょうか。
なんでサーバサイドで定義した関数が実行できちゃうのか、なんでページ遷移しているのにローカル変数の参照を持ち続けられるのか。。。

Liftは、HTML要素の生成と同時に、サーバサイドクロージャー( process _ や、 (s) => login(s) など)をメモリ上にキャッシュし、実行待ちの状態を作っています。
自動付与されたinput name属性(ブラウザでHTMLソースを見てみましょう)をキーに、キャッシュしたクロージャーを見つけ出して実行する。ということをやっています。
(net.liftweb.http.{S,SHtml}あたりを FuncHolder で検索してみると大体わかるでしょう。)

「Liftは負荷分散に弱いのでは?」とされる理由はココで、メモリ上に実行環境をキャッシュしているために、フェールセーフな環境を作りづらいということです。もちろん負荷分散ではSticky Sessionを利用したり、そもそもこれらの便利機能を避けた作りにすることもできるので、一概に「Liftはスケールしない」とは言えません。

ただ、この機構が気持ち悪いと感じる人は、他のフレームワークをあたりましょう!


このシリーズ

1.Lift再入門
2.Snippetメソッドとして許される型
3.ログインFORM - S.param使ったら負け
4.サーバーサイドバリデーションとサーバサイド関数
5.行列型の編集FORM
6.Radio、Checkboxが鬼門?
7.Ajax Form
8.javascriptからsubmitできない

動作確認サンプルコード github
Simply Lift(必読)


同じカテゴリのエントリ
1.Lift再入門 / 8.javascriptからsubmitできない / 7.Ajax Form / 6.Radio、Checkboxについて / 5.行列型の編集FORM / 4.サーバーサイドバリデーションとサーバサイド関数 / 3.ログインFORM - S.param使ったら負け / 2.Snippetメソッドとして許される型 / sbt0.12.xで依存jar抽出タスク / scala2.10+lift2.5+NetBeans7.2 / Scalaで入門関数プログラミング / reactive-webを試してみました / Lift2.2M1のテンプレート機能 / Scala Compiler Plugin / View Bound/Context Bound / ScalaZa01参加してきました / Akka Frameworkチュートリアルの次 / Akka Frameworkチュートリアルその2 / Akka Frameworkチュートリアル / LiftでJCaptcha / Url Rewrite Filter / sbt-android-plugin / Android SDK for Scala / 祝Lift2.0リリース / Liftの携帯対応まとめ / Scala2.8への移行 / Lift 2.0-scala280-SNAPSHOT/sbt0.7.1 / Scalaお絵かき環境 - Kojo / Lift+Quartzでバッチ / Scala&Liftを採用した理由 / Liftでdate_select系ヘルパーを作る / LiftでAjax / LiftのSubmitかしこい / lift-mapperのpaginateを使う / snippetをspecする / Lift Mapperを拡張する / LiftのDBをMySQLに / Liftプロジェクト環境を整える / Scala本読み比べてみました / NetBeans6.7&scala / じつはScalaはライトな言語 / Scalaバザ~ル / lift1.0所感 / specsを読む / implicit def / ScalaならNetBeansがサイコー / scala勉強会@東北がスタート / それでも俺はLiftをやるってのか / Scala&Liftセットアップ / ブログリニューアル /
コメント

コメントしてください

closed.