境界を越える: Rails での拡張

acts_as プラグインを解剖する

Java™ プログラミング言語は長年の間、エンタープライズ・ライブラリー統合のための依存性注入コンテナーから EJB (Enterprise JavaBeans) 技術に至るまで、そして Eclipse 用のコンポーネント・モデルに至るまで、表現力豊かで強力な統合機能を持つ、偉大な「るつぼ」でした。非常に多くの概念やアーキテクチャーがあるため、Java 開発者は、本質的に異なるソフトウェア・ライブラリーやコンポーネントをつなぎ合わせ、まとまりを持ったものとするための新しい方法を開拓してきました。しかし、優れた統合技術を持っているのは Java 開発者だけではありません。この記事では、Ruby on Rails のプラグインの動作について、acts_as_state_machine という一般的なプラグインを調べながら説明していきます。

Bruce Tate (bruce.tate@j2life.com), CTO, RapidRed

Bruce TateBruce Tateは父であり、マウンテンバイク乗りであり、カヤック乗りであり、そしてテキサス州オースチンに住んでいます。Joltを受賞した『Better, Faster, Lighter Java』を含め、ベストセラーとなった3冊の著書を執筆しています。最近、『Spring: A Developer's Notebook』を発刊しました。彼はIBMに13年間在籍した後、J2Life, LLC社を設立し、Java技術とRubyに基づく軽量開発戦略とアーキテクチャーを専門としたコンサルティングを行っています。



2007年 3月 13日

私がこの記事を執筆している間、テキサス州とオクラホマ州は、長かったアイス・ストームの季節を終わろうとしています。凍った道路を恐れるだけではなく、その道路を走る短気なテキサス人ドライバーにも恐れをなしていた運転者達が、再び道路に出始めています。そして私の生活も、3 日間の休息の後、うわべは平常に戻ろうとしています。私は Java から Ruby に切り替えた後、氷による凍結とは別の短時間のフリーズを体験しました。私が Java プロジェクトで作業する際には、ちょっとしたニッチの問題があっても、必ずそれを解決するための特別な Spring ライブラリー、あるいは Eclipse コンポーネントを見つけることができました。しかし Ruby on Rails がまだ新しかった頃には、そうしたライブラリーやコンポーネントを自分自身で作成する必要があったのです。幸いなことに、雪解けと共に、私が体験したフリーズも見事に解消されつつあります。それは、何千人もの人達が Rails を拡張するために使ってきた、効果的なプラグイン・アーキテクチャーのおかげなのです。

このシリーズについて

この、境界を越えるシリーズでは、著者である Bruce Tate が、「今日の Java プログラマーは、他の手法や言語を学ぶことから多くを得ることができる」という概念を推し進めます。プログラミングの世界の様相は、あらゆる開発プロジェクトにとって Java が最善の選択肢であることが明確だった頃から変わってきています。他のフレームワークも Java フレームワークと同じ構築形態をとりつつあり、また他の言語での概念を学ぶことによって、それを Java プログラミングに生かすこともできます。皆さんが書く Python (あるいは Ruby や Smalltalk、その他何であれ) コードによって、皆さんの Java コーディングに対する取り組みも変わる可能性があるのです。

このシリーズでは、Java 開発とは大幅に異なりながら、同時に Java 開発にも直接応用できるプログラミング概念や手法について紹介します。場合によると、そうした技術を利用するためには、それら統合する必要があるかも知れません。また場合によると、そうした概念は直接利用できるものかもしれません。他の言語やフレームワークが、開発者やフレームワーク、Java コミュニティーでの基本的な手法にまで与えうる影響に比べると、個々のツールはそれほど重要ではありません。

