冥冥乃志

ソフトウェア開発会社でチームマネージャをしているエンジニアの雑記。アウトプットは少なめです。

follow us in feedly

Unfiltered + HerokuでTwitter ToDoサービスを作る(リポジトリの変更とタスクの変更イベント作成編)

Twitter ToDoサービス(TwoDo)を作る連載6回目。
前回までで、アプリケーションの各種設定を外部ファイル化し、テンプレートエンジンを使ってUIを作ってみました。今回は、UIからのイベントを処理する部分を作成したいと思います。β版ですがとりあえず今回でいったん作ったものを公開します。

リポジトリを変更する

前回の記事をアップした際、 id:xuwei さんよりコメント*1scala-toolsのリポジトリがなくなったとのご指摘を受けました。元記事を確認してみると、Maven Centralに統合されて色んなライブラリを使うのが楽になってるようです。元記事からリポジトリ内のライブラリを確認してみると、casbahは含まれていましたがSalatは含まれていませんでした。というわけで、casbahのリポジトリの参照先を変更しようと思います。
とは言っても、heroku側のリポジトリ参照先がどうなってるのか微妙によくわかっていないので、探ってみました。色々とググってみて行き着いたのが以下のページで、herokuのリポジトリのようです。

heroku/heroku-buildpack-scala · GitHub

ここを確認すると、どうやらherokuへのpush時のリポジトリは、デフォルトではプロキシ経由でつながっている模様。プロキシなしのリポジトリMaven Centraにつながるようなので、プロキシ経由でもそうなのだと仮定して進めることにします。
まず、前々回追加した以下のリポジトリへの参照を削除します。その結果、build.sbtのresolvers設定は以下のようになります。

resolvers += "repo.novus for salat build release" at "http://repo.novus.com/releases/"

resolvers += "repo.novus for salat build snapshot" at "http://repo.novus.com/snapshots/"

また、casbahのバージョン指定を以下のように変更します(こちらに関しても id:xuwei さんより指摘です)。

"com.mongodb.casbah" %% "casbah" % "2.1.5-1",

これで、ビルドしてみましょう*2。ローカル環境では上手く行きます。続いて、herokuへのpushも成功しました。というわけで、先ほどの過程は正しかったようです。

これで、リポジトリの参照設定の変更が終わりました。Maven Centralに統合されたおかげでリポジトリの参照設定がだいぶすっきりするようになりました。こういう部分の変更は色々と影響範囲が大きいものですが、設定周りがすっきりするのはありがたいですね。

チェック時のイベントを実装する(設計)

さて、リポジトリの設定を変更し終わったので、UIからのイベントを受けてタスクの状態を変更する処理を実装します。色々とUIを作ったり、チェックした後にポストしたりとかもあんまり格好よくないので、Ajaxを使うことにします。処理の流れは以下になります。

  1. チェックボックスをクリック
  2. タスクの状況とIDをサーバに非同期でPOST
  3. サーバ側でリクエストを受けて処理結果を返す
  4. エラーだったらメッセージを出す

コントロールがチェックボックスで、処理に異常が発生しなければ画面の状態を操作する必要がありません。というわけで、失敗した場合にメッセージを出してあげるくらいで、DOM操作はしないことにします。釣った魚にはえさを与えない感じの仕様です。

チェック時のイベントを実装する(サーバサイド)

そういうわけで受け(違う)の人の実装です。
まずは、受けるリクエストの形を決めます。今はやりのかっこいい感じにしたいですね。というわけでRESTfulな感じのリクエストにしたいと思います。以下のように想定してみました。

POST /changetaskstatus/ツイートのID/タスクの状態(true/false)

