IBM®
本文へジャンプ
    Japan [変更]    ご利用条件
 
 
検索範囲検索:    
    ホーム    製品    サービス & ソリューション    サポート & ダウンロード    マイアカウント    
skip to main content

developerWorks Japan  >  Web development | Sample IT projects  >

Ruby テンプレート機能を Project Zero アプリケーションに追加する

RHTML ファイルにより Ruby サポートを拡張し、動的ユーザー・インターフェースを作成する方法

developerWorks
ページオプション

JavaScript を要するドキュメントオプションは表示されません

サンプルコード

原文はこちら

原文はこちら


レベル: 中級

Dan Jemiolo (danjemiolo@us.ibm.com), Advisory Software Engineer, IBM 

2008年 1月 22日

Ruby ユーザーの皆さんにお知らせです。Project Zero アプリケーションを作成するときに Groovy ユーザーと PHP ユーザーができることを、今や Ruby ユーザーでもできるようになりました。前回の記事では、Ruby スクリプト言語をサポートするために Project Zero を拡張する方法を説明しました。そのなかで作成したコードは、Ruby ユーザーが自分たちのスクリプト作成技術を Zero プラットフォームに適用し、Zero ならではのプログラミング・モデルを利用できるようにするためのものですが、当然スクリプトを作成することだけが Ruby を使用してアプリケーションを作成する方法ではありません。Ruby on Rails フレームワークを使用するプログラマーは、JSP および PHP に似た HTML テンプレートにも Ruby を組み合わせています。RHTML ファイルと呼ばれるこれらのテンプレートは、動的ユーザー・インターフェースを作成する際に非常に役に立ちます。そこで、この記事では Ruby サポートを拡張して RHTML ファイルを組み込む方法を紹介します。Zero アプリケーションを作成するときに Groovy ユーザーと PHP ユーザーができるすべてのことを Ruby ユーザーにも可能にする方法を学んでください。

始める前に

この記事では、読者が Project Zero M2 をダウンロード済みであること、そして初心者向けチュートリアルの手順を完了しているか、あるいは単純なアプリケーションを自分自身で作成した経験があることを前提とします。また、この記事より前に掲載された「Add Ruby scripting to your Project Zero applications」を読んで、JSP、PHP、RHTML などのサーバーサイドのテンプレート言語の背後にある概念を理解していることも必要です。

Project Zero コミュニティー
Project Zero の Web サイトをざっと見て、Project Zero が最近の Web アプリケーションに強力な (しかも極めて単純な) 開発および実行プラットフォームをどのように提供するかを確かめてください。コミュニティーではプロジェクト開発についての議論、開発者の支援を活発に行っています。あなたの意見もぜひお聞かせください。

はじめに

この記事の前に掲載された「Add Ruby scripting to your Project Zero applications」では、Ruby スクリプト言語をサポートするように Project Zero を拡張する方法を説明しましたが、このソリューションは Ruby on Rails 開発者が Ruby を使ってアプリケーションを作成するすべての方法をカバーしているわけではありません。Rails フレームワークには、JSP や PHP と同じような発想で、Ruby コードを HTML テンプレートに組み合わせる方法があります。RHTML ファイルと呼ばれるこれらのテンプレートは、動的なユーザー・インターフェースを作成する際に非常に役に立ち、プログラマーは (J2EEサーブレットでの場合のように) print 文を使用しなくても HTML を作成できるようになります。Zero ではすでに、ユーザーが Groovy と PHP (.gt および .php ファイル) を使ってテンプレートを作成できるようになっているので、Ruby にもこれを適用しない手はありません。

RHTML プロセッサーを作成するためのストラテジー

