When you use Ruby to wrap an API, you have to have a way to configure it. Maybe the wrapper needs a username and secret key, or maybe just a host.
There are a few different ways to handle this. So which one should you choose?
The easy, global way
You might want your service to act like it’s always around. No matter where you are in your app, you’d have it ready to use. Otherwise, you’ll spend three lines of configuring it for every line of using it!
You could make the configuration global, using constants or class attributes:
Lots of gems use this pattern. It’s pretty easy to write, and really easy to use. But it has some big problems:
-
You can only have one
ProductApi
.If you want to use the Product API as two different users, or hit different servers from a single app, you’re out of luck.
-
ProductApi
has global data that’s easy to accidentally change.If a thread or a part of your app changed
ProductApi.user
, everything else usingProductApi
would break. And those are painful bugs to track down.
So, class variables have some problems. What if you configured instances of your Product API class, instead?
What would it look like with #initialize
?
If you used instances, you’d create and configure your API wrapper when you need it:
Now, you can pass different details to your API whenever you use it. No other methods or threads are using your instance, so you don’t have to worry about it changing without you knowing it.
This seems better. But it’s still not as easy as it should be. Because you have to configure your API every time you use it.
Most of the time you don’t care how the API is set up, you just want to use it with sane options. But when you’re working with instances, every part of your app that uses the API has to know how to configure it.
But there’s a way to get the convenience of global access, using good defaults, while still being able to change it if you need to.
And this pattern shows up all the time in an interesting place: OS X and iOS development.
How do you get good defaults and flexibility?
What if you could configure each instance of your API wrapper, but you also had a global “default” instance when you just didn’t care?
You’ll see this “defaultSomething” or “sharedWhatever” pattern all over the iOS and Mac OS SDKs:
And you can still ask for instances of these classes if you need more than what the default gives you:
You could build something like that in Ruby, with a default_api
class method:
And the implementation might look something like this:
Here, I used environment variables in default_api
, but you could also use config files. And you could switch the ||=
to use thread- or request-local storage instead.
But this is a decent start.
Most gems I’ve seen, like the Twitter gem, will have you configure and create each API object when you need them. This is an OK solution (though I usually see people assigning these to globals anyway).
But if you go one step further, and also use a pre-configured default object, you’ll have a much more comfortable time.