japan.internet.comThe Internet & IT Network
RSS
  • ニュース
  • コラム
  • リサーチ
  • ヘッドライン
  • 特集
  • ブログ
  • プレスリリース
  • 専門チャンネル
  • イベント
  • ランキング
  • ニュースメール
2008年9月6日
文字サイズ文字サイズ小文字サイズ中文字サイズ大
デベロッパー2007年9月25日 13:00

10分でできる初めてのRubyプログラム

海外海外internet.com発の記事
  • このエントリーを含むはてなブックマーク
  • この記事をクリップ!
  • Buzzurlにブックマーク
  • Yahoo!ブックマークに登録
  • newsing it!

はじめに

 Rubyの多様性や強力さについては既にご存知のことでしょう。Rubyをさらに深く知るためには、その背後にある繊細かつ重要な概念を理解しておきたいところです。この記事では、小さな実用的なRubyプログラムを作成します。Rubyは基本的にクラスとオブジェクトを使用するオブジェクト指向の言語であり、動作をカプセル化したクラスを簡単に作成できます。この記事では、最初に簡易版のプログラムを作成し、そのプログラムを拡張していきます。読み進むにつれて、Rubyの仕組みをより深く理解できるようになるでしょう。

 今回のサンプルプログラムには2つの目的があります。

  1. Rubyの機能を紹介する。
  2. 実用的に処理を進める。

 Rubyに関する知識が適度にある読者がこのプログラムを作成するならば、この記事のタイトルにあるように、おそらく10分以上の時間がかかることはないでしょう。Rubyの動作について学習し、どのようなコードが有効であるかを理解できれば、実用的なユーティリティを簡単に作り出すことができます。もちろん、Rubyを扱うのがまったく初めての場合は、このようなプログラムを作成するのに10分以上の時間がかかります。

必要なもの

 このチュートリアルは、Rubyの最新バージョンがインストールされ、コードエディタが使用できることを前提としています。Rubyでプログラミングするのに、特別なIDEは必要ありません。Vim、Emacs、TextMateで十分です。NetBeansとEclipseも使用できます。

目標: ファイル起動の簡便化

 Rubyは本来、テキストベースのコマンドライン指向の言語です。複数のWebアプリケーションフレームワークに加えて、いくつかのGUIライブラリも使用できますが、RubyでのGUI開発はこの記事の範囲を超えています。ここでの目標は、コマンドラインから動作するものを作成することです。

 サンプルプログラムの役割は、ファイル起動の簡便化です。指定されたテキストファイル(おそらくRubyソースコードファイル)を、コマンドラインから関連アプリケーションで起動する手段を作成するものとします。さらに、ファイルの種類とアプリケーションの関連付けを追跡することなくこの動作を実現したいと思います。ご存知のようにこの動作はWindowsで既に実現されていますが、今回のランチャープログラムでは、この単純な動作に追加の機能を実装します。

バージョン0のLauncherコード

 最初に、ほんの数行から成るRubyファイルを作成します。Rubyファイルの拡張子は「.rb」であり、ファイルの冒頭には、Rubyインタプリタのパスを定義する非常に重要な行を記述します。ファイルの名前は「launcher.rb」とします。

#!/usr/local/bin/ruby

# Example application to demonstrate some basic Ruby features
# This code loads a given file into an associated application

