Posts tagged with api

OmniAuth's Overzealous Approach to Facebook Auth

Call me a stickler, but I think there should be two pages that load quickest in any web app: The home page (for people not logged in) and the initial page you see once you are logged in, usually the dashboard.

The Good

Tackling the first of these two criteria, the guest home page, is fairly easy. This page could be as simple or as complex as you want. Facebook keeps it simple and static. Foursquare adds some flair. Whatever the approach may be, it's pretty easy to control the load time of the guest home page because you're likely building it from scratch.

OmniAuth had the revolutionary idea to consolidate third party methods of authentication, most using OAuth 1 or 2. But as consumers of libraries which take on such a burden, we have to be extra careful of the intricacies. For OmniAuth, one of those intricacies includes authentication with Facebook.

The Bad

When OmniAuth successfully authenticates with Facebook, somethings terrible happens: it makes a request to the Facebook Graph API...every...single...time. Not only the first time you log in with Facebook, but all subsequent times. This is because of the vast decoupling between OmniAuth and your app. OmniAuth knows nothing about your underlying data model so it can't reliably store the authenticated user's Facebook information (and know not to request it again). To provide the user's Facebook information within your success callback, OmniAuth makes a request to the Facebook API.

I think it goes without saying but this is really bad for usability. To glance at a couple problems with this approach, consider that the Graph API is down. Or, consider that it never responds at all. Or, consider that you're over your Facebook API quota. Your users will be sitting in limbo at the most critical time they're using your app, during the login process. Maybe this is their first time logging in. Making one API call could potentially make it their last. Impress users early and with OmniAuth's Facebook integration you could be missing out.

The Fix

The solution is to roll your own Authentication. Facebook's JavaScript SDK is awesome (most of the time) and you could probably integrate it within the same timeframe as you could OmniAuth but with the added benifit of a much better user experience. Unlike similar solutions to the Facebook JS SDK (Twitter @Anywhere), Facebook provides you with everything OmniAuth does, including the API access token.

Sidenote: As of this posting, OmniAuth 1.0 is currently under active development and it doesn't look like this issue has leaked into the OmniAuth Facebook Extension yet. The official release is still at 0.2.6.

Happy Facebooking!

Posted by Mike Pack on 09/21/2011 at 01:49PM

Tags: facebook, omniauth, api


FreshBooks on Rack...on Heroku - Part Three

In Part One of this series, we constructed a "hello world" Rack app. In Part Two of this series, we brought our app to life with the ruby-freshbooks library. In this part, we'll quickly finish out by deploying our app to Heroku.

For the entire source code, head to https://github.com/mikepack/freshbooks_on_rack.

Getting the app on Heroku

The next step is getting your app on Heroku. You'll need a Heroku account first, obviously, and you'll need to configure your Heroku account so that your SSH key is recognized.

First, create your git repository and commit your files:

cd fb_on_rack_dir
git init
git add .
git commit -m 'Here is the app!'

Create your Heroku app:

heroku create your-fb-heroku-app

Push your changes to Heroku:

git push heroku master

Check it out! Open your browser, head to http://your-fb-heroku-app.heroku.com and you should see your running Rack app iterating over all your projects.

Rack HTTP Basic Authentication

Most likely, with all those valuable numbers, you don't want your app exposed to the world. The most simple way to remedy this is to add HTTP Basic Auth to your app. Rack makes this dead easy.

Change your config.ru file to look like the following:

config.ru

require 'rack'
require 'fb_on_rack'

use Rack::Auth::Basic, "FreshBooks on Rack" do |user, pass|
  user == 'me' and pass == 'secret'
end

run FBOnRack.new

This will present you with the oh-so-familiar HTTP Basic Auth prompt. We've hardcoded the username and password to be me and secret, respectively. You're now protected from all those internet wanderers.

A quick note about simplicity

While I greatly appreciate elucid's work on the library, ruby-freshbooks isn't perfect. During the creation of this app, I got some unexpected values as I was iterating through result sets. For instance, one iteration would provide me with a hash and the next an array. Here's an error indicative of the inconsistent result set:

TypeError at /
cant convert String into Integer

Ruby /mnt/hgfs/share/freskbooks_on_rack/fb_on_rack.rb: in [], line 25 Web GET localhost/

If you see this error, check your result set and handle it appropriately.

In Ruby 1.9.2, I would receive the following error as I started the Rack app with rackup:

ruby-1.9.2-p136/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:36:in `require': no such file to load -- fb_on_rack (LoadError)

This error is due to changes in Ruby 1.9 require's path expectation. To fix this...

config.ru

require 'fb_on_rack'

...should look like...

config.ru

require './fb_on_rack'

I wouldn't, by a stretch, call this a production-ready app. In lieu of handling all error and usecase situations, I kept it simple. My main goal is to show you the basics of working with the ruby-freshbooks gem and the potential it holds for aggregating important data.

Happy Freshbooking!

Posted by Mike Pack on 05/12/2011 at 05:26PM

Tags: freshbooks, rack, heroku, api


FreshBooks on Rack...on Heroku - Part Two

In Part One of this series, we constructed a "hello world" Rack app, so to speak. In this part, we'll dive right into using the ruby-freshbooks gem and a little metaprogramming to keep things DRY.

Once again, for the entire source code, head to https://github.com/mikepack/freshbooks_on_rack.

Working with the ruby-freshbooks gem

ruby-freshbooks maps API calls in a somewhat object-oriented fashion. You can call #create, #update, #list, etc on a good number of API entities (like time_entry, client and staff). Check the FreshBooks API docs for a full list of available methods. Generally, the methods are called like the following:

connection = FreshBooks::Client.new('youraccount.freshbooks.com', 'yourfreshbooksapitoken')
connection.client.list
connection.staff.get :staff_id => 1

You can authenticate with your API token (as shown above) or OAuth. For instruction on authenticating with OAuth, check the ruby-freshbooks docs.

Now, lets take a look at the full Rack app that simply prints out all the projects for N number of accounts, and totals the number of hours along with the total income.

Again, FBOnRack#call is invoked upon a request to our Rack app. This method is the heart and soul of our app.

fb_on_rack.rb

require 'ruby-freshbooks'

class FBOnRack
  @cachable_entities = ['staff', 'task']

  def initialize
    @connections = [FreshBooks::Client.new('account1.freshbooks.com', 'apitoken1'),
                   FreshBooks::Client.new('account2.freshbooks.com', 'apitoken2')]
  end

  def call(env)
    res = Rack::Response.new
    res.write "<title>FreshBooks on Rack</title>"
   
    @connections.each do |connection|
      connection.project.list['projects']['project'].each do |project|
        res.write "<h1>Project: #{project['name']}</h1>"
        total_income = 0.0
        total_hours = 0.0

        connection.time_entry.list(:project_id => project['project_id'])['time_entries']['time_entry'].each do |entry|
          rate = get_rate(connection, project, entry)
          total_hours += entry['hours'].to_f
          total_income += rate.to_f * entry['hours'].to_f
        end
        res.write "Total hours: #{total_hours}<br />"
        res.write "Total income: #{total_income}<br />"
      end
    end

    res.finish
  end

private

  @cachable_entities.each do |entity_name|
    cache_var = instance_variable_set("@#{entity_name}_cache", {})
    get_entity = lambda do |connection, entity_id|
      if cache_var.has_key?(entity_id) # Check if the entity is already cached
        cache_var[entity_id]
      else
        entity = connection.send(entity_name).get(("#{entity_name}_id").to_sym => entity_id)[entity_name] # Make the API call for whatever entity
        cache_var[entity_id] = entity # Cache the API call
      end
    end
    define_method(("get_#{entity_name}").to_sym, get_entity)
  end

  def get_rate(connection, project, entry)
    case project['bill_method']
    when 'project-rate'
      project['rate']
    when 'staff-rate'
      get_staff(connection, entry['staff_id'])['rate']
    when 'task-rate'
      get_task(connection, entry['task_id'])['rate']
    end
  end
end

Lets break this down a little.

The first thing we should tackle is that strange loop at the start of the private definitions:

@cachable_entities.each do |entity_name|
  cache_var = instance_variable_set("@#{entity_name}_cache", {})
  get_entity = lambda do |connection, entity_id|
    if cache_var.has_key?(entity_id) # Check if the entity is already cached
      cache_var[entity_id]
    else
      entity = connection.send(entity_name).get(("#{entity_name}_id").to_sym => entity_id)[entity_name] # Make the API call for whatever entity
      cache_var[entity_id] = entity # Cache the API call
    end
  end
  define_method(("get_#{entity_name}").to_sym, get_entity)
end

The gist of this is to define a caching mechanism so we're not slamming the FreshBooks API. If we fetch an entity by the entity's ID, cache the result for that ID. Let's break this chunk of code down once we're inside the loop:

cache_var = instance_variable_set("@#{entity_name}_cache", {})

This does what you would expect: it defines an instance variable. @cachable_entities contains two entities we want to cache, staff and task. So in the end we have two class instance variables which act as in-memory cache: @staff_cache = {} and @task_cache = {}.

get_entity = lambda do |connection, entity_id|
  if cache_var.has_key?(entity_id) # Check if the entity is already cached
    cache_var[entity_id]
  else
    entity = connection.send(entity_name).get(("#{entity_name}_id").to_sym => entity_id)[entity_name] # Make the API call for whatever entity
    cache_var[entity_id] = entity # Cache the API call
  end
end

Here we define a closure which will fetch our result either from the cache or make a call to the API. After retrieving our entity from the API, we cache it.

define_method(("get_#{entity_name}").to_sym, get_entity)

Here we define our methods (#get_staff(connection, staff_id) and #get_task(connection, task_id)) as private instance methods. The get_entity parameter here is our lambda closure we defined above.

#get_staff and #get_task are called within our #get_rate method (but could be used elsewhere). #get_rate returns the rate which should be used for a given time entry. Rates can be project-based, staff-based or task-based. We need to find the appropriate rate based on the project['bill_method'].

Modify this code to your needs, restart your Rack server, visit http://localhost:9292/ and you should see all your projects, the total time spent on each and the total income from each.

If you've made it this far, give yourself a pat on the rear because this part in the series is definitely the hardest. Let me know if you have any issues understanding the FBOnRack class above. In Part Three of this series, we'll finish off by deploying to Heroku and baking a cake.

Posted by Mike Pack on 05/04/2011 at 11:42AM

Tags: freshbooks, rack, heroku, api


FreshBooks on Rack...on Heroku - Part One

I love FreshBooks. It makes my time tracking incredibly easy and my invoicing hassle free. That's not all; their website is extremely powerful but feels lightweight and friendly to use. As a software engineer, I really appreciate and expect my invoicing tool to be Web 2.0 and fun.

For it's free account, FreshBooks only allows you to have 3 clients. You can have any number of projects under those 3 clients but they set a cap in hopes you'll pay for their service. Well, I have more than 3 clients and I love freemium. FreshBooks allows you to have any number of freemium accounts. While it's a pain in the ass to have to switch between accounts for invoicing, it's worth the $20/month I'm saving...for now.

Another annoying thing about working with numerous freemium accounts is you can't quickly calculate numbers based on all projects you have. For instance, the total income from all projects or the total projected income for a single month. To remedy this, I wanted to create a lightweight Heroku app which would poll all my FreshBook freemium accounts and calculate some numbers, specifically the total hours spent and the total income for each project. FreshBooks has an API which allows me to do just that. +1 FreshBooks!

In this three-part series we'll build the app from the ground up, starting with Rack and then deploying to Heroku. Lets get started.

For the source of the entire project, head to https://github.com/mikepack/freshbooks_on_rack.

Rails to Rack

One consideration for this little project is the framework to use or if a framework is appropriate at all. I sought the following:

  • Rack based...It needs to run on Heroku
  • Lightweight...I don't have a model so I don't need MVC

I love frameworks. They make most arduous tasks simple. I'm a Rails programmer but I realize for a project this small, Rails is way overkill. Other potential overkill frameworks include Merb, Sinatra and even Camping. I stumbled upon Waves, a resource oriented, ultra-lightweight framework. Unfortunately, Waves has been dead since 2009. So I decided to ditch the framework and go straight to Rack.

Rack Setup

If you don't have Rack yet, install the gem:

gem install rack

Create a directory for your project:

mkdir freshbooks_on_rack
cd freshbooks_on_rack

Rack expects a configuration file to end in .ru:

config.ru

require 'rack'

You should now be able to run your Rack application from the command line:

rackup

Visit http://localhost:9292/ to see your app in action. It won't do anything yet (it'll throw an error), but the Rack basics are there.

FreshBooks Setup

FreshBooks has a great API. There's also a great corresponding gem which ruby-fies the API responses. The ruby-freshbooks gem is an isomorphic, flexible library with very little dependencies. freshbooks.rb is another FreshBooks API wrapper gem but has one major quip: it uses a global connection so you can't (naturally) pull in information from different FreshBooks accounts.

Heroku uses Bundler so create a Gemfile (you don't need the rack gem in your Gemfile):

Gemfile

source 'http://rubygems.org'
gem 'ruby-freshbooks'

Install the gem with Bundler:

bundle

Creating the Rack App

 Let's create a class which will handle our Rack implementation:

fb_on_rack.rb

require 'ruby-freshbooks'

class FBOnRack
  def call(env)
    res = Rack::Response.new
    res.write '<title>FreshBooks on Rack</title>'
    res.finish
  end
end

All our class does so far is respond to a Rack call with a title tag. I use Rack::Response here to make things a little easier when it comes to the expected return value of our #call method. Take a look at this example for a reference on how to respond without using Rack::Response.

Now lets update our config.ru file to call our FBOnRack class:

config.ru

require 'rack'
require 'fb_on_rack'

run FBOnRack.new

FBOnRack#call will now be invoked when a request is made to our Rack app. Restart your Rack app (you'll need to do this on every code change), visit http://localhost:9292/ and you should see a blank page with a title of "FreshBooks on Rack".

Tada! You've just created a sweet Rack app, you should feel proud. In Part Two of this series, we'll take a look at how to work with the ruby-freshbooks gem. We'll generate simple, yet useful data and get our hands dirty in some metaprogramming.

Posted by Mike Pack on 05/01/2011 at 02:37PM

Tags: freshbooks, rack, heroku, api