Zero を対象とした RHTML プロセッサーの実装方法を決定するときに定める主な目標は、RHTML パーサーおよびインタープリターを一から作成する手間を省くことです。Ruby テンプレート機能は素晴らしい機能ですが、大規模なライブラリーを導入してコンパイラー・コードを作成し、コンパイラー・コードのバグがなくなるまで大勢のユーザーがテストに時間をかけるのに値するほど素晴らしいというわけではありません。以前の記事では、Ruby スクリプトを使ってスクリプト実行作業のすべてを JRuby に委譲することに成功しました。それと同じように、RHTML ファイルに組み込まれたコードを JRuby に処理させることができれば、専用の Ruby インタープリターを作成する必要はなくなります。

ここで問題となるのは当然のことながら、JRuby が実行できるのは有効な Ruby 構文を持つ Ruby スクリプトだけで、RHTML ファイルは実行できないという点です。RHTML ファイルに組み込まれた Ruby コードを JRuby によって実行し、残りのコンテンツ (従来の HTML) は変更せずにそのままクライアントに送信するにはどうしたらよいのでしょうか。また、どうにか Ruby コードを抽出して JRuby に渡すことができたとしても、コードの出力を静的 HTML に統合する方法を考えなければなりません。ファイル全体に散在する Ruby コードのさまざまなスニペットはすべて (互いの変数にアクセス可能な) 同じスコープで実行されるように意図されているという点を考えると、コードをマークアップから分離して JRuby に渡すだけでは事が足りないことは明らかです。ここでは、それよりも賢い方法が必要です。

その答えは、JSP コンパイラーが JSP ファイルを J2EE サーブレット・クラスに変換するように、コードとマークアップを含めた RHTML ファイル全体を Ruby スクリプトに変換するという方法です。一旦テンプレートが有効な Ruby スクリプトになれば、元の形を認識できるインタープリターを使わなくても、そのテンプレートを JRuby に渡すことができます。以降のセクションでは、RHTML 構文の詳細と RHTML プロセッサーの中心となる変換プロセスについて説明します。

RHTML 構文について

RHTML ファイルとは、Ruby コードが特殊な <% および %> タグで囲まれて埋め込まれている HTML ファイルのことです。この特殊なタグのなかに含まれるすべてのものは、ページがクライアントに返される前にサーバーで処理されるので、元の Ruby コードがエンド・ユーザーの目に触れることはありません。Ruby コードには少なくとも 1 つの完全な Ruby 文があり、各グループ (<% ... %>) に含まれる文は前のグループで実行された文に影響されます。コード・スニペットは複数のグループに分けることができるため、プログラマーは print 文を使って HTML を生成する必要に迫られることがなく、コードとマークアップを明白に分離することができます。リスト 1 に RHTML コンテンツの例を記載します。


リスト 1. Ruby 文が組み込まれたサンプル RHTML ファイル
                
    
    <html>
        
      <body>
        <p>
        Here is a list of numbers 1 - 10:
        <%
          (1..10).each do |n| 
            print n, ' '
          end
        %>
        </p>
      </body>
    
    </html>
      

Ruby コードを HTML に組み込む際に考慮しなければならない特殊な事例が 1 つあります。それは、開発者が単一の値や単純な式の結果を挿入したいだけの場合には、開始タグを <%= に変更できるということです。リスト 2 に、完全な文を使わずに単純な値をページに組み込む例を示します。


リスト 2. 式が組み込まれたサンプル RHTML ファイル
                
      
    <html>
        
      <body>
        <p>
        The sum of 3 and 4 is <%= 3 + 4 %>
        </p>
      </body>
    
    </html>
      

有効な Ruby スクリプトの生成

リスト 1 やリスト 2 のようなファイルから Ruby スクリプトを生成するには、埋め込まれているコードと併せて静的 HTML を表す Ruby 文を作成しなければなりません。Ruby 以外 (HTML、JavaScript など) のすべてのテキストは、文字列リテラルを出力する print 文に変換してください。すべての Ruby コードは必要に応じて print 文 (HTML の場合) を使ってそのままコピーします。特殊な埋め込み値 (<%= ... %>) には、タグに囲まれた Ruby 式を出力する print 文を作成する必要があります。リスト 3 は、リスト 2 のファイルから生成したスクリプトです。


