in Rails

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 ‘net/http’
require ‘cgi’

def content
  url = “http://www.text-link-ads.com/xml.php?inventory_key=”+TEXTLINKADS KEY+”&referer=”+CGI::escape(@request.env[‘REQUEST_URI’])+”&user_agent=”+CGI::escape(@request.env[‘HTTP_USER_AGENT’])
  @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) %>

23 Comments

  1. Pascal, great work! I will be contacting you about using this code on our site for other Ruby users, thanks again!

  2. You rock. This is exactly what I was looking for (and couldn’t find) this morning. Then I stumbled upon it in my feed reader. Way to go!

  3. Pascal, I don’t have much knowledge of rails, but how does the view code know to loop through the array and print out all the links?.. It looks like you’re using link[‘URL’][0], link[‘Text’][0], etc… Wouldn’t this simply print out the first link? Is some kind of a loop necessary, or does rails know to display all elements of the array in this fashion?

  4. Justin, that’s a good point.

    the code above is missing the iteration, replace:

  5. <%= link['BeforeText'][0] -%> “><%= link['Text'][0] -%> <%= link['AfterText'][0] -%>
  6. <% for link in @links["Link"] -%>

  7. <%= link['BeforeText'][0] -%> “><%= link['Text'][0] -%> <%= link['AfterText'][0] -%>
  8. <% end -%>

    Or use the code from the typo plugin, which was correct. I must have missed something while formatting the article. I’ll fix it in the post itself.

  9. Pascal, great, thanks for your quick response!

  10. I just can’t get this to work. Every time I include it in one of my controllers, I get “Application Error (Rails)” errors when trying to access any pages that calls any method in that controller.

    When I go to the production log to see what is going on, all it tells me is: “wrong number of arguments (0 for 1))”

    Any ideas?

  11. Ryan, can you tell me a bit a bit more about your setup? Maybe share some of the code. How about stack traces? Is there any besides the “wrong number of arguments…” error
    What version of rails are you using?

  12. Hi Pascal, thanks for the response.

    Our server is running version 1.0 of Rails. I did some primitive debugging, and it appears this bit of the text-link-ads code is causing the problem:

    url = “http://www.text-link-ads.com/xml.php?inventory_key=####TLA#KEY####&referer=”+CGI::escape(@request.env[‘REQUEST_URI’])
    agent=”&user_agent=”+CGI::escape(@request.env[‘HTTP_USER_AGENT’])

    I can post a stack trace, but don’t want to mess up your comments here.

  13. Ummm, I am an idiot – Pascal, can you please edit out my user key in the code above?

  14. Ryan, I’ve removed the key.
    Include the stack trace, if it messes up the comments, I’ll fix it.
    Try having 4 spaces on the left, this might trigger markdown to use the code styling.

  15. Thanks Pascal. Here is the stack trace:

    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/base.rb:902:in `request’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/base.rb:902:in `log_processing’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/base.rb:380:in `process_without_filters’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/filters.rb:377:in `process_without_session_management_support’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/session_management.rb:117:in `process’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/components.rb:71:in `process_with_components’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/components.rb:137:in `component_response’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/components.rb:108:in `render_component_as_string’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/components.rb:107:in `component_logging’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/components.rb:107:in `render_component_as_string’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/components.rb:44:in `send’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/components.rb:44:in `render_component’
    #{RAILS_ROOT}/app/views/layouts/application.rhtml:85:in `_run_rhtml_layouts_application’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_view/base.rb:314:in `send’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_view/base.rb:314:in `compile_and_render_template’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_view/base.rb:290:in `render_template’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_view/base.rb:249:in `render_file’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/layout.rb:249:in `render_without_benchmark’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/benchmarking.rb:53:in `render’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/benchmarking.rb:53:in `measure’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/benchmarking.rb:53:in `render’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/base.rb:911:in `perform_action_without_filters’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/filters.rb:368:in `perform_action_without_benchmark’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/benchmarking.rb:69:in `perform_action_without_rescue’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/benchmarking.rb:69:in `measure’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/benchmarking.rb:69:in `perform_action_without_rescue’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/rescue.rb:82:in `perform_action’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/base.rb:381:in `send’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/base.rb:381:in `process_without_filters’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/filters.rb:377:in `process_without_session_management_support’
    C:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.12.1/lib/action_controller/session_management.rb:117:in `process’
    C:/ruby/lib/ruby/gems/1.8/gems/rails-1.1.2/lib/dispatcher.rb:38:in `dispatch’
    C:/ruby/lib/ruby/gems/1.8/gems/rails-1.1.2/lib/fcgi_handler.rb:150:in `process_request’
    C:/ruby/lib/ruby/gems/1.8/gems/rails-1.1.2/lib/fcgi_handler.rb:54:in `process!’
    C:/ruby/lib/ruby/gems/1.8/gems/rails-1.1.2/lib/fcgi_handler.rb:53:in `each_cgi’
    C:/ruby/lib/ruby/site_ruby/1.8/fcgi.rb:595:in `each’
    C:/ruby/lib/ruby/site_ruby/1.8/fcgi.rb:595:in `each_cgi’
    C:/ruby/lib/ruby/gems/1.8/gems/rails-1.1.2/lib/fcgi_handler.rb:53:in `process!’
    C:/ruby/lib/ruby/gems/1.8/gems/rails-1.1.2/lib/fcgi_handler.rb:23:in `process!’
    F:/Inetpub/jnw_rails/public/dispatch.fcgi:24

  16. Ryan, just a hunch, but try renaming the request(url) method I have into something else.

  17. Thanks, Pascal, that seems to have mostly solved the problem. I am still getting an error on just on the “content” method. I get error related to this line:

    response.lifetime = 6.hour

    The error says:

    NoMethodError (undefined method `lifetime=’ for #):

  18. for response.lifetime to work, you need to be using the expiring\_action\_cache plugin. If you are not using it, you don’t need it.

    I just created a plugin to replace the old code. Should be much easier to deal with.

    to get started:

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

    The readme has some examples on how to use it.

  19. Excellent. I will give the new plugin a shot and let you know how it goes. Thanks for your help!

  20. I tried this out, but got the following error:

    ActionView::TemplateError (undefined local variable or method `links_TLA’

  21. You are welcome.

    Just to clarify on the response.lifetime. First the reason why I did not mention it. Very simple in fact. My main focus was on typo, and that plugin is included. But as you found out, is not in the default rails.

    Second, the reason why you need it. That has to do with the way rails caching works. Once a page built by a controller is rendered, and your environment is “production”, rails will cache the rendered html so next time, the page will be rendered a lot faster.

    That cache is invalidated if the model changes. In the case of the links for TextLinkAds, they may change on the TextLinkAds server, but since the page is cached, it would still display the old links (or none if you previously had none).

    That’s where expiring\_action\_cache enters the pictures. It lets you set an expiry on rails cache (in my example, I use 6 hours). So after that time, the cache will become invalid, forcing a rerendering with the latest links.

    If you are not using typo, you should be able to install that plugin using:

    script/plugin install svn://typosphere.org/typo/trunk/vendor/plugins/expiring_action_cache

  22. Ryan,

    I tried is with Rails 1.1.2 in a rthml generated from a scaffold and it works for me.

    Can you include the full stack?

    Can you tell me a bit more about your setup?

  23. I went ahead and used the original code, and just removed the line:

    response.lifetime = 6.hour

    Now everything seems to be working fine. Though there is one small thing. At present I of course have no real ads showing. But instead of showing the default link I put in the view:

    <% if @links != nil %>
    <% if (@links != nil)&&(@links["Link"] != nil) %>
    <% for link in @links["Link"] -%>
    <%= link['BeforeText'][0] -%> < a href="<%= link['URL'][0] -%>“><%= link['Text'][0] -%> <%= link['AfterText'][0] -%>
    <% end -%>
    <% end -%>
    <% else %>
    Advertise Here!
    <% end -%>

    I get a link that says “Test Link Ad” that links back to my site.

    This is my controller code at present:

    def tla_display
    url = “http://www.text-link-ads.com/xml.php?inventory_key=TLA_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 = getLinks(url+agent) rescue nil render :layout => false
    #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)
    render :layout => false
    end
    end

  24. Ryan,

    if you write the code like this, you don’t need

    @links = read_fragment(url_data)

    after the “#otherwise try again in 1 hour” line

    However, if getLinks fails, render may not have anything to render even though you may have it in the cache.

    I would move

    render :layout => false

    below the block

    if @links != nil

    else
    ….
    end

    I ‘m still interested in getting the stack trace in the plugin if you still have it. Someone else might hit the same problem!

Comments are closed.