これまで少しでも Rails を経験したことのある人であれば、まず間違いなく ActiveRecord の中の acts_as コマンドに気付いたはずです。ActiveRecord はパーシスタンスを処理するためのものですが、データベースでの保管、取得の他にもクラスに動作を追加したいことがよくあるものです。例えば acts_as_tree を使うと、parent_id 属性を持つクラスにツリーのような振る舞いを追加することができます。ActiveRecord モデルの中で acts_as_tree 以外のことを何も指定しなければ、例えば親レコードあるいは子レコードを取得するためのメソッドなど、ツリーを管理するためのメソッドを動的に追加することができます。私はこの 1 ヶ月間で、投票処理やバージョン管理、Ajax、複合キーなど、基本的な Rails ではサポートされない、あらゆる種類の機能を持った Rails 用プラグインを見つけることができました。

Rails の拡張モデルは Ruby 言語の機能の上に構築されていますが、Java 言語のモデルとは大幅に異なって見えます。この記事では、拡張モデルを内側から見られるように、acts_as プラグインについて調べることにします。ここではお遊びのエンド・ツー・エンドのシナリオを作成する代わりに、より広く内容をカバーし、また実際のプラグインの様子と、それらが実際の稼働コードの中でどう使われているかを実感できるように、実稼働システムの一部を例として示すことにします。

ステート・マシン API

多くの皆さんが知っているとおり、ステート・マシンはシステムの状態を数学的に表現したものです。ステート・マシンには、状態や状態間の遷移を表現するノードが混在しています。いかなる瞬間においても、ステート・マシンにはアクティブな状態 (カレント・ステートとも言われます) が 1 つあり、イベントによって状態間の遷移がトリガーされます。この概念を説明するために、私が現在日々行っている仕事から例を引用しましょう。仕事というのは、非営利団体と寄付者のためのマーケットである CTP (ChangingThePresent.org) の開発と維持管理です (「参考文献」を参照してください)。CTP は非営利団体に対して、その団体の組織に関する情報と必要としている寄付の内容 (例えば 1 人の癌研究者の 1 時間、あるいは 1 人の生徒への何冊かの本など) を提出させます。寄付者は簡単なショッピング・カートを使って別の名前で慈善寄付を行えます。そうしたすべての情報を収集しようとすると非常に手間がかかるため、私はステート・マシンを使ってワークフローを単純化することにしました。

この問題を解決するために、Scott Barron が作成した、acts_as_state_machine (「参考文献」を参照) というサードパーティーのプラグインを使うことにします。acts_as_state_machine は多くの Rails プラグインと同様、Ruby の機能と Rails 独自の機能を組み合わせることによって、ライブラリーのみならず DSL (domain-specific language: ドメイン固有言語) も提供でき、ユーザーに素晴らしいエクスペリエンスを与えることができます。

ある顧客が、コンテンツを CTP に送信します (submitted 状態)。そうすると CTP 管理者はそのコンテンツを受信し、場合によってはそれを編集します (processing 状態)。もし CTP が編集を行う場合、非営利団体はそうした変更を承認できる必要があります (nonprofit_reviewing 状態)。CTP あるいは非営利団体がコンテンツを承認すると、CTP はそのコンテンツをサイトに表示することができます (accepted 状態)。図 1 は、このステート・マシンを図で示したものです。

図 1. CTP のステート・マシン
図 1. CTP のステート・マシン

このプラグインを使うと、クラス・オブジェクトを直接修飾することができます。つまりさまざまな状態や状態間の遷移、そうした遷移を起動するイベントを、DSL を使って表現することができます。リスト 1 は、私が CTP で非営利団体を管理するために使用している、単純化したバージョンのステート・マシンを示しています。

リスト 1: ステート・マシンの例
 class Nonprofit < ActiveRecord::Base

  acts_as_state_machine :initial => :created, :column => 'status'
  
  # These are all of the states for the existing system. 
  state :submitted            
  state :processing           
  state :nonprofit_reviewing  
  state :accepted             
  
  event :accept do
    transitions :from => :processing, :to => :accepted
    transitions :from => :nonprofit_reviewing, :to => :accepted
  end
  
  event :receive do
    transitions :from => :submitted, :to => :processing
  end

  # either a CTP  or nonprofit user edits the entry, requiring a review
  event :send_for_review do   
    transitions :from => :processing, :to => :nonprofit_reviewing
    transitions :from => :nonprofit_reviewing, :to => :processing
    transitions :from => :accepted, :to => :nonprofit_reviewing
  end

