Geek Blog for Little Red String

Geek stuff, amature programming, Linux, Ruby, open source

Absolute urls in Rails – polymorphic_url and a Custom Helper

I was trying to figure out how to cleanly generate absolute URLs from resources in my RSS feed and thought I would put this together. There are several options for generating absolute URLs in different situations. This is probably not exhaustive.

1) resource_url(resource)

Use the resource_url method (rather than the resource_path method). This is standard Rails stuff (api reference). Use this first, but for my situation I didn’t want to specify what kind of resource I was using since my feed handles objects from a couple of different models.

2) :path_only => false

Use the :path_only => false option with url_for (and subsequently link_to). You can read about url_for in the api. I was hoping I could do something like the following.

url_for(@feed_item, :path_only => false)

This doesn’t work because url_for takes one argument. That argument can be a lot of things (a hash, an array, a resource object) but you have to pick one.

You could use the :path_only => false option like this:

url_for(:controller => 'posts', :action => 'show', :id => 4, :path_only => false)

But I haven’t done something like that since the rails 1.x days and I’m not about to go back.

3) Use polymorphic_url

I haven’t seen this much, but it does exactly what I wanted for my RSS feed:

# In my view:
polymorphic_url(feed_item)
#=> http://127.0.0.1:3000/songs/15-coveted

Perfect.

4) Custom Helper Method

Next I added an “enclosure” tag when the RSS item is a song. This makes the feed a little podcasty so that someone could subscribe to it with a podcast client and Google Reader will give you a nice little player.

I’m using paperclip to take care of my attachments. Unfortunately the paperclip attachment#url method only spits out a path and not an absolute urls (which are required for valid enclosure tags). With a heavy heart I added a helper method.

module UrlHelper
# ... 
  def path_to_url(path)
      request.protocol + request.host_with_port + path
  end
end

So the final view I ended up with was this.

# in /feeds/show.xml.builder
xml.rss(:version => 2.0, "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
  xml.channel do
    xml.title       "Little Red String RSS Feed"
    xml.link        "http://littleredstring.com/feed"
    xml.description "The recent songs and posts from Little Red String."
    xml.language    "en-us"
    xml.ttl         40
    for feed_item in @feed_items
      xml.item do
        xml.title       feed_item.title
        xml.description feed_item.description_html
        xml.pubDate     feed_item.published_at.to_s(:rfc822)
        xml.guid        polymorphic_url(feed_item)
        xml.link        polymorphic_url(feed_item)
        xml.enclosure   render_rss_enclosure(feed_item) if has_enclosure_file?(feed_item)
      end
    end
  end
end

# in feed_helper.rb
module FeedHelper
  # These methods only handle mp3s.  In the future we may want to have enclosures
  # with video as well.  

  def render_rss_enclosure(feed_item)
    file = feed_item.mp3
    {:url => path_to_url(file.url), :length => file.size, :type => file.content_type}
  end

  def has_enclosure_file?(feed_item)
    feed_item.respond_to?(:mp3?) && feed_item.mp3?
  end
end