リスト 3. RHTML ファイルに相当する Ruby スクリプト
                
    
    print "<html><body><p>The sum of 3 and 4 is "
    print 3 + 4
    print "</p></body></html>"
      

ご覧のように、このスクリプトは概念的には単純です。スクリプトの大部分を占めるのは print 文で、必要に応じて他の Ruby 文が混じっています。リスト 3 のコードを最適なものにする方法として考えられるのは、生成された HTML を元のファイルのように見せるために改行文字を使うことです。そうすれば、開発者にとって RHTML テンプレートのデバッグが遥かに簡単になります。ここでの実装では、このようにして最適なコードにすることにします。

Ruby スクリプトの保存

RHTML プロセッサーを計画するときに最後に決めなければならないことは、生成したスクリプトをどう扱うかです。最も簡単なのは、元の RHTML ファイルと簡単に対応付けられる名前を付けたファイルを使ってスクリプトをディスクに書き込むことでしょう。例えば元のファイルの名前が my-file.rhtml だとすると、生成したスクリプトを my-file.rhtml.rb という名前にします (.rb は Ruby スクリプトの一般的な拡張子です)。こうすると、プロセッサーは以下のアルゴリズムを使ってスクリプトを生成し、実行することができます。

  1. スクリプトのファイル名として RHTML ファイルを表す名前を作成します。
  2. 既存のスクリプトがあり、RHTML ファイルが最近変更されていない場合には、生成をスキップします。
  3. そうでない場合にはスクリプトを生成し、ステップ 1 で作成した名前を使ってディスクに保存します。
  4. スクリプトを実行します。

ステップ 2 には 2 つの目的があります。1 つは、同じスクリプトが何度も繰り返し生成されることを防ぐための最適化です。さらに、開発者の便宜を図るという目的もあります。開発者が RHTML ファイルに加えた変更はすぐに次の要求に現れることになるためです (開発時間を大幅に節約できます)。

RHTML プロセッサーの実装

RHTML プロセッサーを実装するためのストラテジーは整ったので、今度はこのストラテジーをオリジナルの Ruby プロセッサーに関連付ける必要があります。前回の記事での場合と同じように、記事の説明に従って段階的にコードをサンプル・プロジェクトに追加していくことも、あるいは完成したプロジェクトを「ダウンロード」セクションからダウンロードすることもできます。説明に従ってコードを追加する場合は、前回の記事で使ったサンプル・プロジェクトを使ってください。このサンプルには、通常の Ruby サポートがすでに用意されているからです。

現在、サンプル・プロジェクトでは JRubyInterpreter というクラスによって Ruby をサポートする処理が扱われます。このクラスは Zero Core の Interpreter インターフェースを実装し、JRuby インタープリターを使って HTTP リクエストを実行します。RHTML ファイルを処理するには、まず Ruby スクリプトを生成してから JRuby を呼び出す別の Interpreter クラスを作成しなければなりません。そこで、JRubyInterpreter クラスを継承して、JRuby 関連のコードを作成しなおさなくても済むようにします。リスト 4 に、このサンプル・プロジェクトに必要な RHTMLInterpreter クラスの実装を示します。


リスト 4. RHTMLInterpreter クラス
                
    
    package zero.scripting.ruby;

    import java.io.File;

    import zero.core.context.GlobalContext;
    import zero.core.events.HandlerInfo;
    import zero.scripting.html.HTMLCompiler;

    public class RHTMLInterpreter extends JRubyInterpreter 
    {
        public void invoke(HandlerInfo handlerInfo) 
        {
            try 
            {
                File scriptFile = new File(handlerInfo.handler);
                HTMLCompiler compiler = new HTMLCompiler();
                
                //
                // convert RHTML file to a Ruby script file
                //
                File compiledFile = compiler.compile(scriptFile, ".rb");
                
                super.invoke(compiledFile);
            } 
            
            catch (Throwable error) 
            {
                GlobalContext.put("/request/status", 500);
                GlobalContext.put("/request/error", error);
            }
        }
    }
      