皆さんはこれまでこの Ruby の機能を見たことがないかもしれませんが、この言語はステート・マシンのフローを非常に的確に記述できるのです。それぞれの状態の記述に続いて、ステート・マシンがサポートするイベントの記述があります。また、各イベントのあとには、イベントによって起動される一連の状態遷移を見ることができます。

各ステートメントは、有効な Ruby 構文を表現しています。クラス定義の後に、acts_as_state_machine :initial => :created, :column => 'status' があります。Java 開発者である皆さんは、メソッド定義の代わりにメソッド呼び出しがあるのを見て不思議に思うかもしれません。Ruby では、クラス・レベルのこうしたメソッド呼び出しを、マクロと呼びます。Ruby では、各クラスがロードされる際にマクロを使ってクラスに機能を追加することがよくあります。実際、メソッド定義 (def) は Ruby のマクロに他なりません。

次に、state :submitted など、一連の状態があることがわかります。これらはメソッド呼び出しであり、それぞれ 1 つのシンボルを 1 つのパラメーターとして使います。(シンボルはユーザーが定義する名前です。) event コマンドもメソッド呼び出しであり、シンボル (イベントの名前を定義します) と、遷移を定義するクロージャーを使います。

各遷移はメソッド呼び出しであり、その後にハッシュ・テーブルが続きます。Ruby では、ハッシュ・マップを key => value という対として表現します (各対はカンマで区切られ、また中括弧 { } で囲まれます)。ハッシュ・マップを関数コールの最後のパラメーターとして使う場合には、中括弧はオプションです。状態や遷移、イベントなどのメソッドは、クロージャーやハッシュ・マップと組み合わせることで、便利な DSL になることがわかります。

ステート・マシンを使うためには、Nonprofit オブジェクトをインスタンス化し、このオブジェクトに対してイベントごとにメソッドをコールし、その後に ! を付けます (リスト 2)。

リスト 2. ステート・マシンを操作する
>> np = Nonprofit.find(2)
=> ...
>> np.current_state
=> :submitted
>> np.receive!                              
=> true
>> np.accept!
=> true
>> np.current_state
=> :accepted

Rails の仕様では、「!」があると属性の修正と保存を 1 つのステップで行います。さて、ステート・マシン・プラグインに対する要件が明確になりました。つまり下記が必要なのです。

  • ステート・マシン・コードを置くための便利な場所
  • クラス・メソッドを指定するための方法 (DSL 用に必要です)
  • インスタンス・メソッドを、Nonprofit あるいは他のターゲット・クラスに加えるための方法

この記事のこれから先では、このプラグインを詳細に説明します。実際にコードを追ってみたい方は、acts_as_state_machine プラグインをダウンロードしてください (「参考文献」にある Scott Barron のサイトへのリンクを参照し、そのリンク先にある指示に従って、Subversion を使ってプラグインを入手してください)。trunk/lib までナビゲートすると、acts_as_state_machine.rb ファイルがあります。また、trunk/init.rb には初期化コードがあります。必要なものは、この 2 つのファイルのみです。


acts_as プラグイン

原則として、すべての acts_as プラグインは同じように動作します。acts_as モジュールをビルドするためには、必ず下記の手順に従います。

  1. モジュールを作成します。クラスのメソッド名 (初期化マクロ) は acts_as_ で始めます。
  2. 初期化コードの中で ActiveRecord ベース・クラスを開き、acts_as_ モジュールを追加します。
  3. acts_as_ 関数 (例えば acts_as_state_machine など) の中のターゲット・クラスの動作を拡張します。

リスト 3 に示す init.rb の初期化コードを見てください。

リスト 3. acts_as_state_machine 用の初期化コード
require 'acts_as_state_machine'

ActiveRecord::Base.class_eval do
  include ScottBarron::Acts::StateMachine
end

