Pushrod

Old dogs, new tricks

Posts Tagged ‘classic cars

Facebook templates made easy with Rails 2.0 custom Mime types

with 3 comments

I’ve written elsewhere about how I used my own lightweight library to add Facebook functionality to Autopendium :Stuff About Old Cars, the classic car community website I run.

The library has made it fairly easy to keep up with Facebook’s many changes, and the Facebook app has been a good marketing tool for the site. But adding more functionality to the app has meant duplicating code, as all the actions are handled by a FacebookController.

However, now that I’ve updated to Rails 2.0, adding Facebook functionality is a whole lot easier, and the solution is so simple, I’m sure it’s a common usage pattern.

Let’s take the Autopendium classic car events calendar, which we’ve just introduced:


I don’t really want to do an events action in the FacebookController just to make it available in the Facebook app; I’d rather just use the EventsController#index action and render it with a custom template. (We’re already doing something similar for ics MimeTypes — serving up the events in iCalendar format, so they can be imported directly into your electronic calendar, but that’s for another post).

Custom Mime Types to the rescue

With the new custom Mime types in Rails 2.0, it’s a breeze. Often these are used for customising apps for the iPhone (as shown here), but I reckoned the situation was pretty similar with integrating Facebook interfaces to existing apps.

If the request comes in via the the Facebook canvas we need all the custom Facebook FBML to specify style, etc (and even if you’re using an iframe, you’ll probably want a custom layout).

So this is what I did. First, add a custom facebook mimetype in your environment file:

 Mime::Type.register_alias "text/html", :facebook

Then you need to some way to recognize you’ve received a request from your Facebook app.

You could do a check on the params, as all requests from Facebook have a number of Facebook-specific parameters (fb_sig, etc, which is covered briefly in the second part of my Facebook lightweight library posts). This has the added advantage that you can use the normal URLs in your facebook templates/links. However, if you’re using REST-type routes — as I am — you may end up with difficulties for the moment, as all request from Facebook are POSTs. (According to FB, this may change in the future and already there’s a parameter in the request which says what method the original request was.)

A simpler way is to use the routes (or possibly a subdomain, as shown in the iPhone example). I’ve already got the Autopendium FB app set up so that all request from Facebook have a base URL of autopendium.com/facebook/ (i.e. what Facebook calls the callback URL) . This normally sends everything to the Facebook controller, so /facebook/latest goes to the #latest action in the FacebookController. However, if I add a couple of line to my routes.rb file:

map.connect 'facebook/:controller', :format => 'facebook', :action => 'index'
map.connect 'facebook/:controller/:id', :format => 'facebook', :action => 'show'

