uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

PHPのようにGrailsを使う方法

GrailsアプリケーションではGSPのビューは以下の方法で表示できます(他にもあるかも)。

  • コントローラーアクションの結果として表示
    • renderで指定したビュー
    • コントローラ名とアクション名から定まるデフォルトのビュー
  • ビューを直接表示する
    • URLMappingでURLパターンからビューを指定する

しかし、「ビューだけ」の、GSPの集合だけで機能するサイトは作れません。JSPで言うと、JSPだけのServlet無しのWebサイト、あるいはPHPの埋め込みページだけで構成するようなサイトが作れないのです。

そうなのはおそらく意図的であり、それがWeb開発の黎明期にアンチパターンとしてみなされてきたので排除されているのでしょう。上記のような制約「GSPはコントローラからの画面遷移を経て表示される」があることのメリットは、GSP内で使用できるデータの由来を明確化できること、そして、画面遷移のリンク情報をページの中にばらまくのではなく、コントローラのコードで指定することでしょう。

ただ、そうだとしても、以下のような開発手順をとりたいときがあります。

  • 開発時に、まずHTMLでのモックアップがある(デザイナーや企画者が作成するなど)
  • それをGSPに一括変換する
  • 適宜以下を実施する
    • FORMの部分をGSPタグ<g:form>でおきかえる
    • レイアウトをSiteMeshでおきかえる
    • 静的リソースはアセットにして…

現状のGrailsでは、上記の手順にはなりません。ページの持つ機能をコントローラ・アクションの階層に分割し、そしてコントローラアクションの結果としてのページ群に対応させ…、というトップダウンの機能設計をせざるを得ず、直接画面確認に行けないのです。

本記事では、この問題を解決するために、ページ間遷移について個別のコントローラ無しGSPの集合としてアプリを作る、つまり「PHPのようにGrailsを使う」方法を紹介します。

DispatchController

showPageというアクションを1つだけ持った以下のコントローラDispatchControllerを用意します。

package common

class DispatchController {

    /** grails-app/view/pages配下に配置された、コントローラに所属しない
     * (コントローラのアクション名と一致していることを期待せず、リンク
     * をたどることやメニュー選択の結果として表示されることを想定して作
     * 成されている)ページを表示する。
     */

    def showPage() {
        render view:"/pages/${params.pageName}"
    }
   
}

URLMappingに以下を追加します。

        "/pages/$pageName"(controller: "dispatch", action: "showPage")
        "/"{
            controller = "dispatch"
            action = "showPage"
            pageName = "index"
        }


これで準備は終わり。あとは

配下に、たとえば

というGSP(htmlの拡張子をgspにリネーム)を置けば、

      <a href="${request.contextPath}/pages/a">..</a>

というようなリンクで相互にGSPビューを表示できるようになります。views/pages/index.gspがトップページになります。
あもちろん、<g:link>とか使っても良いです。

まとめ

コントローラ・アクションの編成の方法論というのは、Grails初学者にとっては難しいところです。

Scaffoldではドメイン/アクションをベースにしたビューを生成してくれますが、Webサイトというものが一般には「DBテーブルエディタ」ではない以上、この構造は必ずしも一般的ではなく、参考にならないときもあります。

テーブル単位のCRUDに限らない、コントローラ・アクションの階層をどう編成すべきか。この問いに、なんとか解を出さないと、index.gsp以外の1ページを表示することすらままならないかもしれないわけです。デフォルトのindex.gspのようにページごとにURLMapping追加していってもいいんですが、面倒だし不毛ですよね。

多数のページから呼ばれ得る多数のコントローラの関係があるとき、むしろコントローラ/アクションの階層をページ集合とは独立した機能群として設計する方がやりやすいときもあるでしょう。

このPHPぽいアプローチならば、動くサイトを拡張しながら、コントローラを都度考えていくことができます。また、それとは別に、Grailsの初心者にとってのとっつきは、はるかに良くなると思います。

もちろん、この技法と通常のコントローラベースのアプローチを組合せても全く問題ありません。

問題もありえます。この方法だけで、大規模サイトとか業務システムのサイトの全体をつくるべきではないでしょう。単機能の1ページサイトでも不要でしょう。でも、ヘルプページとか、社長のご挨拶ページとか、サイトマップとか、そういうページを含むサイト全体を作るために、この技法を織り交ぜて適用するのが有用なことがあるでしょう。

ということで、選択肢の一つとして心にとめておいておくと便利なのではないかと思い、紹介させてもらいました。

「えせMVC論争」についてこそこそと一言言っておくか

某所でRailsの「えせMVC」の論争が話題のようです。へたれなので、トラバは打ちませんが一言いわささしていただきます。

  • MVCパターン != MVC2。これ重要。Java界では誰もMVCなんていってません*1。むしろ「2」をつけることで、「本来のMVCそのものじゃない」ということを言ってます。混同するべきじゃない。「MVC2は純粋なMVCじゃない」と怒るのはありえない。純粋じゃないって最初から言ってるんだから。MVC2はMVCに対して比喩的ないいかたです。だって、M->Vで更新通知がオブザーバーパターンになってないでしょ。MVCでありうるわけがない*2。これに比べたらロジックのおき場所がどうの整合性がどうのとかは些細な問題です。RailsGrailsもWebのアプリケーションアーキテクチャとかについて、「えせMVC」か「えせじゃないMVC」かでいうと、間違いなく「えせ」*3
  • 「モデル、ビュー、コントローラという用語を使うから(狭義の/本来の)MVCだと思っちゃったじゃないか〜」という主張は、一理ありますが、じゃあ代わりに何を使えば良いかは問題。ちなみにGrailsだとRailsのModelに相当するのは「ドメインクラス」です。
  • Railsフレームワークとしてサービス層をサポートしてないのが問題。必要なものが存在していないことは人間を不機嫌にさせる。Life is beautifulの方がGrails使ってたら、もしかしたらこんなことにはならんかっとったかもしれませんね。複数のデータ間にまたがったデータ整合性保証を扱うロジックをおくべき場所が明示的にどこかにあるのが健康的なのだと私は思います。

(追記。「本来のMVC」って、SmalltalkのMVCのことだと思っていいんですよね???付随する議論を見てると、MVCという言葉が指している概念が、人によって違っている気がしてきた。「MVC=WebアプリアーキテクチャのMVC2」だと本気で思ってる人がいるとしたら、それは考えをただしたほうが良いでしょう。Swingのデータモデルとかテーブルモデルの仕組みのほうが近いんですよ。Swingのデータモデルと、Webアプリの永続化entityがぜんぜん違うのは、明らかだと思うのですよ。これら両方あわせて「MVCパターン」と言うとしたら、何がなにやら・・・。)

*1:言ってる人が多々いるような気がしてきた。MVCについて[http://ja.wikipedia.org/wiki/Model_View_Controller:title=日本語版Wikipedia]の説明も変だな。[http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller:title=英語版]はまだまし。

*2:JSFとかseasideとかliftとかWicketとかリスナモデルにはなってるんだっけかな?V->Mの方向だけだったかな?

*3:でもえせだから悪いとは思いません。えせ上等。