このコードでは、コアとなる ActiveRecord クラス (ActiveRecord::Base) を開いて、acts_as_state_machine を追加しています。class_eval メソッドによって、このクラスが開かれ、このクラスのコンテキストで次のクロージャーを実行します。こう書くと大げさですが、実際の概念は単純です。つまりこのコードは ActiveRecord ベース・クラスを開いて、ScottBarron::Acts::StateMachine モジュールと混合します。Ruby では、任意のクラスを開いて迅速に再定義することができるのです。

この機能によって柔軟性が高まるため、この機能は Ruby の最大の強みの 1 つです。しかし強みは弱みでもあります。柔軟性を持たせすぎると、コードは理解しにくく、維持管理しにくくなりがちなため、注意する必要があります。今度は acts_as_state_machine.rb ファイルを開き、どんなコードが混合されるのかを見てみましょう。


モジュールを初期化する

ここで、ステート・マシンの実装の詳細から離れ、ステート・マシンへのインターフェースをプラグインによって公開する方法について説明します。リスト 4 は、モジュールの定義と、ステート・マシン自体のインターフェースの一部を示しています。

リスト 4. モジュールの構造
module Acts                        #:nodoc:
  module StateMachine              #:nodoc:
    class InvalidState < Exception #:nodoc:
    end
    class NoInitialState < Exception #:nodoc:
    end
    
    def self.included(base)        #:nodoc:
      base.extend ActMacro
    end
    
    module SupportingClasses
      class State
        attr_reader :name
      
        def initialize
          ...
        end
        
        def entering
          ...
        end
        
        ...
      end
      
      class StateTransition
        attr_reader :from, :to, :opts
        
        def initialize
          ...
        end
        
        def perform
          ...
        end
        ...
      end
      class Event
      ...
        def fire
          ...
        end
        
        def transitions
          ...
        end
        ...
      end

リスト 4 の先頭に、ネストしたモジュール定義があります。モジュールにはメソッド定義がありますが、ベースとなる、継承の階層構造がありません。その代わり、既存の任意の Ruby のクラスにモジュールを加えることができます。この概念が初めての人は、モジュールのことを、インターフェースにインターフェースの実装を加えたものと考えてください。モジュールの良いところは、既存の任意の Ruby のクラスにモジュールの機能を加えることができ、しかもいくらでも加えることができるところです。また、クラスの既存機能を活用することもできます。この方法は Mix-in と呼ばれます。C++ も多重継承を使って同様の機能を実現することはできますが、非常に醜悪で面倒な事態が起きます。Java を作った人達は、そうした事態を避けるために多重継承をなくしました。しかしモジュールを使えば、面倒な事態を起こさずに多重継承の利点の一部を利用することができます。Smalltalk や Python などの言語も、Mix-in による継承をサポートしています。

リスト 4 の残りの部分は、ステート・マシンを実装するための、ありふれた詳細処理の一部を示しています。ここでは、これらのクラスによって、ステート・マシンのスタンドアローン実装が行われることを知っておけば十分です。それ以外のコードは、そのステート・マシンのインターフェースをプラグインのクライアントに公開する処理を行っているため、ずっと興味深い部分と言えます。


acts_as モジュール

プラグインの作成者には、実装を配置する場所と DSL (クラス・メソッド) を公開する方法、そしてステート・マシンのインスタンス・メソッドを公開する方法、という 3 つが必要なことを思い出してください。これらの中には、リスト 3 のアクションの中で見たイベント・メソッドが含まれます。リスト 4 は実装を配置する場所を提供しました。次のコード片では DSL を処理します。

acts_as プラグインのアーキテクチャーには、acts_as マクロという 1 つのアンカー・ポイントがあります。acts_as プラグインのクライアントは、このメソッドを、ターゲット・クラス内のメソッド呼び出しを使って導入します。私の場合では、リスト 1 の acts_as を、下記のコード行を使って Nonprofit クラスから呼び出します。

 acts_as_state_machine :initial => :created, :column => 'status'

今度はリスト 5 を見てください。これは acts_as_state_machine のための ActMacro です。このクラスはモジュールの属性を処理し、さまざまなクラスやインスタンス・メソッドを導入します。

