The Adapters

Adapters are gems named ambitious-something, where something corresponds to the data store they are adapting. They can be required in code via ambition/adapters/something.

To install and test the ActiveRecord adapter:

$ gem install ambitious-activerecord
$ irb 
>> require 'rubygems'
>> require 'ambition/adapters/active_record'

Adapters typically inject themselves into their target automatically, so that should be all you need.

There are a few adapters in development or released currently:

If you’re interested in writing your own adapter, read on.

The Anatomy of an Adapter

Ambition adapters consist of two parts: Translators and the Query. Translators are used to translate plane jane Ruby into strings while the Query is used to build and execute a query from those strings.

The three translators are Select, Slice, and Sort. Their names correspond to the API method they represent. Each translator consists of methods which convert passed arguments into a string.

Here’s how the ActiveRecord adapter maps translator classes to SQL clauses:

Your translators and the Query have three special methods available at all times:

owner is the class from which the request was generated. { |u| == 'Pork' }
# => owner == User

clauses is the hash of translated string arrays, keyed by processors. { |u| == 'Pork' }
# => clauses ==  { :select => [ " = 'Pork'" ] }

stash is your personal private stash. A hash you can use for keeping stuff around. Translators are free to set things which can later be picked up by the Query class.

For instance, the ActiveRecord adapter’s Select translator adds to the stash[:include] array whenever it thinks it needs to do a join. The Query class picks this up and adds it to the hash it feeds find(:all). { |u| == 'Pork' }
# => stash == { :include => [ :profile ] }

stash is basically a way for your translators to talk to each other and, more importantly, to the Query.

The Query is what kicks off the actual data store query, after all the translators have done their business. Its clauses and stash hashes are as full as they will ever be.

The kicking is done by one of two methods:

While two other methods are generally expected to turn the clauses hash into something semantically valid:

So, for instance, Query#kick may look like this:

def kick
  owner.find(:all, to_hash)

A straightforward translator/query API reference can be found on the api page.

The easiest way to understand translators is to check out the source of the existing adapters or by using the adapter generator.

The Adapter Generator

Ambition ships with an ambition_adapter script which can generate an adapter skeleton. Built using Dr Nic’s Rubigen, it spits out all the files, tests, and Rakefiles your adapter needs.

Run it:

$ ambition_adapter flickr
      create  lib/ambition/adapters/flickr
      create  test
      create  lib/ambition/adapters/flickr/base.rb
      create  lib/ambition/adapters/flickr/query.rb
      create  lib/ambition/adapters/flickr/select.rb
      create  lib/ambition/adapters/flickr/slice.rb
      create  lib/ambition/adapters/flickr/sort.rb
      create  lib/ambition/adapters/flickr.rb
      create  test/helper.rb
      create  test/select_test.rb
      create  test/slice_test.rb
      create  test/sort_test.rb
      create  README
      create  Rakefile

Presto, you’ve got a ready and willing adapter skeleton in place now. Check out the comments and you’re on your way.

The Flow: Ambition + Adapters

Let us examine the flow of a typical call, using the ActiveRecord adapter for reference.

The call: { |u| == 'Chris' && u.age == 22 }.to_s

The first few steps:

This processing is the real meat. Ambition will instantiate your Select translator and pass values to it, saving the return value of these method calls. returning "( = 'Chris' AND users.age = 22)"

At this point we leave adapter-land. The final string is stored in the clauses hash (available to your Query) by the context. The clauses hash is keyed by the translator name—in this case, :select.

The final string is then returned:

"SELECT * FROM users WHERE ( = 'Chris' AND users.age = 22)" 

And that’s all there is to it. Except, of course, for the api page.