Creating an API »

Created at: 01.12.2011 16:00, source: RailsTips - Home, tagged: api gauges

A few weeks back, we publicly released the Gauges API. Despite building Gauges from the ground up as an API, it was a lot of work. You really have to cross your t’s and dot your i’s when releasing an API.

1. Document as You Build

We made the mistake of documenting after most of the build was done. The problem is documenting sucks. Leaving that pain until the end, when you are excited to release it, makes doing the work twice as hard. Thankfully, we have a closer on our team who powered through it.

2. Be Consistent

As we documented the API, we noticed a lot of inconsistencies. For example, in some places we return a hash and in others we returned an array. Upon realizing these issues, we started making some rules.

To solve the array/hash issue, we elected that every response should return a hash. This is the most flexible solution going forward. It allows us to inject new keys without having to convert the response or release a whole new version of the API.

Changing from an array to a hash meant that we needed to namespace the array with a key. We then noticed that some places were name-spaced and others weren’t. Again, we decided on a rule. In this case, all top level objects should be name-spaced, but objects referenced from a top level object or a collection of several objects did not require name-spacing.

{users:[{user:{...}}, {user:{...}}]} // nope
{users:[{...}, {...}]} // yep
{username: 'jnunemaker'} // nope
{user: {username:'jnunemaker'}} // yep 

You get the idea. Consistency is important. It is not so much how you do it as that you always do it the same.

3. Provide the URLs

Most of my initial open source work was wrapping APIs. The one thing that always annoyed me was having to generate urls. Each resource should know the URLs that matter. For example, a user resource in Gauges has a few URLs that can be called to get various data:

{
  "user": {
    "name": "John Doe",
    "urls": {
      "self": "https://secure.gaug.es/me",
      "gauges": "https://secure.gaug.es/gauges",
      "clients": "https://secure.gaug.es/clients"
    },
    "id": "4e206261e5947c1d38000001",
    "last_name": "Doe",
    "email": "john@doe.com",
    "first_name": "John"
  }
}

The previous JSON is the response of the resource /me. /me returns data about the authenticated user and the URLs to update itself (self), get all gauges (/gauges), and get all API clients (/clients). Let’s say next you request /gauges. Each gauge returned has the URLs to get more data about the gauge.

{
  "gauges": [
    {
      // various attributes
      "urls": {
        "self":"https://secure.gaug.es/gauges/4ea97a8be5947ccda1000001",
        "referrers":"https://secure.gaug.es/gauges/4ea97a8be5947ccda1000001/referrers",
        "technology":"https://secure.gaug.es/gauges/4ea97a8be5947ccda1000001/technology",
        // ... etc
      },
    }
  ]
}

We thought this would prove helpful. We’ll see in the long run if it turns out to work well.

4. Present the Data

Finally, never ever use to_json and friends from a controller or sinatra get/post/put block. At least as a bare minimum rule, the second you start calling to_json with :methods, :except, :only, or any of the other options, you probably want to move it to a separate class.

For Gauges, we call these classes presenters. For example, here is a simplified version of the UserPresenter.

class UserPresenter
  def initialize(user)
    @user = user
  end

  def as_json(*)
    {
      'id'          => @user.id,
      'email'       => @user.email,
      'name'        => @user.name,
      'first_name'  => @user.first_name,
      'last_name'   => @user.last_name,
      'urls'        => {
        'self'    => "#{Gauges.api_url}/me",
        'gauges'  => "#{Gauges.api_url}/gauges",
        'clients' => "#{Gauges.api_url}/clients",
      }
    }
  end
end

Nothing fancy. Just a simple ruby class that sits in app/presenters. Here is an example of the the /me route looks like in our Sinatra app.

get('/me') do
  content_type(:json)
  sign_in_required
  {:user => UserPresenter.new(current_user)}.to_json
end

This simple presentation layer makes it really easy to test the responses in detail using unit tests and then just have a single integration test that makes sure overall things look good. I’ve found this tiny layer a breath of fresh air.

I am sure that nothing above was shocking or awe-inspiring, but I hope that it saves you some time on your next public API.


more »

Rails and Merb Merge: ORM Agnosticism (Part 5 of 6) »

Created at: 23.02.2010 20:15, source: Engine Yard Blog, tagged: Technology ActionPack ActiveModel activerecord api datamapper haml Rails 3 Railties