リスト 5. acts_as を追加する
module ActMacro
  # Configuration options are
  #
  # * +column+ - specifies the column name to use for keeping the state (default: state)
  # * +initial+ - specifies an initial state for newly created objects (required)
  def acts_as_state_machine(opts)
    self.extend(ClassMethods)
    raise NoInitialState unless opts[:initial]
    
    write_inheritable_attribute :states, {}
    write_inheritable_attribute :initial_state, opts[:initial]
    write_inheritable_attribute :transition_table, {}
    write_inheritable_attribute :event_table, {}
    write_inheritable_attribute :state_column, opts[:column] || 'state'
    
    class_inheritable_reader    :initial_state
    class_inheritable_reader    :state_column
    class_inheritable_reader    :transition_table
    class_inheritable_reader    :event_table
    
    self.send(:include, ScottBarron::Acts::StateMachine::InstanceMethods)

    before_create               :set_initial_state
    after_create                :run_initial_state_actions
  end
end

リスト 5 のモジュールには、acts_as_state_machine という 1 つのメソッドがあります。このメソッドは、下記の 5 つのタスクを行います。

  • クラス・メソッドを導入する
  • ステート・マシンの例外を処理する
  • 属性を管理する
  • インスタンス・メソッドを導入する
  • フィルターの前後を処理する

acts_as_state_machine メソッドは、最初にクラス・メソッドを導入します。(こうしたメソッドの詳細はリスト 6 を見るとわかります。) 次に、このメソッドは例外を処理します。この場合では、唯一の例外は、クライアントが初期状態を指定しない場合に発生します。ちょっと継承の属性をスキップして、次に進みましょう。self.send メソッドはインスタンス・メソッドを導入します (リスト 7 はその詳細です)。最後に、before フィルターと after フィルターは ActiveRecord マクロであり、ActiveRecord がレコードを作成する前と後に set_initial_state と run_initial_state_actions をコールします。

write_inheritable_attribute マクロと class_inheritable_reader マクロに戻りましょう。なぜこのモジュールが単純な継承を使わないのか、不思議に思う人がいるかもしれません。その理由は単純です。このモジュールは、継承の階層構造を独自に持っているのです。こうしたマクロによって、モジュールは属性をターゲット・クラス (この例では Nonprofit) に加えることができるのです。最も重要な属性は、state_column と、状態とイベントと遷移を含む一連の遷移表です。さて今度は、DSL を形成するクラス・メソッドを追加します。


クラス・メソッドとインスタンス・メソッドを追加する

リスト 6 を見ると、DSL を導入するという魔術が、ようやくわかります。

リスト 6. acts_as_state_machine のためのクラス・メソッド
module ClassMethods
  def states
    read_inheritable_attribute(:states).keys
  end
  
  def event(event, opts={}, &block)
    tt = read_inheritable_attribute(:transition_table)
    
    et = read_inheritable_attribute(:event_table)
    e = et[event.to_sym] = SupportingClasses::Event.new(event, opts, tt, &block)
    define_method("#{event.to_s}!") { e.fire(self) }
  end
  
  def state(name, opts={})
    state = SupportingClasses::State.new(name.to_sym, opts)
    read_inheritable_attribute(:states)[name.to_sym] = state
  
    define_method("#{state.name}?") { current_state == state.name }
  end
  ...

先ほど触れたとおり、event マクロと state マクロは単純なメソッドであり、ClassMethods と呼ばれるモジュールの中で定義されます。event メソッドは遷移表の属性を読み取り、続いてイベント表の属性を読み取ります。このメソッドは、イベント表にイベントを追加した後、そのイベントに対するメソッドを動的に定義し、新しいメソッドを event の fire メソッドに接続します。

このモジュールは event メソッドを定義した後、state メソッドを定義します。state メソッドは状態表を読み取り、新しい状態を追加します。そして次にターゲット・クラスにコンビニエンス・メソッドを追加し、そのインスタンスがカレント・ステートであれば true を返します。例えば nonprofit.submitted? は、もし状態フラグが submitted であれば true を返します。これで DSL が完全にサポートされました。

