ActiveApi

ActiveApi allows you to define an XML schema in Ruby, and use that schema to convert ruby objects to xml.

Theory

I think that exposing your data via XML is not the job of the model. Instead, it’s the job of a class (or classes) that is specifically designed to translate your model schema into a schema appropriate for public consumption.

A good api tool will have built-in support for:

  • XSD or DTD generation

  • Versioning

  • The ability to represent your model in a way that is not tightly coupled to the model itself

ActiveApi attempts to do this.

Usage

You define a schema like so:

Schema.version(:v1) do |schema|
  schema.define :article do |t|
    t.attribute :id
    t.string    :title
    t.date      :published_on
    t.has_many  :comments
  end

  schema.define :comment do |t|
    t.belongs_to  :user
    t.string      :article_title, :value => proc{|element|
      element.object.article.title
    }
  end

  schema.define :user do |t|
    t.string :username, :value => :user_name
  end
end

To create xml from this schema:

@v1 = ActiveApi::Schema.find(:v1)
@element = @v1.build_xml [@article1, @article2], :node => :article
@element.to_xml

Which will give you xml output that looks like this:

<?xml version="1.0"?>
<articles>
  <article id="1">
    <title>target efficient applications</title>
    <published_on>2004-08-22</published_on>
    <comments>
      <comment>
        <article_title>target efficient applications</article_title>
        <user>
          <username>foo</username>
        </user>
      </comment>
    </comments>
  </article>
  <article id="2">
    <title>recontextualize viral e-services</title>
    <published_on>2004-12-05</published_on>
    <comments>
      <comment>
        <article_title>recontextualize viral e-services</article_title>
        <user>
          <username>foo</username>
        </user>
      </comment>
    </comments>
  </article>
</articles>

Extending

ActiveApi is highly extensible. The general pattern used to extend ActiveApi is to subclass the default ActiveApi implementation, and specify that you’d like to use your subclass instead.

For example, if you are working with a database that has audit columns such as timestamps, you might want to do this:

class MyDefinitionClass < ActiveApi::Definition
  def timestamps
    date_time :created_at
    date_time :updated_at
  end
end

@schema = Schema.version(:v1, :definition_class => MyDefinitionClass) do |xsl|
  xsl.define :article do |t|
    t.timestamps
  end
end

Schema.find(:v1).build_xml [@article], :node => :article

Which will produce the following xml:

<?xml version="1.0"?>
<articles>
  <article>
    <created_at>1945-12-21T00:00:00+00:00</created_at>
    <updated_at>1992-04-05T00:00:00+00:00</updated_at>
  </article>
</articles>

NOTE: when specifying custom definition classes, those classes must be loaded before calling Schema.version

You can also create custom classes for any element you define, like so:

@schema = Schema.version(:v1) do |xsl|
  xsl.define :article, :builder_class => "MyCustomClass"
end

class MyCustomClass < ActiveApi::ComplexType
  def build(builder)
    builder.send :foo, :bar => "baz" do |xml|
      xml.send :woot, "lol"
    end
  end
end

Schema.find(:v1).build_xml [@article], :node => :article

Which will produce the following xml:

<?xml version="1.0"?>
<articles>
  <foo bar="baz">
    <woot>lol</woot>
  </foo>
</articles>

NOTE: since the builder classes are evaluated at runtime, you can specify a string name for the class, and the class does not have to be loaded before calling Schema.version

Features

You define your schema completely separately from your data. So you could in theory render multiple types of objects with the same schema, provided that they have the same interface. You could also render a single object in any number of ways.

You can choose to have the builder send methods on your object, or provide more complex values by using the :value => proc{} syntax.

The element keeps track of all of it’s ancestors, so you can emit certain elements conditionally based on what has come before them.

The Schema definition just creates an array of Ruby objects, which you could use to create documentation.

You define your schema versions using whatever versioning scheme you want - could be a string, symbol or any other object. You can render the same objects with different schemas easily. This library is totally agnostic as to how or if you version your schemas.

The schema defining DSL allows you to define any valid XSL data format.

Implementation

ActiveApi uses’s Nokogori::XML::Builder to create the xml nodes. As such, the creation of the xml is fast. The rest of the code likely needs major refactoring to be performant and have a small footprint with large datasets.

Authors

While I (Jeff Dean) wrote all of the code in this repo, the code was inspired by pairing on a similar project with:

  • Mike Dalessio

  • Peter Jaros

  • Ben Woosely

Development

The basic idea here is to create a DSL that can provide:

  • A valid xml schema that you can link to and distribute

  • A means to document your code

  • A bunch of useful, time-saving ways to easily translate your model to a consumable xml document

See the issue tracker on github for a complete list.

Copyright © 2009 Jeff Dean. See LICENSE for details.