10分でできる初めてのRubyプログラムはじめにRubyの多様性や強力さについては既にご存知のことでしょう。Rubyをさらに深く知るためには、その背後にある繊細かつ重要な概念を理解しておきたいところです。この記事では、小さな実用的なRubyプログラムを作成します。Rubyは基本的にクラスとオブジェクトを使用するオブジェクト指向の言語であり、動作をカプセル化したクラスを簡単に作成できます。この記事では、最初に簡易版のプログラムを作成し、そのプログラムを拡張していきます。読み進むにつれて、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 C:somedir> launcher.rb ファイルを実行すると、コードに何らかのエラーがない限り、何も表示されないまま実行が終了します。何も表示されなければそれで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 このコードでは最初に変数( 次に、RubyはLauncherクラスの Launcherの実装動作の追加 これから作成するプログラムの主な目的は、指定されたファイル名を受け取り、これを関連するアプリケーションに渡して何らかの処理を行うことです。そこで、ファイル名とアプリケーションのマッピング方法をプログラムに認識させるために、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メソッドを定義して、新しく作成されたインスタンス内で引数を処理する必要があります。インスタンス変数 指定されたファイルを関連アプリケーションで実行するためには、プログラムにさらにコードを追加します。 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 コンパクトなコードにするために、ここでは上記のメソッドをつなげて記述しています。つまり、 ここまでのサンプルコードは、StringおよびHashであることが期待されるオブジェクトを使用してきました。しかし、本当に注意すべきは、これらのオブジェクトが特定のメッセージに適切に応答することです。このような小さなプログラムでは、メッセージとランタイム動作に基づく巧妙で強力なオブジェクトシステムの重要性は感じられないかもしれませんが、大規模なRubyプログラムを作成するときには、この点を理解していることが重要です。 オブジェクト、データ型、および動作について(ダックタイピング) Rubyは、オブジェクト指向プログラミングのメッセージ受け渡しモデルに従います。 オブジェクトはメッセージを受け取ると、最初に、対応するメソッドを探します。この検索は、オブジェクト自身のクラスから始まってObjectクラスに達するまで、継承階層をさかのぼる形で行われます。一致するものが見つからない場合は、メソッド この柔軟性は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 このコードを試す前に、正しい実行プログラムを参照するように、 $ 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 まず注目してほしいのは、最初に これはRubyのコアクラスにも当てはまります。例えば、独自のメソッドを備えたStringクラスを定義することで、デフォルトのStringの動作を拡張したり変更したりできます。文字列の変更(例えば特殊文字の置換)を頻繁に行う場合は、次のように定義することでコードを簡潔にできます。 class String def amp_escape self.gsub( '&', '&' ) end end これにより、次のようなコードを記述できるようになります。
"This & that".amp_escape
新しいプログラムファイルでは、この新しい動作を処理する必要があります。まず 最初のバージョンでは、単にファイル拡張子を使ってハッシュからアプリケーション名を取得していたのに対して、このコードでは、 新バージョンの 新しいLauncherクラスは、元のアプリケーションマップコードを保持しています。このマッピングは、特殊なRubyコードが失われた場合にファイルを処理するための代替システムとして役割を果たすものであり、新バージョンでも最初のバージョンと同じ処理が行われることを意味します。コードでは、引き続き 起動するアプリケーションがわからない場合にこの処理を実行する最も簡単な方法は、文字列を作成する方法です。文字列を作成するコードは各自で記述します。Rubyの 同様に、 動的ロードこの仕掛けを追加するには、プログラムを変更して、ファイルの種類を特定のアプリケーションに関連付けるのではなく、Rubyコードに関連付けます。このRubyコードでは、ファイルの種類に応じてどのアプリケーションを起動するかを判断するための起動ロジックを処理します。 補足:クラスの動的ロード
面白い
runの実装を含んだカスタムRubyクラスを定義するのは非常に楽しい作業です。まず、すべてのクラスは各自が処理するファイル拡張子にちなんだ名前のファイルに格納されるものとします。例えば、HTMLファイルを処理するハンドラクラスは「html.rb」という名前のファイルになります。また、このようなファイルはすべて、「handlers」という名前の相対ディレクトリに格納されます。この2つの規則を組み合わせることで、get_handlerコードは何をどこで検索すればよいかを理解できます。長々とした設定は必要ありません。get_handlerが呼び出されると、次の処理が行われます。
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を使用するために必要です。 ハンドラクラスの作成 ファイル名をRubyコードにルーティングする簡単なフレームワークができたので、HTMLファイルのハンドラクラスを作成します。このクラスは、ターゲットのファイル名に相当する少なくとも1つの引数と、追加パラメータのオプション配列を受け取る 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の場合は、実行ファイルへのフルパスが必要な可能性があります。 追加のパラメータがある場合、 実際の引数の前に このハンドラコードは、パラメータ処理の可能性を示すちょっとした例にすぎません。現状でも、ターゲットの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 ここでは多くの処理が行われていますが、重要なのは 最小限の枠組みで簡潔かつ表現豊かなコードを作成これまでの説明で、以下に示すような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。
|