It’s been a little while since I’ve last posted in this series. During that time, we released Rails 3.0 beta, and announced the launch of RailsPlugins.org. Plugin authors have registered almost 150 plugins, with 40 already boasting compatibility with Rails 3. Over the next few weeks, we’re going to roll out some more features to help users find projects to help get updated, so keep an eye out.

Today I’m going to talk about the features we added to Rails 3 to provide ORM agnosticism. When we first started, the idea of such agnosticism was pretty fuzzy. As we approached the beta, we became convinced that we would shoot for the moon and give DataMapper, Sequel and other ORMs first-class access to all of the same parts of the framework that ActiveRecord had. Again, this sounds pretty fuzzy, so let me lay it out for you.

ActiveRecord is a Framework Extension

You read that right. While Rails itself ships with ActiveRecord as a default, the Railties gem, responsible for bootstrapping Rails, knows nothing about ActiveRecord. In order to achieve this, we had to make that bootstrapping process much more pluggable. While this is amazing for extensions, like DataMapper, that want to replace a built-in framework, it also exposes more functionality to any Rails extension distributed as a gem.

Rails::Railtie

In short, Railties now coordinates the initialization process, which involves the activation of a number of individual Railtie subclasses. Cute, no? Every Rails framework has its own Railtie subclass, which provides a number of useful pieces of functionality:

  • The ability to add a key (like config.active_record) to an Application’s configuration, and assign it defaults that will exist before Application configuration. For instance, a plugin could use this functionality to set a configuration key as an Array, so the Application could simply do config.action_view.view_paths << "my_path". While this isn’t exactly earth-shattering, it levels the playing field between Rails components, like ActiveRecord, and third-party extensions, like DataMapper or Haml
  • The ability to create additional generators that hook into Rails’ default generators. For instance, Rails’ scaffold generator invokes sub-generators for stylesheets, template engine, test framework, helpers, and ORM. For instance, when a user installs Haml as a gem, it can register itself as the handler for template engine generators, and provide replacements for each case where Rails provides a default implementation. In this case, Haml would replace the template engine generators provide by ActionController’s Railtie
  • The ability to supply Rake tasks to load when the user invokes the Rake command. If DataMapper supplies the same named Rake tasks as those supplied by the ActiveRecord Railtie, the user can completely remove ActiveRecord and replace it with DataMapper, retaining all of the named Rake tasks. This would allow other tasks, such as those used in testing, to automatically prepare the database and other tasks it performs by invoking tasks with certain names (like db:create)
  • Supply a log subscriber to integrate seamlessly with the uniform request logging. This allows extensions like DataMapper to add their timings into the log output. In this case, DataMapper simply replaces the “Model” timing that ActiveRecord provides, allowing a level of very tight integration with the rest of the framework
  • Specify initializers that should run at specific points in the initialization process. This allows extensions to set things up very early in the boot process, but then defer some setup until after the user has configured their application, or after specific parts of the initialization process have occurred. Each initializer also receives an instance of the Application object, giving it full access to the user’s configuration

In fact, if you take a look at ActiveRecord’s deep integration with the rest of the framework (which is roughly equivalent to its highly coupled implementation in Rails 2.3), you’ll find that it looks extremely similar to DataMapper’s equally deep integration. In fact, a major goal of the work we put into improving modularity in Rails 3 was to maintain the same level of stack integration we’ve historically had, while exposing the same toolchain for those who wanted to replace only certain elements of the stack.

The ActiveModel API

Giving DataMapper access to the same level of integration with Railties as ActiveRecord is one of two major pillars in making Rails truly ORM agnostic. The second part is decoupling Rails’ historic connection between ActiveRecord, its ORM, and ActionPack, its controller and view layer. In this area, Rails takes a holistic, very conventional approach to linking the model and the view using REST principles.

In particular, domain objects, persisted via the ORM, can have canonical URLs that are used pervasively throughout the controller and view layer. The specific URLs can be configured via the router, and Rails 3’s router is particularly powerful, but once configured, objects do have canonical URLs.

@post = Post.first
 
redirect_to @post           #=> Location: /posts/orm-agnositicism
redirect_to Post            #=> Location: /posts
 
form_for @post              #=> <form action="/posts/orm-agnosticism" method="PUT">
form_for Post.new           #=>
 
error_messages_for @post    #=> a representation of the validation errors that exist on the object
 
link_to @post.title, @post  #=> <a href="/posts/orm-agnosticism">
                            #     Rails and Merb Merge: ORM Agnosticism (Part 5 of 6)
                            #   </a>
 
posts_path                  #=> "/posts"
post_path(@post)            #=> "/posts/orm-agnosticism"
 
