A new Rails plugin for TextLinkAds (including support for Feedvertising)

Change of Strategy

Instead of updating my constantly breaking Typo sidebar plugin, and to implement Feedvertising from TextLinkAds, I’ve changed gears and chosen to implement just a regular plugin (very close to the new way of doing sidebar plugins in typo 4.1). This approach should work in all versions of typo, as well as any other Ruby on Rails’s application.

The support for feedvertising is slightly different than the one from the WordPress plugin that is the only option offered so far, but should be fairly close.

Installation

Get the plugin from subversion:

script/plugin install http://svn.nanorails.com/plugins/textlinkads/

or to use svn:externals and get future updates via svn update

script/plugin install -x http://svn.nanorails.com/plugins/textlinkads/

The installation will copy a file textlinkads.yml into your config directory.

The file looks like this:

key: TLA_KEY
affiliateid: 0
title: Sponsors
advertisehere: Advertise here!
testing: false
caching: true

Replace TLA_KEY with the one provided by TextLinkAds, set your affiliateid if you’d like to have a link to TextLinkAds with your affiliate id (so you can get credit if someone signs up for a TextLinkAds account). Change the title and advertisehere messages if you don’t like the defaults.

Set testing to false once you’ve verified it works (while testing=true, the plugin will use a special page provided by TextLinkAds that displays to links. However, that page does not contain any RSS links)

Finally, if the caching done by Rails is not enough, the plugin can cache the calls to retrieve the links. See the caching section for explanations on how to setup caching.

Integrating with Typo

Adding the display of regular links

To add the regular TextLinkAds links, you need to add a call to render_TLA anywhere in the rendering code. In typo, the most likely place is in your template’s default.rhtml

Here’s what my template looks like


<div id="sidebar">
  ...
  <div class="sidebar-node"><%= render_TLA %></div>
  <% response.lifetime = 6.hour %>
  <%= render_sidebars %>
</div>

to replace the original:


<div id="sidebar">
  <%= render_sidebars %>
</div>

Add the RSS links

For RSS2.0 for example, edit the file app/views/xml/_rss20_item_article.rxml to add a call to render_TLA_RSS(post_id)

here’s the original file:


  xm.item do
    xm.title post_title(item)
    if this_blog.show_extended_on_rss
      content = item.full_html
    else
      content = item.body_html
    end
    xm.description content
    xm.pubDate pub_date(item.published_at)
    ...

xm.item do
  xm.title post_title(item)
  if this_blog.show_extended_on_rss
    content = item.full_html
  else
    content = item.body_html
  end
  content += render_TLA_RSS(item.id)
  response.lifetime = 6.hour
  xm.description content
  xm.pubDate pub_date(item.published_at)

Depending of which format you need, you may need to edit a different file.

That’s it.

Integrating with other apps

For other apps, just take a similar approach and add calls to render_TLA and render_TLA_RSS where most appropriate. Both calls are accessible from any Controller or Helper class.

Caching

Set “caching: true” in textlinkads.yml. Make sure Rails is configured with some caching. Typically, you need to have


config.action_controller.perform_caching = true
config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/tmp/cache"

either set from config/environment.rb or config/environment/[CURRENT ENVIRONMENT].rb

This will cache the pages where the ads appear, and cache the feed pages. But to make sure these pages will update properly if the TextLinkAds ads inventory is updated, you must ensure the cache will expire.

The best way to do that is to use the expiring_cache_action plugin. To install:

script/plugin install http://typosphere.org/trac/browser/trunk/vendor/plugins/expiring_action_cache

Then where you added calls to render_TLA, just add:

response.lifetime = 6.hour

This way, the cached page will expire 6 hours later so that would be the lapse of time to wait to see the updated ads.

No need to expire the cache for RSS, it should get expired automatically every time an article is added.

If you use this plugin in other applications, add a comment or send me a not (psq_0×40_nanorails_0×2e_com) and I’ll add a link to your instructions or code.

TextLinkAds Typo Sidebar Plugin

Based on “Displaying ads from TextLinkAds in a rails application”, here’s the first release of my typo sidebar plugin to display ads from TextLinkAds. The plugin uses typo built-in caching as I explained before.

Download

Download either textlinkadssidebar.zip or textlinkadssidebar.tgz.

Installation

Unzip (unzip textlinkadssidebar.zip) or untar (tar xzvf textlinkadssidebar.tgz) directly into the components/sidebars/ directory of your typo installation.

Configuration

Using the sidebar tab of the admin section of typo, you’ll find an Item named “Text-Link-Ads” on the left hand side.
Simply drag it to the right side where at the desired location (the higher the better!).
Enter a title for that section (here I use nanoRAILS Sponsors).
Fill in your XML KEY from the “Get ad code” section on TextLinkAds.
Enter your affiliate ID (so you can get paid for referrals).
Enter the text for the referral link (I have “Advertise on nanoRails”).
Click on the Publish Changes button.
Once you refresh your blog, you will have a TextLinkAds section.

Text Link Ads

6/15/06 update: to use in the trunk of typo (1055 currently), you will need textlinkadssidebar-1055.zip or textlinkadssidebar-1055.tgz.

The short of it is that sidebar plugins have changed quite a bit! You may be better off recreating from scratch using one of the available ones.

The longer story is that you no longer need a configure.rhtml. Instead, you use the setting helper to describe each setting. You need to subclass Sidebars::ComponentPlugin instead of Sidebars::Plugin. You also need to remove the configure method and the way you specify the display name and and the description is also done with a helper.

Oh yeah, the file content.rhtml is unchanged :D

Caching ads from TextLinkAds in rails

In the first part, I explained how you could
Display ads from TextLinkAds in a rails application.
This created the basis for today’s addition: caching.

