Topic: A (mostly) resftful tags controller for multiple models

I recently started a new project and had to have tagging on multiple models. Unfortunately, the web is rife with useless and unrealistic examples of how to organize your tags_controller for one model only. So I thought I'd post mine and see what other people are doing. Plugin-wise, I'm using a slightly modified version of acts_as_taggable_on_steroids, which I've always liked and I've found is easy to integrate with will_paginate.

routes.rb

map.with_options :protocol => HTTP_PROTOCOL do |http|
    http.resources :tags, :only => [:index, :show], :requirements => {:protocol => HTTP_PROTOCOL}
    http.model_tag 'tags/:id/:model', :controller => 'tags', :action => 'model_show'
end

So we start with restful routes, but as is often the case, they only take us so far. So I added in a new named route for getting tags by model. Note that in my acts as taggable tag_list class, I am gsubbing slashes with dashes for URL integrity.

tags_controller.rb

  before_filter :find_tag, :except => :index
 
  # GET => Site tag cloud
  def index
    @tags = Tag.counts :limit => 100
  end
 
  # GET => Generic tag search that returns a preview of tagged results for all taggable models => /tags/orange
  # tag_path('orange') 
  def show
    @articles = Article.paginate(Article.find_options_for_find_tagged_with(@tag_name, :page => params[:page], :per_page => 3))
    @events = Event.paginate(Event.find_options_for_find_tagged_with(@tag_name, :page => params[:page], :per_page => 3))
  end
 
  # GET => class specific tag results => /tags/orange/articles
  # link_to_tag('orange', @article)
  # link_to_tag('orange', Article)
  def model_show
    klass = params[:model].classify.constantize
    if klass.new.respond_to?(:taggings)
      @results = klass.paginate(klass.find_options_for_find_tagged_with(@tag_name, :page => params[:page]))
    else
      raise
    end
  rescue
    redirect_to tag_path(@tag.name)
  end
 
  private
 
  def find_tag
    tag = Tag.find_by_name(params[:id])
    if tag
      @tag_name = tag.name
    else
      redirect_to :tags
    end
  end

So in the controller we have a tag cloud method (plenty of tag cloud snippets out there, and aatos includes one), a tag show page with a sampling of results for all models with that tag, and then the juicy bit: a class-specific show page, which uses the custom route and some rejigging of the passed model name.

tags_helper.rb

  

# Takes a class type or a class instance and produces a pretty name for it
  # Article => "article", Article.find(1) => "article"
  def prettify(klass)
    class_name = klass.class == Class ? klass.name : klass.class.name
    class_name.underscore.humanize.downcase
  end

# a single tag link for a model type or an instance of a model
  def link_to_tag(tag_name, taggable)
    link_to h(tag_name), model_tag_path(tag_name, prettify(taggable).pluralize)
  end


And here's where we generate class-specific model links, such as on an article show page or event show page. If you want to go to the generic tag show page, you can just use tag_path(tag_name).

I hope you find this helpful. Feedback is always (usually) enjoyed. wink

Last edited by uberllama (2009-08-03 00:40:22)