RHTMLInterpreter.invoke() メソッドは、RHTML から Ruby に変換するという大変な作業を HTMLCompiler クラスで処理します。このクラスの実際の役目は、親クラスに実行を委任する前に確実に変換が行われるようにすることだけに過ぎません。HTMLCompiler クラスは大規模ですが、最も重要な部分は上記で使用している compile() メソッドです。リスト 5 に、compile() メソッドのコードを抜粋して示します。コードの残りの部分については、「ダウンロード」セクションを参照してください。


リスト 5. HTMLCompiler.compile() メソッド
                
    
    public File compile(File htmlFile, String extension)
        throws IOException, HTMLCompilerException
    {
        File compiledFile = getCompiledFile(htmlFile, extension);
        
        //
        // make sure we don't re-compile files that haven't changed
        //
        if (compiledFile.exists() && 
            compiledFile.lastModified() > htmlFile.lastModified())
            return compiledFile;
        
        String html = getHTML(htmlFile);
        
        int current = 0;
        int codeStart = html.indexOf(_EMBEDDED_START_TOKEN, current);
        
        FileWriter writer = new FileWriter(compiledFile);
        
        //
        // look for the code fragments and add them to the script as 
        // regular code statements. all HTML gets converted into 
        // print statements
        //
        while (codeStart >= 0)
        {
            //
            // record all HTML preceding next code fragment
            //
            writePrintStatements(writer, html.substring(current, codeStart));
            
            int codeEnd = html.indexOf(_EMBEDDED_END_TOKEN, codeStart);
            
            if (codeEnd < 0)
                throw new HTMLCompilerException("No matching end token found.");
            
            int stmtStart = codeStart + _EMBEDDED_START_TOKEN.length();
            String code = html.substring(stmtStart, codeEnd);
            
            //
            // special case - for <%= expression %> fragments, we just 
            // want to insert the expression's value, not make it a 
            // separate statement
            //
            if (html.charAt(stmtStart) == _EMBEDDED_VALUE_TOKEN)
                writeValuePrintStatement(writer, code.substring(1));
            
            //
            // normal case - code recorded as it was written
            //
            else
            {
                writer.write(code);
                writer.write('\n');
            }
            
            current = codeEnd + _EMBEDDED_END_TOKEN.length();
            codeStart = html.indexOf(_EMBEDDED_START_TOKEN, current);
        }
        
        //
        // take care of HTML after last code fragment, if any
        //
        if (current < html.length())
            writePrintStatements(writer, html.substring(current));
        
        writer.flush();
        writer.close();
        
        return compiledFile;
    }
      

compile() メソッドには、前述のアルゴリズムのステップ 2 で説明した最適化が含まれていることに注目してください。つまり、最初の if 文によって、実際に必要なスクリプトだけを生成するようにしています。また、writePrintStatements() メソッドのコードを見てみると、元のファイルにあるすべての改行に対応する改行文字が含まれていることにも気付くはずです。そのため、生成された HTML はほとんど同じような表示になります。

Zero 構成ファイルの更新

コードの準備は完了しましたが、まだ 1 つ、完了しなければならないステップが残っています。それは、Ruby インタープリターにも行っていたように、事前に RHTML インタープリターを Zero Core に登録し、.rhtml ファイルに対するリクエストの処理方法を Zero Core が認識するようにするというステップです。リスト 6 のスタンザをアプリケーションの /config/zero.config ファイルに追加すると正式に登録されます。


リスト 6. アプリケーションの /config/zero.config ファイルへの追加内容
                
    
    [/app/interpreters]
    .rhtml=zero.scripting.ruby.RHTMLInterpreter
      

Ruby テンプレートのテスト