… I get facebook/events (which is generated by a link in the Facebook canvas of apps.facebook.com/autopendium/events routing to the index action in the Events controller with our custom :facebook format. Likewise facebook/events/3 will route to the show action in the Events controller with an :id of 3 in the params hash.

Then in my Events controller, I just add an additional line to the respond_to block:

respond_to do |format|
format.html # index.html.erb
...
format.facebook # index.facebook.erb
end

This means the response for the Facebook request will be served using a special facebook template, with no render :template => “special_facebook_template” needed.

Classic Car Events in Facebook

Even better, if it will automatically use a custom facebook application layout if it exists (called application.facebook.erb). So all your standard links, frame, css can be included without a single extra line. Mine looks something like this:

<%= stylesheet_link_tag "facebook_basic" %>
<fb:header decoration="add_border">Autopendium :: Stuff about old cars</fb:header>
<br />
<fb:tabs>
<fb:tab_item href="http://apps.facebook.com/autopendium" title="Intro">Intro</fb:tab_item>
<fb:tab_item href="http://apps.facebook.com/autopendium/show" title="My Autopendium">My Autopendium</fb:tab_item>
<fb:tab_item href="http://apps.facebook.com/autopendium/events" title="Classic Car Events">Classic Car Events</fb:tab_item>
</fb:tabs>
<div class="container">
<%= yield  %>
</div>
Advertisements

Written by ctagg

April 12, 2008 at 12:54 pm

A RubyonRails library for the ebay shopping API

with 21 comments


After the lightweight Facebook library I wrote to scratch my own itch, a couple of days ago I started to look at adding ebay items to Autopendium :: Stuff About Old Cars, the classic car website I run. Users were already shown books from Amazon, appropraite to the content being shown on the page, and it seemed to make sense to show models, cars and parts from ebay, for the vehicle or model being displayed.

Amazon books on Autopendium

I’d had a look at adding ebay functionality quite a while back, when I’d first just started to use Ruby and Rails, and couldn’t quite get to grips with ebay4r, which was at that time the ebay API ruby library. Since I’d last looked, another library had been written, Cody Fauser’s ebayapi, which he introduces with a brief tutorial here, and having a quick look at the code and the tests, it seemed just the job. I then fired up IRB and and gave it a test drive in the console.

It all seemed fine, just rather slow. The problem is, the library uses ebay’s SOAP interface, which is markedly slower than the REST one. And in fact, even the Trading REST interface is slower than the Shopping interface, as a quick and dirty benchmark shows:


  user       system       total      real
0.050000    0.030000    0.080000  (  6.487812) # 10 calls to the shopping REST API
0.130000    0.060000    0.190000  ( 12.517658) # 10 calls to the trading REST API

Now, if you want all the functionality that the Trading API provides — the ability to bid on items, or to list new items — that speed trade-off is no problem, as the user will expect such things to take a couple of seconds.

But if you’re wanting to include items for sale on a page each time it’s displayed (even allowing for caching), each 1/10th of a second counts, and the extra functionality that the Trading API provides is irrelevant.

Unfortunately, there’s no Ruby or Rails library for the Shopping API. So, time to scratch my own itch again. Enter ebay-shopping, a RubyonRails plugin for the ebay Shopping API. It’s a pretty straightforward plugin that was fairly easy to write (the first version, implemented as a basic lib file, was done in an afternoon), and is even easier to use.

To install, from the root of your rails app simply run the usual

script/plugin install http://ebay-shopping.googlecode.com/svn/trunk/ ebay_shopping

Then run

ruby vendor/plugins/ebay_shopping/install.rb

This will copy a basic configuration file into your app’s config directory. This is where you put your ebay settings (Ebay Application id, affiliate info if you have it, etc). Update this with your settings — the only thing you actually need is the app id, which you can get by signing up at http://developer.ebay.com (The code you need is called the AppID — the Auth Token and other stuff is for the Trading API).

Then fire up the Rails console and away we go:


>> request = EbayShopping::Request.new(:find_items, :query_keywords=>"Cadillac")
=> #<EbayShopping::Request:0x246aa54 @affiliate_shopper_id="my_campaign", @affiliate_partner="1",
@site_id=nil,@affiliate_id="foo1234bar", @callname=:find_items, @call_params={:query_keywords=>"Cadillac"},
@app_id="my_ebay_app_id_1234567">

>> response = request.response
=> #<EbayShopping::FindItemsResponse:0x2444520 @request=#<EbayShopping::Request:0x244a36c,
@url="http://open.api.ebay.com/shopping?version=547&appid=my_ebay_app_id_1234567&callname=FindItems&QueryKeywords=Cadillac",
@affiliate_shopper_id="my_campaign", @affiliate_partner="1", @site_id=nil, @affiliate_id=nil, @callname=:find_items,
@call_params={:query_keywords=>"Cadillac"}, @app_id="my_ebay_app_id_1234567",
@full_response={"Version"=>"547", "Timestamp"=>"2008-01-13T13:20:27.641Z", "Build"=>"e547_core_Bundled_5879814_R1",
"Item"=>[{"ShippingCostSummary"=>{"ShippingType"=>"NotSpecified"}, "ListingStatus"=>"Active", "TimeLeft"=>"P20DT16H59M6S",
"PrimaryCategoryName"=>"eBay Motors:Cars & Trucks:Cadillac:STS", "Title"=>"Cadillac : STS",
..."ItemSearchURL"=>"http://search.ebay.com/ws/search/SaleSearch?fsoo=2&fsop=1&satitle=Cadillac",
"Ack"=>"Success", "TotalItems"=>"15580", "xmlns"=>"urn:ebay:apis:eBLBaseComponents"}>

>> response.total_items
=> 15580

To get the items from the response, just ask for them


>> first_item = response.items.first
#<EbayShopping::Item:0x2413a88 @gallery_url="http://thumbs.ebaystatic.com/pict/230212386614.jpg",
@all_params={"ShippingCostSummary"=>{"ShippingType"=>"NotSpecified"}, "ListingStatus"=>"Active",
"TimeLeft"=>"P20DT16H59M6S", "PrimaryCategoryName"=>"eBay Motors:Cars & Trucks:Cadillac:STS",
"Title"=>"Cadillac : STS", "ConvertedCurrentPrice"=>{"currencyID"=>"USD", "content"=>"9500.0"},
"GalleryURL"=>"http://thumbs.ebaystatic.com/pict/230212386614.jpg", "ItemID"=>"230212386614",
"ListingType"=>"FixedPriceItem", "EndTime"=>"2008-02-03T06:19:33.000Z", "PrimaryCategoryID"=>"124117",
"ViewItemURLForNaturalSearch"=>"http://cgi.ebay.com/Cadillac-STS_W0QQitemZ230212386614QQcategoryZ124117QQcmdZViewItem"},
@view_item_url_for_natural_search="http://cgi.ebay.com/Cadillac-STS_W0QQitemZ230212386614QQcategoryZ124117QQcmdZViewItem",
@end_time="2008-02-03T06:19:33.000Z", @primary_category_name="eBay Motors:Cars & Trucks:Cadillac:STS",
@converted_current_price={"currencyID"=>"USD", "content"=>"9500.0"}, @title="Cadillac : STS",
@item_id="230212386614", @time_left="P20DT16H59M6S">

The key attributes for the item are available through ruby-ized version of the ebay Attributes (full documentation for the Shopping API calls and responses)


>> first_item.title # for the Title attribute
=> "Cadillac : STS"
>> first_item.gallery_url # for the GalleryURL attribute
=> "http://thumbs.ebaystatic.com/pict/230212386614.jpg"
>> first_item.view_item_url_for_natural_search # for the ViewItemURLForNaturalSearch attribute
=> "http://cgi.ebay.com/Cadillac-STS_W0QQitemZ230212386614QQcategoryZ124117QQcmdZViewItem"
>> first_item.bid_count
=> nil
>> first_item.primary_category_name
=> "eBay Motors:Cars & Trucks:Cadillac:STS"

As you can see, most of these responses are just strings. For the price, you’ve got a couple of options


>> first_item.converted_current_price
=> #<EbayShopping::Money:0x1410b70 @content=9500.0, @currency_id="USD">
>> first_item.converted_current_price.content
=> 9500.0

or


>> first_item.converted_current_price.to_s
=> "$9500.00"

It’s also worth noting the end time is returned as a Ruby Time object, so you can do calculations against it


>> first_item.end_time
=> Sun Feb 03 06:19:33 GMT 2008
>> first_item.end_time.class
=> Time

Finally, there’s a catch_all [] method which allows you to access other attributes using a familiar hash key notation:


>> first_item["ShippingCostSummary"]
=> {"ShippingType"=>"NotSpecified"}

Other methods and usage are given in the code comments and the fairly extensive test suite (browse the source here). There are also hooks to allow for caching and (separately) error caching, which is necessary if you want to get your app approved as a Compatible Application, which allows you a greater number of API calls per day (I did). I’ll post on usage of these and examples if anyone wants me to.

Tie that into your Rails app, and you’ve got an instant mash-up:
Ebay items on Autopendium

At the moment, the library’s only available as a RubyonRails plugin, rather than a Ruby gem. The only reason for this is that it was extracted from a Rails app, and is slightly structured accordingly (e.g. the YAML config file, and option for different settings in different environments). However, it’s probably not a huge job to package it as a gem, or to use the code as is in a standalone Ruby app.

p.s. Some of the less frequently used API calls haven’t yet been implemented, but are being done bit by bit, and if anyone’s got a crushing need for one of the missing ones, let me know, and I’ll bump it up the priority list.

Written by ctagg

January 13, 2008 at 5:47 pm