ツイートのIDは前回のテンプレートに渡していますので、画面から取得することができます。タスクの状態は、チェックボックスの状態で得られます。URLの構成自体に問題はなさそうです。
というわけで、サーバ側の処理を実装しようと思います。

   // 認証部分は省略(前回より少し変更しています。Githubのソースを公開するのでそちらでご確認を)
   case POST(Path(Seg("changetaskstatus" :: tweetId :: status :: Nil))) => {
     val taskUpdatedUserData = userData.updateUserTasks(
       userData.userTaskList.map(
         userTask =>
           if (userTask.tweetId == tweetId.toLong)
             SimpleTwoDoTask(
               userTask.tweetId,
               userTask.tweetStatus,
               status.toBoolean
             )
           else userTask
       )
     )

     try {
       updateUser(taskUpdatedUserData)
       Ok ~> JsonContent ~> ResponseString("""{"result": true}""")
     } catch {
       case ex: Exception =>
         Ok ~> JsonContent ~> ResponseString("""{"result": false, "errmsg": %str}""".format(ex.getMessage))
     }
   }

URLで指定されたタスクIDと状況を更新して、成功したらJSONの形で結果を返します。失敗した場合はエラーメッセージと一緒に返します。
タスクが一つだけなのにmap関数を使ってたり、微妙にいくつか気に入っていないところがあるんですが……。とりあえず、今の私の限界です。

チェック時のイベントを実装する(クライアントサイド)

続いて攻め(違う)の人の実装です。
基本的にはjQueryを使ってPOSTするだけです。以下のような関数を定義します。

function changeTaskStatus(twId, taskStatus) {
    var urlstr = "/changetaskstatus/" + twId + "/" + taskStatus
    $.post(
        urlstr,
        {},
        function(json) {
            if(json.result == false) {
                alert(json.errmsg);
            }
            return;
        },
        "json"
    );
}

コールバック関数で、受け取った結果がfalseであればダイアログでメッセージを出すようにしています。
これをチェックボックスのonclickイベントに設定します。

                %dt.tasklist
                    - for(tweet:com.simpletwodo.mongodbutil.SimpleTwoDoTask <- userData.userTaskList)
                        - import tweet._
                        %p.taskdetail
                            %input.checkbox{:type => "checkbox", :id => {tweetId.toString}, :checked => {taskStatus}, "onclick" => "changeTaskStatus(this.id, this.checked);"}
                                %label.checkbox{"for" => {tweetId.toString}} #{tweetStatus}

ところで、上記関数、jsファイルにしてlinkタグで読み込ませていたんですが、jQueryのリンクよりも後にすると認識してくれませんでした。linkタグの順番って影響するんでしょうかね?

まとめ

今回、全6回にわたってUnfilteredを使ってToDoサービスを作ってみましたが、微妙に無理をして作ってる感じがしましたね。セッション管理も独自実装が必要ですし、マッピングとアクションが結びついてる感じもこの規模だと気になりませんが、もう少し大きなものを作ることを考えるともう少し切り離して扱いたいところです。
ただ、今回実装したイベント処理みたいなものは比較的作りやすい感じがしたので、APIサーバみたいなものを実装したり比較的小さなサービスをあまり手間をかけずに実装するのには向いているような印象を受けました。Unfilteredで作るもののコードがScalaっぽい感じになりやすいのは好感が持てました。

というわけで、かなりβ版*3ですが、とりあえずサービスを実装することができました。後は内容を育てていくフェーズだと思いますので、いったん連載は終了します。使い勝手のために変更するときは、TwoDoタグで公開しますのでそちらを確認して下さい。

サービスのURL:http://simpletwodo.herokuapp.com/
Githubhttps://github.com/Shinsuke-Abe/Simple-Two-Do

*1:http://d.hatena.ne.jp/mao_instantlife/20120304/1330869743#c1330883314 を参照。

*2:念のため、.ivy2ディレクトリを削除してキャッシュを効かないようにしてからビルドしました

*3:Casbahが利用しているドライバが古いため、MongoURIのパースがかなりいい加減で接続できない部分を借り対処しています。MongoURI がいい加減すぎワロエナイ | イトウ アスカ Blogを参照