Cloudant+ActiveResource

nosqlgogoに行ってきた!

そこでCouchDBについて初めて聴き、RESTful APIを持っているならActiveResourceで接続できるんじゃないか?と思い、Cloudantの2GB無料アカウントをもらったのでブログでも作ってみようと試してみた。

で、現在挫折中。残骸はgithubにあげてある。 実際はCouchRest Modelが使えそう(教えてくれた@d6rkaizさんありがとう!)。

一応作業のメモ。これでfind, firstは使えるようになる。

  • CloudantというActiveResource::Baseを継承した抽象クラスを作り、そこでCloudantにあるCouchDBとのやりとりを仲介することにした
  • レスポンスはすべてJSONで返ってくるが、APIのURL末尾に.jsonが不要なのでcollection_path、element_path、new_element_pathから.jsonを除く
  • さらにCouchDBでドキュメントの一覧を取得するには/some_documents/all_docsというURLになるのでcollection_pathにall_docsを書き加える
  • またドキュメントの一覧を取得した場合には以下のように多段rowsというキーに対して配列
  • 各ドキュメントごとに_idというプライマリキーを持つのでself.primary_keyを書き換える。ただしActiveResource::Baseのクラス変数のprimary_keyは継承したクラスごとに設定されてしまうため、そのままでは子クラスで共通して使うことができない。そこでcattr_accessorで上書きしてから使う。
class Cloudant < ActiveResource::Base
  self.site = "https://#{CLOUDANT_USER}.cloudant.com/"
  self.user     = CLOUDANT_USER
  self.password = CLOUDANT_PASS
  self.format   = :json

  # primary_key is for each model by default,
  # so override them by one class variable
  cattr_accessor :primary_key
  self.primary_key = "_id"

  def id_from_response(response)
    return nil if response.body.empty?

    ::JSON.parse(response.body)["id"].to_i
  end

  class << self
    def collection_path(prefix_options = {}, query_options = nil)
      prefix_options, query_options = split_options(prefix_options) if query_options.nil?
      "#{prefix(prefix_options)}#{collection_name}/_all_docs#{query_string(query_options)}"
    end

    def element_path(id, prefix_options = {}, query_options = nil)
      prefix_options, query_options = split_options(prefix_options) if query_options.nil?
      "#{prefix(prefix_options)}#{collection_name}/#{URI.escape id.to_s}#{query_string(query_options)}"
    end

    def new_element_path(prefix_options = {})
      "#{prefix(prefix_options)}#{collection_name}/new"
    end

    # in cloudant response collections are wrapped in a hash, so we have to peal them
    def instantiate_collection_with_cloudant(collection, prefix_options = {})
      if collection.is_a?(Hash) && collection["rows"].present?
        rows = collection["rows"].map { |row| { "_id" => row["id"] } }
        instantiate_collection_without_cloudant(rows, prefix_options)
       else
         instantiate_collection_without_cloudant(collection, prefix_options)
      end
    end
    alias_method_chain :instantiate_collection, :cloudant
  end
end