RHTML サポートが準備できたところで、早速、RHTML ファイルをアプリケーションに追加し、Web ブラウザーから呼び出してテストしてみましょう。Ruby および RHTML の完全な Zero との統合を示すため、サンプル・テンプレートでは Zero の GlobalContext API を使ってリクエストのデータを読み取り、レスポンスを作成します。リスト 7 の RHTML ファイルでは、グローバル・コンテキストの全内容を HTML レスポンスのなかでシリアライズすることで、表示しています。


リスト 7. コードのテスト用 RHTML ファイル
                
    
    <%require "java"%>
    <%include_class "zero.core.context.GlobalContext"%>
    <%include_class "zero.core.context.SimpleFormatter"%>
    <html>
      <head>
      <title>Sample RHTML Page</title>
      </head>
      <body>
        <p>The current contents of the global context are:
          <pre>
            <%
              formatter = SimpleFormatter.new
              GlobalContext.dump formatter
              print formatter
            %>
          </pre>
        </p>
      </body>
    </html>
      

リスト 7 のマークアップをアプリケーションの /public ディレクトリーにある test-ruby-and-markup.rhtml ファイルに追加します。次に zero run コマンドを実行して Zero アプリケーションを起動し、お好みの Web ブラウザーで http://localhost:8080/test-ruby-and-markup.rhtml にアクセスすることで、テストしてみてください。その結果、単純な HTML ページにグローバル・テキストの内容が表示されるはずです。図 1. に、このページが Mozilla Firefox に表示された場合のスクリーンショットを示します。


図 1. Ruby テンプレートで作成された HTML ページのスクリーンショット

まとめ

Ruby により Zero アプリケーションを実装できるのは素晴らしいことですが、Rails のテンプレート機能を使えることは、さらに素晴らしいことです。この記事で作成したコードを使用すると、Groovy ユーザーと PHP ユーザーが Zero アプリケーションを作成する際に使える技が Ruby ユーザーにも可能になります。コードが埋め込まれた HTML を実行可能なスクリプトに変換するために用いたストラテジーは極めて一般的なものなので、さらに強力なテンプレート機能や新しい言語を必要に応じてプラットフォームに追加するときにも使うことができます。





上に戻る


ダウンロード

内容ファイル名サイズダウンロード形式
Sample JRuby integration code and test fileszero.scripting.zip8419 KBHTTP
ダウンロード形式について


参考文献

学ぶために

製品や技術を入手するために
  • Project Zero M2 をダウンロードして、この記事で説明したベスト・プラクティスを早速実践してください。

  • JRuby 1.0 をダウンロードして、この記事に記載してコードを実行してください。


議論するために
  • projectzero.org のディスカッション・フォーラムに参加してください。

  • developerWorks blogs: developerWorks コミュニティーに参加してください。


著者について

Dan Jemiolo はノースカロライナ州リサーチ・トライアングル・パークにある IBM の Project Zero チームの Advisory Software Engineer です。彼は現在、Zero プラットフォームの再利用可能コンポーネントとそのサービス・カタログに取り組んでいます。以前の職務には Apache Muse 2.0 の設計および開発、そして OASIS Web サービス標準化団体への参加などが含まれます。彼は 3 年前に Rensselaer Polytechnic Institute でコンピューター・サイエンスの修士号を取得した後、IBM に入社しました。




記事の評価


サイト改善のため、ご意見をお寄せください。こちらのフォームからお願いいたします。



 


 


不充分・不完全である大変素晴らしい
 


この記事を共有する

del.icio.us del.icio.us newsing newsing FC2ブックマーク FC2ブックマーク
Choix! Choix! ニフティクリップ ニフティクリップ Yahoo!ブックマーク Yahoo!ブックマーク
MM/memo MM/memo CZブックマーク CZブックマーク livedoorクリップ livedoorクリップ
はてなブックマーク はてなブックマーク Buzzurl(バザール) Buzzurl(バザール)




上に戻る


Java およびすべての Java 関連の商標およびロゴは、Sun Microsystems, Inc. の米国およびその他の国における商標です。 他の会社名、製品名およびサービス名等はそれぞれ各社の商標です。

    日本IBMについて プライバシー お問い合わせ