インスタンス・メソッドも、クラス・メソッドとまったく同様に動作します。リスト 7 はインスタンス・メソッドを示しています。

リスト 7. acts_as_state_machine のインスタンス・メソッド
module InstanceMethods
  def set_initial_state
    write_attribute self.class.state_column, self.class.initial_state.to_s
  end

  ...
  
  def current_state
    self.send(self.class.state_column).to_sym
  end
  
  ...
end

ActMacro はクラスを開き、インスタンス・メソッドを追加します。属性を使うために read_inheritable_attribute マクロを使う必要はありません。それは、こうした属性は ActiveRecord が定義するクラス・インスタンス変数であるからです。ここでは、初期状態を設定してカレント・ステートを返すメソッドのみを示しています。これ以外のメソッドも、同じように動作します。

リスト 7 の最初のメソッドは、初期状態を設定し、既存の ActiveRecord 列を更新します。ActMacro を呼び出した際に列の名前を設定したことを思い出してください。current_state メソッドは、単純にインスタンス変数の値を返します。send メソッドは、1 つのシンボル・パラメーターによる名前 (この場合は state_column の名前) を持ったメソッド名を呼び出します。


まとめ

皆さんは、単にステート・マシンを作成し、それをライブラリーとして使った方が簡単だと思うかもしれません。しかし acts_as プラグインは、それよりももっと便利なのです。acts_as を使うことで、データベースにステート・マシン列を効果的に追加することができます。また、他のプラグインを使えば、バージョン管理や監査履歴の作成、画像の処理、その他何百という単純作業を、そうした作業があたかも Rails 環境とデータベースとをシームレスに統合するかのように行うことができます。

皆さんは Java 言語を使って Eclipse プラグインや Ant タスク、あるいは Spring ライブラリーをコード・ベースに統合する、あるいは EJB コンポーネントを導入するといった経験があるかもしれません。Java コミュニティーから生まれた多くの概念によって、拡張に対する開発者の考え方が変わったのです。ここでは Rails の acts_as プラグインを駆け足で紹介しながら、拡張に対する新しい考え方を説明しました。私は Ruby 言語の柔軟性を見たことで、拡張に対する考え方を変えました。acts_as プラグインによって、新世代の開発者達は実際に拡張を作成することができます。そしてその結果、Rails 用の新しい拡張が大量に生まれています。こうした方法の多くは、アスペクト指向プログラミングやバイトコード・エンハンスメントによって、Java 開発者にも利用できるのです。

次回はこのシリーズの最終回として、困難な問題を Ruby を使って解決する場合と、Java プラットフォームでの私の経験とを詳細に比較します。それまでの間、境界を越え続けてください。

参考文献

  • 『Java To Ruby: Things Every Manager Should Know』(2006年、Pragmatic Bookshelf 刊) は、この記事の著者による本です。Java プログラミングから Ruby on Rails に切り替える意味があるのは、いつ、どんな場合か、そしてその方法について解説しています。
  • 『Beyond Java』(2005年、O'Reilly刊) も、この記事の著者による本です。Java 言語の台頭と停滞について、また、一部のニッチな領域で Java プラットフォームに対抗しうる技術について解説しています。
  • Rails プラグインのアーキテクチャーについての資料、Plugins in Ruby on Rails を調べてみてください。
  • Rails のプラグイン、acts_as_state_machine を利用すると、ActiveRecord モデルをステート・マシンとして機能させることができます。
  • この記事で実例として引用した Changing The Present は、Ruby on Rails で構築された非営利のマーケットです。
  • 多重継承を処理する必要があるとすると、クラス変数とインスタンス変数の処理は面倒です。この記事、「Class and instance variables in Ruby」(John Nunemaker 著、RailsTips.org、2006年11月) は、継承可能な属性と呼ばれる方法の例を、順を追って解説しています。
  • Java technology ゾーンには Java プログラミングのあらゆる側面を網羅した記事が豊富に用意されています。
  • developerWorks blogs から developerWorks のコミュニティーに加わってください。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology, Web development
ArticleID=250248
ArticleTitle=境界を越える: Rails での拡張
publish-date=03132007