目次


Web 時代の非リレーショナルデータベース

第 2 回 Apache CouchDB と Ruby on Rails を使って wiki アプリケーションを作成する

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: Web 時代の非リレーショナルデータベース

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:Web 時代の非リレーショナルデータベース

このシリーズの続きに乞うご期待。

開発環境の準備

最初に、前回 CouchDB をインストールした Ubuntu 8.04 のマシンに、Ruby on Rails を使った Web アプリケーション開発 [2] に必要なパッケージをインストールします (Rails は Ubuntu のパッケージを使わず、RubyForge から取得しています)。

$ sudo apt-get ruby
$ sudo apt-get irb
$ sudo apt-get rubygems
$ sudo gem install rails	# GemNotFoundException で失敗する場合はリトライします

2008/07/15 時点で、以下のバージョンが導入されましたので、この環境を前提に、記事を進めます。

$ ruby –v
ruby 1.8.6 (2007-09-24 patchlevel 111) [i486-linux]
$ gem –v
0.9.4
$ gem list

*** LOCAL GEMS ***
actionmailer (2.1.0)
    Service layer for easy email delivery and testing.

actionpack (2.1.0)
    Web-flow and rendering framework putting the VC in MVC.

activerecord (2.1.0)
    Implements the ActiveRecord pattern for ORM.

activeresource (2.1.0)
    Think Active Record for web resources.

activesupport (2.1.0)
    Support and utility classes used by the Rails framework.

rails (2.1.0)
    Web-application framework with template engine, control-flow layer,
    and ORM.

rake (0.8.1)
    Ruby based make-like utility.

sources (0.0.1)
    This package provides download sources for remote gem installation

なお、Ubuntu では gem によってインストールされるコマンドが格納されているディレクトリ (/var/lib/gems/1.8/bin) へのパスが通っていないので、下記を実行してパスを通しておきます。

$ export PATH=$PATH:/var/lib/gems/1.8/bin

Ruby on Rails による開発の流れ