You know that the ads won’t change that often, and saving your bandwith and CPU cycle (and TextLinkAds too) is not too much to ask.

One way to achieve that is to use the fragment cache provided by Ruby on Rails. It does not however provide expiration dates. To add expiration, you can use 2 related elements, one for storing the expiration, one to store the actual data.

Firt you need to generate the names of the 2 keys:

def fragment_key(name)
  return &#8220;TLA/TIME/#{name}&#8221;, &#8220;TLA/DATA/#{name}&#8221;
end

and here, the logic to check whether the cache is present, and has not expired.

def content
  url = &#8220;http://www.text-link-ads.com/xml.php?inventory_key=&#8221;+@sb_config[&#8216;key&#8217;]+&#8221;&referer=&#8221;+CGI::escape(@request.env[&#8216;REQUEST_URI&#8217;])
  agent = &#8220;&user_agent=&#8221;+CGI::escape(@request.env[&#8216;HTTP_USER_AGENT&#8217;])
  url_time, url_data = fragment_key(url)

  #is it time to update the cache?
  time = read_fragment(url_time)
  if (time == nil) || (time.to_time < Time.now)
    @links = request(url+agent) rescue nil
    #if we can get the latest, then update the cache
    if @links != nil
      expire_fragment(url_time)
      expire_fragment(url_data)
      write_fragment(url_time, Time.now+6.hour)
      write_fragment(url_data, @links)
    else
      #otherwise try again in 1 hour
      write_fragment(url_time, Time.now+1.hour)
      @links = read_fragment(url_data)
    end
  else
    #use the cache
    @links = read_fragment(url_data)
  end
end

In case the request to TextLinkAds fails (@links is nil), for whatever reason, you keep the cache for one extra hour till you make an other attempt.

And to make the code complete, here are the needed files to include and other needed helper functions:

require 'net/http'
require 'cgi'

def request(url)
  XmlSimple.xml_in(http_get(url))
end

# Does an HTTP GET on a given URL and returns the response body
def http_get(url)
  Net::HTTP.get_response(URI.parse(url)).body.to_s
end

So this takes care of caching the calls to TextLinkAds. You may not be done, though. Depending on the app you use, and its caching strategy, you may have another problem to solve. Your app may be caching your pages and actions, which may cause your links from TextLinkAds never to be updated. One solution is to use an approach similar to the one used by typo as described by Scott Laird and use caches_action_with_params from *scottstuff*.

This can wrap any method in your controller, and provide you the ability to control the longevity of your application’s cache simply by setting the lifetime value in the response:

response.lifetime = 6.hour

So for an application like typo, the code would be:

def content
  response.lifetime = 6.hour
  url = "http://www.text-link-ads.com/xml.php?inventory_key="+@sb_config['key']+"&referer="+CGI::escape(@request.env['REQUEST_URI'])
  agent = "&user_agent="+CGI::escape(@request.env['HTTP_USER_AGENT'])
  url_time, url_data = fragment_key(url)

  #is it time to update the cache?
  time = read_fragment(url_time)
  if (time == nil) || (time.to_time < Time.now)
    @links = request(url+agent) rescue nil
    #if we can get the latest, then update the cache
    if @links != nil
      expire_fragment(url_time)
      expire_fragment(url_data)
      write_fragment(url_time, Time.now+6.hour)
      write_fragment(url_data, @links)
    else
      #otherwise try again in 1 hour
      write_fragment(url_time, Time.now+1.hour)
      @links = read_fragment(url_data)
    end
  else
    #use the cache
    @links = read_fragment(url_data)
  end
end

As time permits, I’ll be posting my sidebar plugin for typo to provide a nice UI to set your TextLinkAds account and referral id.

Text Link Ads

Displaying ads from TextLinkAds in a rails application

Once my application with TextLinkAds was approved, I looked for a sample code to make it work in typo, or more generally in Ruby on Rails. TextLinkAds did not provide any, and a quick search in google did not return anything.

So I took their php example and came up with these snippets.

In your controller:

require &#8216;net/http&#8217;
require &#8216;cgi&#8217;

def content
  url = &#8220;http://www.text-link-ads.com/xml.php?inventory_key=&#8221;+TEXTLINKADS KEY+&#8221;&referer=&#8221;+CGI::escape(@request.env[&#8216;REQUEST_URI&#8217;])+&#8221;&user_agent=&#8221;+CGI::escape(@request.env[&#8216;HTTP_USER_AGENT&#8217;])
  @links = request(url) rescue nil
end

def request(url)
  XmlSimple.xml_in(http_get(url))
end

def http_get(url)
  Net::HTTP.get_response(URI.parse(url)).body.to_s
end

And in your view (rhtml), you just need to add:

<% if @links != nil %>
    <% if (@links != nil)&&(@links["Link"] != nil) %>
        <% for link in @links["Link"] -%>
            <li><%= link['BeforeText'][0] -%> <
                a href="<%= link['URL'][0] -%>&#8221;><%= link['Text'][0] -%></a> <%= link['AfterText'][0] -%> </li>
        <% end -%>
    <% end -%>
<% else %>
    Advertise here!
<% end -%>

To make the above look right with markdown, I had to cut the < a > element in between. When pasting this, remove the carriage return. If someone has a better idea on how to prevent the < a > element from being interpreted as a link even in a code block, please let me know!

Based on the example, it is fine to cache the result, so I’ll be adding that later tonight. As soon as I get caching up and running, I’ll also post the typo sidebar plugin

Text Link Ads

5/4/06 update: I tweaked the code to account for the no links at all case. TextLinkAds starts with a test link that they later remove, and my code broke. I replaced:

<% if @links != nil %>

by

<% if (@links != nil)&&(@links["Link"] != nil) %>