@posts = Post.where(:author => "wycats")
 
render @post                #=> renders "_post.html.erb", passing in @post as a local
render @posts               #=> renders a collection of "_post.html.erb", passing in each
                            #   Post instance as a local in turn
 
respond_with @post          #=> performs content negotiation between the formats
                            #   the client is willing to Accept (xml, json, html),
                            #   and the formats the @post object is willing to
                            #   provide. The available providable formats are
                            #   transparently determined by introspecting the @post
                            #   object. Additionally, if the object has not been persisted,
                            #   redirect it back to the editing page for further modification.
                            #   If it has been persisted, redirect it back to the index (or
                            #   some other appropriate path).</form>

In short, Rails 2.3 provided a fair bit of integrated functionality between ActiveRecord and ActionPack. For instance, to determine what an object’s canonical URL was, Rails called an internal .class.naming method on it, which provided the information needed to form the canonical URLs. And while other ORMs could attempt to emulate these internals, there was no guarantee that they’d be used in the same way from release to release (and, in fact, they evolved quite a bit, making them a moving target at best).

In Rails 3, we opened them up, exposing an explicit public API via ActiveModel. The ActiveModel API requires that objects respond to to_model, returning an Object that complies with the larger ActiveModel API. This allows objects that already implement methods with the same names as those required by the API to create a facade that presents itself to ActionPack as a fully compliant model.

The API itself is fairly small, with a few methods around validation, the ability to determine whether an object has persisted or not (which can be safely stubbed by objects without persistence), and a number of methods that tell ActionPack how to convert the object into a canonical URL or template name. By doing this, Rails has completely decoupled ActionPack from ActiveRecord directly, and ActiveRecord becomes just one of many ORMs to implement the ActiveModel API.

The DataMapper ORM has already released a Rails extension (called dm-rails) that implements both the Railtie functionality and ActiveModel, making it a full drop-in replacement for ActiveRecord. The Sequel ORM is also hard at work on their Railtie and ActiveModel adapter.

The really great thing about all of this is that we didn’t need to sacrifice one bit of the (some have said overly) tight integration between various parts of Rails, to provide the ability to swap in entirely different components with the same apparent “coupling” that the Rails frameworks have with each other.

As always, questions and comments are welcome!


more »

HTTParty goes foreign »

Created at: 16.03.2009 09:05, source: Robby on Rails, tagged: Ruby on Rails ruby programming google translation api httparty http REST language

Just a quick post to get share something I was tinkering with this evening.

I came across this post by Gerald Bauer, which shows you how to use the Google Translation API with Ruby via Net::HTTP. I thought I’d play with the service with HTTParty.

class GoogleApi
  include HTTParty
  base_uri 'ajax.googleapis.com'

  def self.translate(string="", to="", from="en")
    get("/ajax/services/language/translate", :query => {:langpair => "#{from}|#{to}", :q => string, :v => 1.0})
  end
end

A few examples from playing with it.

>> GoogleApi.translate('bonjour', 'en', 'fr')
=> "{\"responseData\": {\"translatedText\":\"hello\"}, \"responseDetails\": null, \"responseStatus\": 200}"

>> GoogleApi.translate('Red wine', 'fr')
=> "{\"responseData\": {\"translatedText\":\"Vin rouge\",\"detectedSourceLanguage\":\"en\"}, \"responseDetails\": null, \"responseStatus\": 200}"

>> GoogleApi.translate('Where is the bathroom?', 'es')
=> "{\"responseData\": {\"translatedText\":\"\302\277D\303\263nde est\303\241 el ba\303\261o?\",\"detectedSourceLanguage\":\"en\"}, \"responseDetails\": null, \"responseStatus\": 200}"

>> GoogleApi.translate('Good morning', 'it')
=> "{\"responseData\": {\"translatedText\":\"Buon giorno\",\"detectedSourceLanguage\":\"en\"}, \"responseDetails\": null, \"responseStatus\": 200}"

What a party!

>> GoogleApi.translate('party', 'it')
=> "{\"responseData\": {\"translatedText\":\"festa\",\"detectedSourceLanguage\":\"en\"}, \"responseDetails\": null, \"responseStatus\": 200}"
>> GoogleApi.translate('party', 'es')
=> "{\"responseData\": {\"translatedText\":\"fiesta\",\"detectedSourceLanguage\":\"en\"}, \"responseDetails\": null, \"responseStatus\": 200}"

Look how easy that was. :-)

For a previous post on using this gem, read The HTTParty has just begun.


more »