開発を始める前に、通常の (MySQL などの RDBMS を使用する) Rails アプリケーションの作成の流れを確認しましょう。通常、Rails アプリケーションを作成する場合、以下のようになります。

  1. rails コマンドを使用してアプリケーションルートディレクトリ (RAILS_ROOT) を作成する
  2. RAILS_ROOT/config/database.yml を編集し、データベースアクセスに対する設定を行う (オプション)
    (ア) 必要に応じて、他の設定ファイル (config/*) を書き換える (オプション)。
  3. rake コマンドを使用してデータベースを作成する。
  4. データベーススキーマを定義する。
  5. generator を使うなどして、Rails の MVC を追加していく (4 と並行作業の場合もある)。

それでは、この流れに沿って、CouchDB + Rails のアプリケーション環境を作ります。併せて、CouchDB でのデータベース作成・削除について学びます。

ステップ 1. RAILS_ROOT を作成する。

これは CouchDB を使用する場合も同様です。rails コマンドを使用して、アプリケーションルートディレクトリを作成します。CouchDB で作る wiki なので、筆者はアプリケーション名を couchiki と名付けました。

$ rails couchiki

作成された couchiki ディレクトリを以後 RAILS_ROOT と記述することにします。

コラム: ActiveRecord に依存しない Rails 環境を構成する

CouchDB と RDBMS を両方同時に使うことはもちろん可能ですが、今回のアプリケーションでは RDBMS は使用しません。ActiveRecord 抜きでも Rails アプリケーションを作ることができることを実感する良い機会でもありますので、Rails フレームワークから ActiveRecord を外してみる (普段は滅多に行わないでしょう!!)のも、良いでしょう。この環境構成を行わない場合には、sqlite の gem を別途インストールしておく必要があります。

config/environment.rb に config.frameworks という記述がありますが (24行目付近)、これを使用して ActiveRecord を読み込まないように設定することができます。下記は ActiveRecord, ActiveResource, ActionMailer を除外した場合の例です。

RAILS_ROOT/config/environment.rb
# CouchDB のみを使うので、ActiveRecord は外します。
config.frameworks -= [ :active_record, :active_resource, :action_mailer ]

さらに、Rails 2.1 では config/initializers/new_rails_defaults.rb というファイルに、ActiveRecord に依存するコードが記述されているので (5, 8行目)、該当箇所をコメントアウトします。

RAILS_ROOT/config/initializers/new_rails_defaults.rb
# Include Active Record class name as root for JSON serialized output.
# ActiveRecord::Base.include_root_in_json = true

# Store the full class name (including module namespace) in STI type column.
# ActiveRecord::Base.store_full_sti_class = true

ステップ 2. データベースに対する設定を行う。

CouchDB でもデータベースに対する接続設定はあったほうがよいでしょう。HTTP を使用してデータベースにアクセスするので、せめてホスト名・ポート番号は設定しましょう。RAILS_ROOT/config/couchdb.yml ファイルを使用して CouchDB に対するデータベース設定を記述することにします。

リスト 1. RAILS_ROOT/config/couchdb.yml
development:
   host: localhost
   port: 5984
   database: couchiki_development

test:
   host: localhost
   port: 5984
   database: couchiki_test

production:
   host: localhost
   port: 5984
   database: couchiki_produciton

ステップ 3. データベースを作成する。

通常の ActiveRecord を利用するRailsアプリケーションであれば、db:create タスクを使用することで config/database.yml に設定したデータベースを作成することが可能です。今回は CouchDB を使用するので、同じように couchdb:create, couchdb:drop を使って、現在の RAILS_ENV 環境に対するデータベースの作成・削除ができるようにします。

CouchDB ではデータベースの作成・削除も、HTTP API[3] を経由して操作します (表 1)。

表 1. データベース操作のための HTTP API
HTTP Verbパス意味成功時ステータスコード
GET/{dbname}データベースの情報を取得します。200
PUT/{dbname}データベースを作成します。201
DELETE/{dbname}データベースを削除します。200

今回は、作成、削除に相当する HTTP PUT, HTTP DELETE を rake タスクとして実装します (リスト 2)。HTTP クライアントとしては、Ruby 本体に同梱されている Net::HTTP 周りのライブラリを使うことになります[4][5]。CouchDB では / の直下のパスがデータベース名を表します。作成・削除・参照の各操作はそれぞれ HTTP の verb で指定します。データベースの作成は Put リクエスト (#2.1), データベース情報の参照は Get リクエスト (#2.2)、そしてデータベースの削除に Delete リクエスト (#2.3) を発行しています。

リスト2. RAILS_ROOT/lib/tasks/couchdb.rake
require 'net/http'
namespace :couchdb do
  desc 'Create a database on CouchDB for the current environment.'
  task :create do
    config   = YAML.load(File.open(File.join(RAILS_ROOT, "config/couchdb.yml")))
    Net::HTTP.start(config[RAILS_ENV]["host"] || "localhost",
                               config[RAILS_ENV]["port"] || 5984)       do |http|
      # (2.1)
      # PUT http://{host}:{port}/{dbname}
      #
      response = http.request(Net::HTTP::Put.new("/#{config[RAILS_ENV]["database"]}"))
      unless response.kind_of?(Net::HTTPSuccess)
        raise RuntimeError.new("CouchDB Error :#{response.body}(HTTP #{response.code})")
      end

      # (2.2)
      # GET http://{host}:{port}/{dbname}
      #
      response = http.request(Net::HTTP::Get.new("/#{config[RAILS_ENV]["database"]}"))
      puts response.body
    end
  end

  desc 'Drop a database from CouchDB for the current environment.'
  task :drop do
    config   = YAML.load(File.open(File.join(RAILS_ROOT, "config/couchdb.yml")))
    Net::HTTP.start(config[RAILS_ENV]["host"] || "localhost",
                    config[RAILS_ENV]["port"] || 5984)       do |http|
      # (2.3)
      # DELETE http://{host}:{port}/{database}
      #
      response = http.request(Net::HTTP::Delete.new("/#{config[RAILS_ENV]["database"]}"))
      unless response.kind_of?(Net::HTTPSuccess)
        raise RuntimeError.new(
"CouchDB Error :#{response.message}(HTTP #{response.code})")
      end
    end
  end
end

コードを作成したら、正常に動作することを確認します。データベース情報が出力されれば成功です。

$ rake couchdb:create
(in /home/yssk22/rails/couchiki)
{"db_name":"couchiki_development",
"doc_count":0,"doc_del_count":0,
"update_seq":0,"compact_running":false,"disk_size":4096}

ステップ 4. データベーススキーマを定義する。

CouchDB はスキーマレスのデータベースです。データベースに対するスキーマは事前定義不要です。従って migration ファイルに記述するようなスキーマ記述は行いません。その代わり、Wiki の 1 つ 1 つのページを表すモデルオブジェクトを app/model/wiki_page.rb に WikiPage クラスとして定義することにします。WikiPage モデルの作成については、順を追って次の章で説明します。

ステップ 5. Rails の MVC を追加していく。

CouchDB を使用する際には、Rails の ActiveRecord に依存するジェネレーターを使用することができないため、手動でファイルを追加してアプリケーションを構築していくことになります。今回は Model 部分の実装について重点を置いて解説します。

ActiveRecordに代わるモデルクラスの定義

アプリケーション上のデータモデルを表現する WikiPage クラスを定義します。このクラスの役割は次の通りです。

  • CouchDB 上に保存するデータを保持する
  • CouchDB への接続を行い、データ作成・更新・参照・削除 (+その他機能)を提供する

データを保持する

まずは、WikiPage クラスが持つべきデータを定義します (リスト 3-1)。今回はサンプルアプリケーションなので、単純に、タイトル (title) とコンテンツ (content) をデータとして持つ WikiPage クラスを定義します。データベースに保存するデータは @document メンバーに保持するようにします。@document は OpenStruct として定義し、簡単 JSON にシリアライズ・デシリアライズできるようにしておきます。OpenStruct は Hash のように、事前に構造体の定義が不要な構造体クラスです。OpenStruct を使い、delegate で適切にプロパティを参照・設定できるようにしておくことで、(Hash を使う場合と比較して) 不用意に、データ項目を書き換えることを防げるようにした上でデータ項目の変更 (= delegate引数の変更) に対応します (#3-1.1)。delegate は、Rails (ActiveSupport) による Ruby の拡張で、title, content (読み取り) あるいは title=, content= (書き込み)、というメソッド呼び出しが発生したら @document に委譲することを指示しています。

_id および _rev は CouchDB が予約済みのプロパティです。_id はデータベース上の一意キーを表し、_rev は更新番号を示します。id, revision を読み取り専用アクセサしてアクセス可能にしています。この 2 つのプロパティは CouchDB との通信に利用しますが、詳細は後述します (#3-1.2)。

リスト3-1. RAILS_ROOT/app/models/wiki_page.rb
require 'ostruct'
class WikiPage
  # (3-1.1)
  delegate :content,  :title,  :to => "@document"
  delegate :content=, :title=, :to => "@document"

  def initialize(data={})
    hash = {
      "content" => data["content"],
      "title"   => data["title"]
    }
    hash["_id"]  = data["_id"] if data["_id"]
    hash["_rev"] = data["_rev"] if data["_rev"]
    @document = OpenStruct.new(hash)
  end

  # (3-1.2)
  def id;       @document._id;  end
  def revision; @document._rev; end
end

このコードを script/console で試してみます。

$ ruby script/console 
Loading development environment (Rails 2.1.0)
>> page = WikiPage.new
=> #<WikiPage:0xb7222768 @document=#<OpenStruct content=nil, title=nil>>
>> page.title = "title1"
=> "title1"
>> page.content = "content ... "
=> "content ... "
>> page.title
=> "title1"
>> page.content
=> "content ... "
>> page.id
=> nil
>> page.revision
=> nil

これで wiki ページの情報を保持するオブジェクトを作ることができるようになりました。では、この WikiPage オブジェクトのデータを CouchDB に格納、あるいは CouchDB から WikiPage オブジェクトを取得する方法を実装します。

データ作成・更新・参照・削除機能を提供する

CouchDB でのデータ操作は、データベースと同じく HTTP によって提供され、JSON フォーマットのテキストをやりとりします。今回は WikiPage オブジェクトのデータを、そのまま 1 ドキュメントとして保存するような方針にします。つまり、CouchDB に渡すべきデータである @document をそのまま JSON 形式でシリアライズして保存します。逆に、CouchDB から受け取った JSON ドキュメントは、Hash に復元して WikiPage コンストラクタに渡すことで、オブジェクトを復元します。

CouchDB に対するデータの作成・更新・削除の API[6] は表 2 のようになります。

表 2. ドキュメント操作(作成・更新・削除)のための HTTP API
HTTP Verbパス意味成功時ステータスコード
GET/{dbname}/{_id}_id で表される一意キーを持つドキュメントを取得します。200
PUT/{dbname}/{_id}_id で表される一意キーを持つドキュメントを作成または更新します。201
POST/{dbname/ドキュメントを作成します。_id が CouchDB によって自動生成されます。201
DELETE/{dbname}/{_id}?rev={rev}_id で表される一意キーを持つドキュメントを削除します。rev パラメーターに現在のリビジョンを指定する必要があります。200

PUT と POST の違いに注意してください。PUT を使用する場合は、一意キーである _id をクライアント側(この場合 WikiPage クラス)が指定することになります。_id を CouchDB に生成させる場合は POST を使用します。

表 2 を元に、WikiPage#save メソッド(ページの作成、更新に使用)を実装したものが、リスト 3-2 です。

リスト 3-2. app/models/wiki_page.rb
# …(省略)…
  # (3-2.1)
  def save
    request = Net::HTTP::Put.new(
File.join("", WikiPage.db_config["database"], @document.title))
    request["Content-Type"] = "application/json"
    request["Accept"] = "application/json"

    # (3-2.2)
    request.body = @document.marshal_dump.to_json
    response = WikiPage.process_db_request(request)

    if response.kind_of?(Net::HTTPSuccess)
      # (3-2.3)
      data = ActiveSupport::JSON.decode(response.body)
      @document._rev = data["rev"]
      @document._id  = data["id"]
      self
    else
      false
    end
  end

  # (3-2.4)
  def self.process_db_request(request)
    http = Net::HTTP.new(self.db_config["host"],
                         self.db_config["port"])
    http.set_debug_output STDERR
    # this returns HTTPResponse objet
    http.start do |worker|
      worker.request(request)
    end
  end

  def self.db_config
    @@config ||= YAML.load(File.open(File.join(RAILS_ROOT, "config/couchdb.yml")))
    @@config[RAILS_ENV]
  end

今回は title に設定されたタイトルをそのまま _id 値として使用することにしています (3-2.1)。この場合、PUT リクエストを使用するので、データ作成時の HTTP リクエストは次のようになるはずです (development 環境の場合)。

PUT /couchiki_development/title に設定した値
Content-Type: application/json
Accept: application/json
Content-Length: …

{ “title”: “title に設定した値”, “content”: “content に設定した値” }

リクエストボディの JSON を生成するには、OpenStruct を marshal 化し、Hash オブジェクトにした後、to_json を呼び出します (#3-2.2)。そして、リクエストに成功すると HTTP 201 のステータスコードとともに、以下の JSON コンテンツをクライアントに返します。

{ “id”: “title に設定した値”, 
“rev”: “CouchDB が生成したドキュメント毎のリビジョン番号” }

id は(今回は title と一致するはずなので) 無視してかまいませんが、rev は重要です。CouchDB は _rev 値を使用して更新リクエストについて衝突の検出を行います。既に作成済みの URI(/{dbname}/{ title に設定した値} に対して PUT を行った場合、CouchDB はリクエストボディに含まれる JSON の _rev プロパティの値 (アンダースコア_が含まれることに注意してください!) をチェックし、現在のリビジョンと一致するならば更新処理を行い、そうでなければ HTTP のステータスコードとして 412 (メッセージは Precondition Failed) エラーを返すようになっています。そこで、@document で受け取った id,rev 値をそれぞれ、_id, _rev に保持するようにし、以後の PUT リクエスト発行時には割り当てられた _id, _rev の値も送信するようにします (#3-2.3)。

process_db_request は実際に HTTP リクエストを実行するメソッドです。set_debug_output を設定することで、script/console で試すときに HTTP でやりとりしている内容を確認できるようにしています (#3-2.4)。

そして、保存したデータを取得するために、find(arg) メソッドを実装します。ActiveRecord では find(1) など id を指定して取得可能です。そこで同様に、find(“title1”)という使用方法、つまり title 値 (_id 値) で取得できるようにしましょう。

リスト 3-3. app/models/wiki_page.rb
  def self.find(arg)
    case arg
    when :all
      find_all # TODO 次回実装します
    else
      find_by_id(arg)
    end
  end

  def self.find_by_id(id)
    request = Net::HTTP::Get.new(File.join("", WikiPage.db_config["database"], id))
    request["Accept"] = "application/json"
    response = process_db_request(request)
    if response.kind_of?(Net::HTTPSuccess)
      self.new(ActiveSupport::JSON.decode(response.body))
    else
      response.error!
    end
  end

CouchDB から保存したドキュメントを取り出すには次のように HTTP リクエストを発行することになります。

GET /couchiki_development/{title値}
Accept: application/json

このとき、ドキュメントが見つかれば、次のようなJSONデータがレスポンスボディに格納されています。

{ 
“_id”: “title値”, 
“_rev”: “現在のリビジョン番号” 
“title”: “title値”, 
“content”: “content値” 
}

従って元の WikiPage オブジェクトに戻すにはこの JSON を Hash にして、コンストラクタに渡すだけです。JSON を Hash にするには、ActiveSupport::JSON.decode メソッドを使用することができます。

これで WikiPage クラスの大半は完成です。この状態で簡単に script/console で動作を確認することができます。次のリスト 4, 5 の実行例では、実際に新規作成ページを作り、更新の衝突 (_rev プロパティの不一致)が起こるまでを確認しています。それぞれ図 1, 2 と併せて、どんなやりとりをしているのかを確認してください。

リスト 4. script/console での動作確認(1)
$ ruby script/console 
Loading development environment (Rails 2.1.0)
>> page = WikiPage.new
=> #<WikiPage:0xb71d15c0 @document=#<OpenStruct content=nil, title=nil>>
>> page.title = "title1"
=> "title1"
>> page.content = "content1..."
=> "content1..."
>> page.save
opening connection to localhost...
opened

<- "PUT /couchiki_development/title1 HTTP/1.1\r\n
Accept: application/json\r\n
Content-Type: application/json\r\n
Content-Length: 45\r\n
Host: localhost:5984\r\n
\r\n"
<- "{\"content\": \"content1...\", \"title\": \"title1\"}"
    …(1)

-> "HTTP/1.1 201 Created\r\n"
-> "Server: MochiWeb/1.0 (Any of you quaids got a smint?)\r\n"
-> "Etag: \"2707908390\"\r\n"
-> "Date: Thu, 17 Jul 2008 11:13:40 GMT\r\n"
-> "Content-Type: application/json\r\n"
-> "Content-Length: 44\r\n"
-> "\r\n"
reading 44 bytes...
-> "{\"ok\":true,\"id\":\"title1\",\"rev\":\"2707908390\"}"
    …(2)

read 44 bytes
Conn keep-alive
=> #<WikiPage:0xb71d15c0 
@document=#<OpenStruct 
content="content1...", title="title1", 
_id="title1", _rev="2707908390">>
>> page2 = WikiPage.find("title1")
opening connection to localhost...
opened
<- "GET /couchiki_development/title1 HTTP/1.1\r\n
Accept: application/json\r\n
Host: localhost:5984\r\n
\r\n"
    …(3)

-> "HTTP/1.1 200 OK\r\n"
-> "Server: MochiWeb/1.0 (Any of you quaids got a smint?)\r\n"
-> "Etag: \"2707908390\"\r\n"
-> "Date: Thu, 17 Jul 2008 11:14:16 GMT\r\n"
-> "Content-Type: text/plain;charset=utf-8\r\n"
-> "Content-Length: 77\r\n"
-> "\r\n"
reading 77 bytes...
-> "{\"_id\":\"title1\",
\"_rev\":\"2707908390\",
\"content\":\"content1...\",
\"title\":\"title1\"}"
    …(4)
read 77 bytes
Conn keep-alive
=> #<WikiPage:0xb774dbac 
@document=#<OpenStruct 
content="content1...", title="title1", 
_id="title1", _rev="2707908390">>
図 1. リスト 4 の動作図
リスト 4 の動作図
リスト 4 の動作図
リスト 5. script/console での動作確認 (2) (リスト 4 から引き続き実行)
>> page.content = "content1 updated"
=> "content1 updated"
>> page.save
opening connection to localhost...
opened
<- "PUT /couchiki_development/title1 HTTP/1.1\r\n
Accept: application/json\r\n
Content-Type: application/json\r\n
Content-Length: 89\r\n
Host: localhost:5984\r\n
\r\n"
<- "{\"content\": \"content1 updated\", 
\"title\": \"title1\", 
\"_id\": \"title1\", \"_rev\": \"2707908390\"}"

-> "HTTP/1.1 201 Created\r\n"
-> "Server: MochiWeb/1.0 (Any of you quaids got a smint?)\r\n"
-> "Etag: \"367363025\"\r\n"
-> "Date: Thu, 17 Jul 2008 11:14:38 GMT\r\n"
-> "Content-Type: application/json\r\n"
-> "Content-Length: 43\r\n"
-> "\r\n"
reading 43 bytes...
-> "{\"ok\":true,\"id\":\"title1\",\"rev\":\"367363025\"}"
    …(6)
read 43 bytes
Conn keep-alive
=> #<WikiPage:0xb71d15c0
@document=#<OpenStruct 
content="content1 updated", title="title1", 
_id="title1", _rev="367363025">>
>> page2.content = "content1 updated by page2"
=> "content1 updated by page2"
>> page2.save
opening connection to localhost...
opened

<- "PUT /couchiki_development/title1 HTTP/1.1\r\n
Accept: application/json\r\n
Content-Type: application/json\r\n
Content-Length: 98\r\n
Host: localhost:5984\r\n
\r\n"
<- "{\"content\": \"content1 updated by page2\", 
\"title\": \"title1\", 
\"_id\": \"title1\", \"_rev\": \"2707908390\"}"
    … (7)

-> "HTTP/1.1 412 Precondition Failed\r\n"
-> "Server: MochiWeb/1.0 (Any of you quaids got a smint?)\r\n"
-> "Date: Thu, 17 Jul 2008 11:14:52 GMT\r\n"
-> "Content-Type: application/json\r\n"
-> "Content-Length: 47\r\n"
-> "\r\n"
reading 47 bytes...
-> "{\"error\":\"conflict\",\"reason\":\"Update conflict\"}"
    … (8)
read 47 bytes
Conn keep-alive
=> false
図 2. リスト 5 の動作図
リスト 5 の動作図
リスト 5 の動作図

WikiPage#destroy メソッドについては、解説は割愛します。同じように Net ::HTTP を使用して Delete リクエストを発行するだけです。ただし、URI のクエリパラメーターにrevを指定する必要があります。実装詳細はソースコード (couchiki.zip) をダウンロードして確認してください。

ActiveRecord との違い

ActiveRecord では、migration ファイルを記述し、データベーススキーマを定義しておけばそれがモデルクラスとして利用可能でした。CouchDB ではデータベーススキーマ定義は必要ありませんが、今回はモデルクラス上に、データベース上のデータ項目を定義しました。WikiPage クラスは、データベース上のデータ項目の項目名のみの緩い定義をもち、データ操作を実装したクラスとなっています。注目すべきところは、データ項目には表に縛られない任意の構造体を使用可能な点です。例えば、新しいデータ項目としてtagsを用意して (delegateへ:tags, :tags=を追加)、

page = WikiPage.new
page.tags = [“tag1”, “tag2”, “tags3”]

の様な形で配列を格納して、データベース上に”配列のまま”保存することも可能です。実際に tags というデータ項目を追加した実装の利用方法 (特定のタグを持つページの検索) については、次回取り扱うことにします。

日本語の取り扱い

いままで説明したサンプルコードにおいて、日本語の取り扱いに関する二つの問題が存在します。修正例は割愛しますが、以下の 2 つを考慮する必要があります。

一つは、URI に渡る文字に関して URI エンコードを確実に行っていない点です。title プロパティ (CouchDB 上のURIを決定します) に日本語 (または URI エンコードが必要な文字)を渡した場合には、正しく処理できないでしょう。CouchDB では、document の id に使用できる文字に関して特に制約を設けていないようなので (データベース名に関しては使用可能な文字に制限があります)、クライアント側で URI エンコード (およびデコード) を行って対処する必要があります。

もう一つは、ActiveSupport::JSON.decode メソッドが日本語に対して不完全なことです。ActiveSupport::JSON.decode メソッドは、\uXXXX で表現される UTF8 エンコードされた JSON の文字列を正しく復元することができないので、日本語を使う場合には、ruby json ライブラリ[7] を別途導入してそちらのライブラリを使用するとよいでしょう。

コントローラーとビューの定義

ActionView, ActionController は普段通りに実装する

WikiPage クラスは、CouchDB 通信を行う単純な Ruby クラスで、Rails には殆ど依存しないコードとなっていますが、そのインターフェースは ActiveRecord によく似た形式にしています。 この実装を使うことで、Controller や View は、普通の Rails アプリケーションとして実装していくことができます。Ruby on Rails にふれたことのある人は Controller, View についても、是非ソースコードを確認してください。ここでは、既存のページを編集する画面 (edit アクション)の実装だけ紹介します。CouchDB のデータを WikiPage というクラスに押し込め、メソッドをまねることで、ActiveRecord オブジェクトを取り扱うときと同様に、form_for など、(本来 ActiveRecord を前提としている) ActionView ヘルパーメソッド が使えることがわかるかと思います。

リスト 6. WikiPage クラスを利用した Controller, View の実装
config/routes.rb
ActionController::Routing::Routes.draw do |map|
  map.resources :wiki_pages
end

app/controllers/wiki_pages_controller.rb
class WikiPagesController < ApplicationController
  ... (省略) ...
  def edit
    @wiki_page = WikiPage.find(params[:id])
  end
end

app/views/wiki_pages/edit.html.erb
<h1>ページの更新</h1>
<div><%= flash[:notice] %></div>
<% form_for(@wiki_page) do |f|%>
<div>
  ページタイトル:<%=h @wiki_page.title %>
  <%= f.hidden_field :title %>
</div>
<div><%= f.text_area  :content, :size => "80x10" %></div>
<%= submit_tag '更新' %>
<% end %>

添付コードの使い方

couchiki.zip に完全なコードをアーカイブしています。zip ファイルを展開すると couchiki ディレクトリができるので、そのディレクトリが RAILS_ROOT となります。動作を確認するには、RAILS_ROOT に移動して、以下のコマンドで環境を準備し、Rails のアプリケーションサーバーを起動してください。

$ rake couchdb:create
$ ruby script/server

これでサーバーが起動するので、Web ブラウザを使用して http://localhost:3000/wiki_pages/hoge にアクセスしてください。hoge というタイトルのページは見つからないので作成画面に遷移します (図 2)。適当にコンテンツを入力し、作成ボタンをクリックするとページが作成されるはずです (図 3)。

図 3. 存在しないページへのアクセス
存在しないページへのアクセス
存在しないページへのアクセス
図 4. WikiPage の表示
WikiPage の表示
WikiPage の表示

まとめと次回予告

今回は WikiPage モデルの実装を通じて、CouchDB の HTTP API の基本事項を学びました。CouchDB では、HTTP GET|PUT|POST|DELETE を使用してデータベース上のデータの操作を行うことができます。HTTP クライアントライブラリと JSON ライブラリを持つ言語であれば、容易に CouchDB と通信し、データを操作できることを確認できました。また、代表的なエラー処理として更新の失敗が発生するケースを取り上げました。

今回はページ一覧のような、データリストを取り扱う方法に関してはふれませんでした。次回は、データリストの参照に焦点を当て、CouchDB の特徴的な機能である MapReduce による問い合わせの使い方を紹介します。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, Open source
ArticleID=323689
ArticleTitle=Web 時代の非リレーショナルデータベース: 第 2 回 Apache CouchDB と Ruby on Rails を使って wiki アプリケーションを作成する
publish-date=08012008