class Launcher
end

 シャープ記号(#)で行レベルのコメントを開始できることに注意してください。#の右側に続くすべての文字列はインタプリタの処理対象外になります。この他に、複数のコード行をコメント化する方法もあります。Rubyのクラス名は大文字で始まります。クラスは定数であり、Rubyのすべての定数は大文字で始まります(Ruby構文の詳細については、「Ruby ― A Diamond of a Programming Language?」のPart 1およびPart 2を参照してください)。

 このコードは何の処理も行っていないように見えますが、実行は可能です。実際に試してみれば、コピーが実行されている様子を確認できるでしょう。Rubyスクリプトの実行方法は簡単です。次に示すように、rubyインタプリタを呼び出し、ファイルの名前を渡すだけです。

$ ruby launcher.rb
UnixおよびWindowsでlauncher.rbを実行する方法
 Unixシステムでは、ファイルを実行ファイルとして設定し、直接呼び出すことができます。
$ chmod u+x launcher.rb
$ ./launcher.rb
 この点に関しては、いわゆるワンクリックRubyインストーラを使用しているWindowsユーザーの方が一歩進んでいます。文字どおりワンクリックというわけにはいきませんが、最終的には.rbファイルに対する関連付けが設定されるからです。このため、Windowsユーザーは次の方法でアプリケーションを簡単に実行できます。
C:somedir> launcher.rb
 しかし、WindowsファイルエクスプローラからRubyファイルをダブルクリックして起動したとしても、結果はすぐに消えてしまいます。コードはコマンドシェルで実行されますが、これが表示されるのはアプリケーションの実行中だけです。今回のサンプルでは、デモンストレーションの目的上、ファイルはコマンドシェルから実行するのが良いでしょう。

 ファイルを実行すると、コードに何らかのエラーがない限り、何も表示されないまま実行が終了します。何も表示されなければそれでOKです。何も表示されないからと言って、何も起きていないわけではありません。rubyインタプリタはファイルを解析し、クラス定義を検出すると、その定義を使ってオブジェクトを作成できるよう準備します。次のコードでは、前述のコードにクラス定義を追加しています。

#!/usr/local/bin/ruby

# Example application to demonstrate some basic Ruby features
# This code loads a given file into an associated application

class Launcher
end

launcher = Launcher.new

 このコードでは最初に変数(launcher)を作成します。この変数には、Launcherクラスの新しいインスタンスへの参照が割り当てられます。変数の型を宣言する必要はありません。Rubyは強力な動的型を採用しており、変数は任意の型のオブジェクトへの参照を保持できます。Rubyでは、文字列、数値、正規表現を含めて、ほとんどすべてのものがオブジェクトとして扱われます。それぞれに正式な作成メソッド(String.newなど)が用意されていますが、Rubyは常識的な判断に基づいて簡単かつ柔軟に処理しようとします。

 次に、RubyはLauncherクラスのnewを呼び出すことによってオブジェクトインスタンスを作成します。newはクラスメソッドで、Javaのコンストラクタメソッドに似ています。もちろん、空のオブジェクトでは役に立たないので、何らかの動作を追加する必要があります。

Launcherの実装

動作の追加

 これから作成するプログラムの主な目的は、指定されたファイル名を受け取り、これを関連するアプリケーションに渡して何らかの処理を行うことです。そこで、ファイル名とアプリケーションのマッピング方法をプログラムに認識させるために、Launcherクラスのインスタンスの作成時に、何らかのマッピングを渡す必要があります。クラスメソッドnewを使ってクラスのインスタンスを作成できることは、既に示したとおりです。最初から何らかのデータセットを含んでいるインスタンスを作成するには、newメソッドに引数を渡します。これを行うには、もちろん、Launcherにコードを追加しなければなりません。

def initialize( app_map ) 
  @app_map =  app_map
end

 Rubyでメソッドを定義するには、defキーワードに続けて、メソッド名と引数リスト(必要な場合)を指定します。引数リストはわかりやすいように括弧で囲まれていますが、コードの意味が明白な場合は省略してもかまいません。

 Rubyオブジェクトには、多様な動作が組み込まれていることに注意してください。これをそのまま使用することもできれば、上書きすることもできます。

引数をnewメソッドに渡すのにinitializeメソッドを追加する理由
 「newメソッドに引数を渡したいはずなのに、どうしてinitializeというメソッドを追加するのだろう」と思っている人もいるでしょう。その理由は、Rubyがクラスからオブジェクトを作成する方法に関係しています。
 Launcherをはじめとするすべてのクラスは、Objectクラスを継承し、クラスから作成されるオブジェクトはデフォルトでinitializeメソッドを持つことが決められています。クラスメソッドnewが呼び出されると、まず、目的のオブジェクトに対してリソースが割り当てられ、次に、新しいオブジェクトのinitializeメソッドが呼び出されます。newを介して作成パラメータを提供するには、独自のinitializeメソッドを定義して、新しく作成されたインスタンス内で引数を処理する必要があります。

インスタンス変数

 initializeメソッドは、app_mapという1つの引数をとります。前述の変数と同様、メソッド引数の型も指定されていません。指定するのはメソッドが1つの引数(app_map)をとるということだけであり、メソッドの本体の中でこの引数は変数@app_mapに割り当てられます。@記号は、変数がインスタンス変数であること、つまり、このオブジェクト内のすべてのコードに対して有効であることを示しています。オブジェクト作成時にこのインスタンス変数を作成すると、コードに追加する他のメソッドでもこの変数を使用できます。

 指定されたファイルを関連アプリケーションで実行するためには、プログラムにさらにコードを追加します。

class Launcher

  def initialize( app_map )
    @app_map =  app_map
  end

  # Execute the given file using the associate app
  def run( file_name )
    application = select_app( file_name )
    system( "#{application} #{file_name}" ) 
  end

  # Given a file, look up the matching application
  def select_app( file_name )
    ftype = file_type( file_name )
    @app_map[ ftype ]
  end

  # Return the part of the file name string after the last '.'
  def file_type( file_name )
    File.extname( file_name ).gsub( /^./, '' ).downcase 
  end

end

 runメソッドはファイル名を引数として受け取り、それをselect_appに渡すことで、実行すべきアプリケーションを見つけます。次に、Rubyのsystemメソッドを使って該当アプリケーションを起動し、ファイル名を渡します。systemメソッドは、指定されたコマンドをサブシェルに送り込んでいるだけです。一方、runメソッドからファイル名を受け取ったselect_appは、file_typeを呼び出して、正規化されたファイル拡張子を取得します。そしてこれを@app_mapへのキーとして使用し、実行すべきアプリケーションを見つけます。

 file_typeはファイル名を受け取り、RubyのFileクラスのクラスメソッドを使って拡張子を取得します。extnameで返される文字列には、ピリオド(.)とファイル拡張子が含まれます。これは必要ないため、gsub(global substitute:全置換)を使ってその部分を除外します。残りの部分をdowncaseですべて小文字に変換します。

 コンパクトなコードにするために、ここでは上記のメソッドをつなげて記述しています。つまり、File.extnameから返される文字列をgsubが受け取り、gsubから返される文字列をdowncaseが受け取ります。

 ここまでのサンプルコードは、StringおよびHashであることが期待されるオブジェクトを使用してきました。しかし、本当に注意すべきは、これらのオブジェクトが特定のメッセージに適切に応答することです。このような小さなプログラムでは、メッセージとランタイム動作に基づく巧妙で強力なオブジェクトシステムの重要性は感じられないかもしれませんが、大規模なRubyプログラムを作成するときには、この点を理解していることが重要です。

オブジェクト、データ型、および動作について(ダックタイピング)

 Rubyは、オブジェクト指向プログラミングのメッセージ受け渡しモデルに従います。foo.barのようなコードがある場合、これは、fooで参照されるオブジェクトにメッセージ「bar」が渡されることを意味します。大抵の場合、fooで参照されるオブジェクトにはメソッドbarがあるため、このようなコードを目にすると、これは呼び出し側のfoobarメソッドだと考えがちです。たしかにこれは便利で一般的な考え方ですが、その裏で何が行われているかを理解することが重要です。

 オブジェクトはメッセージを受け取ると、最初に、対応するメソッドを探します。この検索は、オブジェクト自身のクラスから始まってObjectクラスに達するまで、継承階層をさかのぼる形で行われます。一致するものが見つからない場合は、メソッドmethod_missingが呼び出されます。もうおわかりかもしれませんが、method_missingには、例外の発生のみを行うデフォルト実装があります。しかし、デフォルトの初期定義を上書きできるように、method_missingも再定義できます。このメソッドは自由に再定義でき、オブジェクトに機能を追加して、任意のメッセージ要求を処理できるようにしたり、実際よりも多くのメソッドをオブジェクトに実装したりできます。

 この柔軟性はRubyの大きな魅力の1つの核を成すものですが、場合によってはトラブルの原因になるという重大な側面もあります。ご存知のように、変数の作成時やメソッド引数リストの宣言時に、データ型の宣言は行われません。しかし、データ型をチェックすることは可能です。コードでオブジェクトの型を確認し、それに応じて処理することができます。例えば、ファイル名(Stringオブジェクトなど)とファイルハンドル(Fileオブジェクトなど)のいずれかを受け取るメソッドを作成するものとします。

 ただし、Rubyのコードでは、単なる保護手段としてオブジェクトの型をチェックすることはほとんどなく、指定されたオブジェクトが特定の型であると自ら主張しない限り続行を拒否するということはありません。クラスとオブジェクトは実行時に可変なため、Rubyにおけるデータ型の概念は、基本的に、ある特定時点でのオブジェクトの動作として定義されます。データ型は、その基になるクラスではなく、オブジェクトが応答するメソッドによって定義されます。Ruby愛好者のLogan Capaldoは次のように述べています。「Rubyの世界では、何が親なのかということは問題にならない。重要なのは、何について話しているかということだけだ。」

 これを一般的に表す用語として「Duck Typing(ダックタイピング)」があります。これは、「アヒルのように歩き,アヒルのように鳴くなら,それはアヒルである」という考え方です。

バージョン0の仕上げ

 この最初のバージョンを、使える状態に仕上げます。ファイルの最後に次のコードを追加してLauncherのインスタンスを作成し、それを使ってアプリケーションを実行します。

def help
  print " 
  You must pass in the path to the file to launch.

  Usage: #{__FILE__} target_file
" 
end

if ARGV.empty?
  help
  exit
else
  app_map = {
     'html' => 'firefox',
     'rb' => 'gvim',
     'jpg' => 'gimp'
  }

  l = Launcher.new( app_map )
  target = ARGV.join( ' ' )
  l.run( target )
end

 helpメソッドは必要に応じて指示を出力します。ARGVは引数ベクトルです。組み込みのRubyオブジェクトであり、プログラムに渡されるすべてのパラメータを保持します。これが空の場合、プログラムは何も処理できないため、ヘルプを表示して終了します。そうでなければ、ハッシュオブジェクトを作成し、それを変数app_mapに割り当てます。

 { ... }という表記法は、Hashオブジェクトを作成する場合のRubyのリテラル構文です。Hash.newも使用できますが冗長です。リテラル表記では、=>を使ってハッシュキーを値にマップします。ハッシュはLauncherインスタンスにデータを割り当てるために使われますが、コマンドライン引数は1つの文字列にまとめられて変数targetに格納され、runに渡されます。

 このコードを試す前に、正しい実行プログラムを参照するように、app_mapで使用されるアプリケーション値を変更する必要があります。拡張子「rb」をテキストエディタにマップしている場合、次のようなコマンドでコードを試すことができます。

$ ruby launcher.rb launcher.rb

 ソースコードがエディタで開きます。

バージョン1への拡張:動的ロードの使用

 とりあえずバージョン0は動きました。しかし、これをさらに改善していきましょう。ファイルの種類をアプリケーションにそのまま直接マッピングするのではなく、ファイルの種類を実行ハンドラにマップします。つまり、どのアプリケーションをどんな引数で実行するかを追加のコマンドライン引数に基づいて決定するようなコードを定義します。

 例えば、Web開発を行っていて、HTMLファイルを作成した場合、ほとんどの場合はそのファイルをブラウザで表示します。そのため、プログラムがそのように動作すれば問題ありません。しかし、ときには、特定のブラウザで表示したいこともあります。今の状態では、Launcherに許可されているのは1つのアプリケーションとの関連だけです。ここで実現したいのは、例えば次のようにして「myfile.html」をOpera Webブラウザで起動したり、

$ ./launcher myfile.html opera

 次のようにしてHTMLの構文チェックを実行したりする機能です。

$ ./launcher myfile.html syntax

 そのためには、何らかの仕掛けを追加する必要があります。

起動ロジックの背後にある仕掛け

 ファイル拡張子を特定のアプリケーション名にマップしていた場所で、今度はRubyコードとの関連付けを追加します。具体的には、処理するファイルの種類ごとにカスタムコード化されたRubyクラスが必要です。

 まず、「launcherx.rb」(xは拡張を表します)という名前の新しいRubyソースファイルを「launcher.rb」と同じディレクトリに作成します。

#!/usr/local/bin/ruby

# File launcherx.rb

require 'launcher'

class Launcher

  def handler( file )
    get_handler(file) ||  build_handler(file)
  end

  def build_handler file
    handler = Class.new 
    application = select_app(file)
    eval "def handler.run 
      system( '#{application} #{file}' ) 
    end" 
    handler
  end

  def get_handler(file) 
    begin
      here  = File.expand_path( File.dirname(__FILE__ ))
      ftype =  file_type(file)
      require "#{here}/handlers/#{ftype }" 
      Object.const_get( ftype.capitalize ).new
    rescue Exception
      nil
    end
  end

  # Execute the given file using he associate app
  def run( file, args  = nil )
    handler(file).run(file, args)
  end

end

 まず注目してほしいのは、最初にrequireを呼び出してLauncherの既存の定義をロードしている点です。しかし、新しいコードでもLauncherという名前のクラスを定義しています。どうなるのでしょうか。Rubyでは、既存のクラス名と同名のクラス定義が検出されると、既存のクラスが新しいクラスで更新されます。最初のバージョンのLauncherで定義されたメソッドは引き続き存在し、追加のコードで定義されたメソッドは新たに追加されます。そして、runの場合と同様に、既存のコードと同じ名前を新しいコード内で使用すると、新しいコードが古いコードを置き換えます。要するに、最初のバージョンに出てきたのと同じコードを重ねて書く必要はありません。追加または変更するだけでかまいません。

 これはRubyのコアクラスにも当てはまります。例えば、独自のメソッドを備えたStringクラスを定義することで、デフォルトのStringの動作を拡張したり変更したりできます。文字列の変更(例えば特殊文字の置換)を頻繁に行う場合は、次のように定義することでコードを簡潔にできます。

class String
  def amp_escape
     self.gsub( '&', '&'  )
   end
end

 これにより、次のようなコードを記述できるようになります。

"This & that".amp_escape

 新しいプログラムファイルでは、この新しい動作を処理する必要があります。まずrunメソッドを変更します。この新バージョンでは、シェルコマンドを直接呼び出すのではなく、Rubyコードを呼び出すからです。また、追加の引数を渡すオプションが必要です。従って、このバージョンでは、ファイル名と引数配列(オプション)を使用します。引数配列がオプションなのは、メソッドの引数リストでデフォルト値(nil)を指定しているためです。このように事前に値が割り当てられる引数は、引数リストの最後に指定する必要があります。

 最初のバージョンでは、単にファイル拡張子を使ってハッシュからアプリケーション名を取得していたのに対して、このコードでは、handlerメソッドを使って対応するRubyクラスを作成し、指定されたファイル名を処理します。handlerメソッドは短いメソッドです。最初にget_handlerを呼び出して、一致するハンドラクラスが見つかるかどうかを確認します。||メソッドはRubyの論理ORです。get_handlerfalse(またはnil。Rubyではこれをfalseとして扱います)を返すと、||の右側のコードが呼び出されます。定義されているハンドラクラスがない場合は、コードがこれを作成します。

 新バージョンのrunでは、get_handlerが、runメッセージに応答するオブジェクトを返すことに注意してください。このため、build_handlerメソッドは、この動作に基づくクラスを定義する必要があります。これを実現するには、さまざまな方法があります。ここでは、最初にクラスClassの汎用インスタンスを作成し、問題になっている特定のファイル種類の処理方法を理解しているrunメソッドを動的に追加します。

 新しいLauncherクラスは、元のアプリケーションマップコードを保持しています。このマッピングは、特殊なRubyコードが失われた場合にファイルを処理するための代替システムとして役割を果たすものであり、新バージョンでも最初のバージョンと同じ処理が行われることを意味します。コードでは、引き続きselect_appを呼び出してデフォルトアプリケーションを取得しています。新しいクラスのメソッドに取り込んでいるのがポイントです。

 起動するアプリケーションがわからない場合にこの処理を実行する最も簡単な方法は、文字列を作成する方法です。文字列を作成するコードは各自で記述します。Rubyのevalでこの文字列を評価し、これを現在のプロセスに組み込みます(注意: 任意の文字列でevalを気軽に使用するのは賢明ではありません。サンプルアプリケーションでは効果を発揮し、Rubyの興味深い機能のデモンストレーションに役立ちますが、特にユーザーから入力を処理するコードなど、本格的なアプリケーションでの使用には注意が必要です)。

 同様に、build_handlerは、問題となっているある処理の方法、つまりrun要求への応答を理解しているオブジェクト(簡単なものではありますが)を返します。

動的ロード

 この仕掛けを追加するには、プログラムを変更して、ファイルの種類を特定のアプリケーションに関連付けるのではなく、Rubyコードに関連付けます。このRubyコードでは、ファイルの種類に応じてどのアプリケーションを起動するかを判断するための起動ロジックを処理します。

補足:クラスの動的ロード
 面白いrunの実装を含んだカスタムRubyクラスを定義するのは非常に楽しい作業です。まず、すべてのクラスは各自が処理するファイル拡張子にちなんだ名前のファイルに格納されるものとします。例えば、HTMLファイルを処理するハンドラクラスは「html.rb」という名前のファイルになります。また、このようなファイルはすべて、「handlers」という名前の相対ディレクトリに格納されます。この2つの規則を組み合わせることで、get_handlerコードは何をどこで検索すればよいかを理解できます。長々とした設定は必要ありません。
 get_handlerが呼び出されると、次の処理が行われます。
  1. 組み込みのFileメソッドを使って、現在のファイルパス位置を求めます(__FILE__はRubyの特別変数で、現在のコードが格納されている実際のファイルを参照します)。
  2. 事前定義されたハンドラディレクトリを現在のパスに追加します。
  3. ターゲットファイル名のファイル拡張子を使って、ハンドラクラスコードが格納されているファイルの名前を求めます。
 このすべてをrequireに渡します(適切なファイルが存在し、ロード可能であることが前提になります)。すべてがうまくいけば、Rubyはこのファイルをロードおよび解析し、目的のクラスをコードで使用できるようになります。
 インスタンス化するクラスの名前が事前にわからない場合は、やや動的な起動を再び実行する必要があります。この場合もevalを使用できますが、Rubyの定数リストを使用して(クラスはRuby定数であることを思い出してください)、newを呼び出すこともできます。うまくいけば、Object.const_getは目的のクラスを返し、newがインスタンスを返します。
 何か問題がある場合(ロードすべきファイルがない場合や、ファイルの形式が不正な場合)は、例外が発生します。今回のコードでは、rescueを使って事態に対処します。rescueでは、固有の例外に対して限定的なエラー処理を実行することも可能ですが、この例の目的上、すべての例外をトラップし、単にnilを返すだけにします。
 get_handlerが、返す値を明示的に指定していないことに気付いたかもしれません。実際のところ、ここで定義したメソッドはいずれも返す値を明示的に指定していません。Rubyでは、最後に実行された式の値がメソッドの戻り値となります(いくつかの例外はあります)。get_handlerの最上位の式はbegin/rescue/endであり、この式の値は、begin/rescueセクション内の最後の式の値か、またはrescue/endで作成される値です。Rubyにもreturnは定義されており、これによって指定した値を返してメソッドを終了させることができますが、ほとんどの場合、メソッドフロー制御だけで明白な戻り値を十分に定義できます。

 この操作を行う前に、1つ小さな変更を加えます。すべてのコードを1箇所にまとめておくのは便利には違いありませんが、小規模なアプリケーションを除いては実際的ではありません。一般的なクラスコードと、ユーザーとやり取りする部分のコードとを分けて、コードを編成し直します。このためには、ファイル「go.rb」を作成し、実際のLauncherコード以外の部分(つまり、追加したばかりの最後のコード部分)をすべて移動します。

#!/usr/local/bin/ruby

require 'launcher'

# Script to invoke launcher using command-line args
def help
  print " 
  You must pass in the path to the file to launch.

  Usage: #{__FILE__} target_file
" 
end

unless ARGV.size > 0
  help
  exit
else
  app_map = {
     'html' => 'firefox',
     'txt' => 'gvim',
     'jpg' => 'gimp'
  }

  l = Launcher.new( app_map )
  target = ARGV.join( ' ' )
  l.run( target )
end

 先頭近くに新たなコード行があることに注意してください。

require 'launcher'

 この行は、現在のスクリプトでLauncherを使用するために必要です。requireメソッドは、指定された文字列に一致するファイルを検索します。ファイル拡張子は省略されていますが、Rubyは最初に.rbファイルを検索し、Rubyファイルが見つからなかった場合は、コンパイル済みのライブラリ(.soなど)も検索します。Rubyは、事前に定義されたロードパスを検索します。これには現在のディレクトリも含まれます。「launcher.rb」と「go.rb」が同じ場所にあれば問題ありませんが、ファイルを移動する場合は、Rubyがファイルを見つけられるように、明示的な指定を行う必要があります。

ハンドラクラスの作成

 ファイル名をRubyコードにルーティングする簡単なフレームワークができたので、HTMLファイルのハンドラクラスを作成します。このクラスは、ターゲットのファイル名に相当する少なくとも1つの引数と、追加パラメータのオプション配列を受け取るrunメソッドを実装する必要があります。クラス名は当然ながらHtmlであり、このクラスを含んでいるファイルは「html.rb」という名前で「handlers」サブディレクトリに置かれます。

class Html

  DEFAULT_BROWSER = 'firefox'

  def run file, args
    if args.empty?
      system( "#{DEFAULT_BROWSER} #{file}" ) 
    else
      dispatch_on_parameters file, args
    end
  end

  def dispatch_on_parameters file, args
    cmd = args.shift
    send( "do_#{cmd}", file, args )
  end

  def do_opera file, args=nil
    system( "opera #{file}  #{args}" )
  end

  def do_konq file, args=nil
    system( "konqueror #{file}  #{args}" )
  end
end

 このコードでは、まずデフォルトブラウザの定数を定義しています。追加の引数が省略されると、ターゲットファイルはFirefoxで起動されます。なお、場合によってはこれを変更して実行コマンドを定義する必要があることに注意してください。私のUbuntuマシンでは、明示的なパスなしでFirefoxを実行し、ブラウザを立ち上げることができます。しかし、例えばWindowsの場合は、実行ファイルへのフルパスが必要な可能性があります。

 追加のパラメータがある場合、rundispatch_on_parametersを呼び出します。dispatch_on_parametersargs配列の先頭のアイテムを取り出し、それを使ってメッセージ文字列を動的に作成します。すべてのRubyオブジェクトにはsendメソッドが組み込まれています。このメソッドを使用すると、オブジェクトに明示的にメッセージを送信できます。ここで行っているようにメソッドを単独で使用すると、現在のオブジェクトを受け取るものとみなされます。つまり、コードは自身に対してメッセージを送信します。

 実際の引数の前にdo_を追加しているのは、メソッド名の重複を防ぐためです。例えば、先頭の引数がexitだったとしても、おそらく、Rubyのexitメソッドを呼び出すつもりではないでしょう。do_exitを呼び出すことで、正しい動作を決定できます。

 このハンドラコードは、パラメータ処理の可能性を示すちょっとした例にすぎません。現状でも、ターゲットのHTMLファイルをデフォルトブラウザで起動したり、特定のブラウザで起動したりできます。

$ ./go index.html opera
$ ./go index.html konq

時間外の作業でさらなる改善

 教育的で実用的なサンプルができ上がりましたが、これを少しだけ掘り下げてみましょう。この作業を行うと10分の目安は過ぎてしまいますが、それだけの価値はあります。

 標準のRubyディストリビューションには、あらゆる種類のタスク用の豊富なライブラリが含まれています。その中で最も興味深いものの1つが、Sean Russellが開発したREXMLです。REXMLは純粋なRubyで作成されているXMLパーサーです。REXMLを使用すると、通常のW3C DOM APIではなく、RubyスタイルのAPIを使ってXMLを操作できます。Seanの功績はすぐにRubyの標準ライブラリの一部になりました。

 話を簡単にするために、今回のサンプルのHTMLファイルではXHTMLを使用する必要があります。REXMLが処理するのはXMLのみだからです(Hpricotのように、ほぼ任意のHTMLを処理できる非常に優れたRubyツールもありますが、別のライブラリをインストールする必要があり、その説明はこの記事の範囲を超えています)。整形式のXHTMLソースになっている場合には、サンプルのHTMLハンドラでファイル分析を実行できます。Htmlクラスの最後に次のコードを追加すると、XHTMLに関する簡単なレポートを生成できます。

def do_report( file, args=nil )
  require 'rexml/document'
  begin 
    dom = REXML::Document.new( IO.read( file ) )
    if args.empty?
      puts basic_xhtml_report( dom )
    else
      puts report_on( dom, args.first )
    end
  rescue Exception
    warn "There was a problem reading '#{file}':
#{$!}" 
  end
end

def report_on dom, element
  els =   dom.root.elements.to_a( "//#{element}" )
  "The document has #{els.size} '#{element}' elements" 
end

def basic_xhtml_report( dom ) 
  report = []
  css = dom.root.elements.to_a( '//link[@rel="stylesheet"]' )
  unless css.empty?
    report << "The file references #{css.size} stylesheets" 
    css.each do |el|
      file_name = el.attributes['href']
      file_name.gsub!( /^//, '')
      unless File.exist?(file_name)
        report << "*** Cannot find stylesheet file '#{file_name}'" 
      end
    end
  end

  js = dom.root.elements.to_a( '//script' )
  unless js.empty?
    report << "The file references #{js.size} JavaScript files" 
    js.each do |el|
      file_name = el.attributes['src']
      file_name.gsub!( /^//, '')
      unless File.exist?(file_name)
        report << "*** Cannot find JavaScript file '#{file_name}'" 
      end
    end
  end

  report.join( "
" )
end

 ここでは多くの処理が行われていますが、重要なのはdo_reportメソッドです。このコードではREXMLのDocumentオブジェクトを作成し、それをdomに割り当てています。余分な引数がない場合は、基本のレポートが得られます。それ以外の場合は、特定の要素について簡単な調査が行われます。

 report_onメソッドはドキュメント引数と要素名を受け取り、REXMLのXPath機能を使ってその要素の使用頻度を見つけます。これはあくまでも初歩にすぎませんが、デモンストレーションと、プログラミングの出発点としての役割は十分に果たしています。

 basic_xhtml_reportメソッドも同様ですが、このメソッドは特定の要素のセットに焦点を当てています。REXMLを使ってすべてのCSSおよびJavaScript参照を見つけ、Fileクラスを使って参照されているファイルの有無を確認します。これも複雑なものではありませんが、プロジェクトの機能拡張に役立っています。

最小限の枠組みで簡潔かつ表現豊かなコードを作成

 これまでの説明で、以下に示すようなRubyの特筆すべきいくつかの機能について理解できたことでしょう。

  • Rubyは基本的にオブジェクト指向の言語であり、「メッセージに応答するオブジェクト」を主要な概念としています。
  • Rubyは強力な動的型を採用しています。「型」の概念は、特定のクラス名や継承階層よりもむしろ、オブジェクトが実行できる動作に基づきます。オブジェクトの動作は、メッセージとメソッドのリテラルマッピングに限定されません。動作が実行時に動的に構成されることもあります。
  • Rubyクラスはオープンです。コアクラスの動作を、アプリケーションにとって適切だと思われる動作に自由に変更できます。

 オープンなクラスと動的な動作の組み合わせによって、最小限の枠組みを利用して、簡潔かつ表現豊かなコードを作成できます。Rubyは陰からあなたのコーディングを支えてくれます。

著者紹介

James Britt(James Britt)
アリゾナ州スコッツデールでRubyコンサルティング会社のNeurogamiを経営。仕事でRailsやNitroのアプリケーションを作成していないときは、Ruby-Doc.orgとRubyStuff.comの運営に携わる。2001年以来Rubyコミュニティで活動し、Rubyに関する書籍を多数執筆。米国およびヨーロッパのRubyカンファレンスで講演し、Phoenix Ruby Users Groupのコーディネータを務める。個人のWebサイトはjamesbritt.com
関連テーマ
最新トップニュース
データメーション
【データメーション】
OSについて気に入らないこと(9月5日)
ベンチャー専門家の目利きブログ「なぜこの企業は伸びるのか?」
【ベンチャー専門家の目利きブログ「なぜこの企業は伸びるのか?」】
「導入期〜成長期へ!一歩一歩と前進を目指す『Annoii(アノイ)』」/maka hou,Inc.(9月5日)
最新テクノロジーの意外な処方箋
【最新テクノロジーの意外な処方箋】
グリッドコンピューティング技術でETに遭遇(9月5日)
Graphic Design Forum
【Graphic Design Forum】
古い Emigre を探して (9月3日)
エンジニアの独り言
【エンジニアの独り言】
データをローカルに保存するWebアプリケーション(8月22日)
デスマーチからの脱却
【デスマーチからの脱却】
30min. iPhoneアプリリリース(8月18日)
最新ハイテク講座
最新ハイテク講座
なぜ勝った? 世界No.1シェアをつかんだ“Windows”(9月5日)
developer.com
developer.com
デザインパターンの使い方: Composite(9月5日)
最新アフィリエイト事例にみる成功の法則
最新アフィリエイト事例にみる成功の法則
コンバージョンレートを高めよう!(9月5日)
百式のネットビジネス研究
百式のネットビジネス研究
ガジェット購入時に将来の買取保証プランを提供する「TechForward」(9月5日)
週刊-サイト別アクセス状況データ
週刊-サイト別アクセス状況データ
ビデオリサーチインタラクティブ調査(月間インターネットオーディエンスデータ)(9月4日)
「IT の耳」
「IT の耳」
【書評】『検索にガンガンヒットさせる SEO の教科書』――SEO テクニックで効果的に PR する(9月4日)
検索エンジンマーケティング
検索エンジンマーケティング
果たしてモバイル SEO は必要なのか?(9月4日)
Eメールマーケティング事情
Eメールマーケティング事情
読者が迷惑メールと認識する時…(9月3日)
日本と韓国のインターネットビジネス最新動向調査
日本と韓国のインターネットビジネス最新動向調査
日本と韓国の動画サイト比較1―現状(9月3日)
SNSをビジネスに活用しよう
SNSをビジネスに活用しよう
「しまじろう」に学ぶ企業内コミュニティの活性化のポイント(9月2日)
海外のインターネットコムアメリカ韓国ドイツトルコ
Copyright 2008 Jupitermedia Corporation All Rights Reserved.http://www.internet.com/