DZone Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

Snippets has posted 5883 posts at DZone. View Full User Profile

MIME Responder Before_filter For Rails

02.13.2008
| 7466 views |
  • submit to reddit
        This is abridged for conciseness.  For full post, visit <a href="http://webjazz.blogspot.com/2008/02/mime-responder-filter-for-rails.html">MIME Responder filter for Rails</a>

I spent last week integrating <a href="http://www.mobtropolis.com">Mobtropolis</a> with facebook. Mobtropolis doesn't require a facebook account to use it, so like other websites, it has its own authentication mechanism, something like:

class PostController < ActionController::Base
  before_filter :website_authenticate_filter, :except => [:index, :list]
end

When I started using facebooker library, it already came with an authentication before_filter. That means we have two authentication filters, one native, and one for facebook. Mobtropolis users don't have to be in facebook to use it, and facebookers don't have to sign up again in mobtropolis to use it.

However, since before_filters are executed in succession, it leads to a case where the facebook authentication would be called if html was requested, and vice versa. The alternative was to take apart both authentication filters, and create a monolithic filter to handle the two different cases. Instead, I made a before_filter respond to different MIME types.

class PostController < ActionController::Base
   before_respond_to_filter :except => [ :index, :list ] do |format|
    format.html :website_authentication_filter
    format.fbml :facebook_authentication_filter
  end
end

That way, I didn't have to mix together the guts of each authentication filter, and it solved the problem of the wrong authentication filter being run. You can also use it like:

class PostController < ActionController::Base
  before_responds_to_filter :only => :home do |format|
    format.html do |controller|
      return if controller.logged_in?
      controller.send(:redirect_to, :controller => :home)
    end
    format.fbml :ensure_application_is_installed_by_facebook_user
  end   
end

It ended up the code for this sort of magic was fairly easy. I'm not sure if there's an easier way to do what I wanted, but I'll see if Rails core people would find it useful (or not). In the meanwhile, for those of you Rubyists that have written plugins before that want to play with it. As with the usual mumbo jumbo, it's provided as is, I'm not maintaining it, and do whatever you want with it:

# init.rb
require 'mime_responder_filter'
ActionController::Base.send :include, Threecglabs::Filters::MimeResponderFilter
# mime_responder_filter.rb
module Threecglabs
  module Filters

    # MimeResponderFilter 
    module MimeResponderFilter

      def self.included(mod)
        mod.extend(ClassMethods)
      end
  
      # Filters can respond to different mime types, so that you can use 
      # different filters depending on which mime type is being requested
      #
      #   before_responds_to_filter :except => [:login, :signup, :forgot, :invite_request, :profile] do |format|
      #     format.html :authentication_filter
      #     format.fbml :ensure_application_is_installed_by_facebook_user
      #   end
      #
      # This way, one can take the appropriate actions in setting up authentication 
      # from different mime types, and still separate the implemenation of the different
      # kinds of implementations
      #
      # The formats also take blocks, like regular filters
      #
      #   before_responds_to_filter :only => :home do |format|
      #     format.html do |controller|
      #       return if controller.logged_in?
      #       controller.send(:redirect_to, :controller => :home)
      #     end
      #     format.fbml :ensure_application_is_installed_by_facebook_user
      #   end
      #
      # NOTE: an :all format defaults to :html, therefore, a format.html is required
      module ClassMethods        
        def before_respond_to_filter(options = {}, &block)
          before_filter MimeResponderFilter.new(&block), options
        end
        
        private
        # This is a call that implements a MIME responder filter
        class MimeResponderFilter#:nodoc:
          attr_reader :filters
          
          def initialize(&block)
            @filters = {}
            block.call(self)
          end
          
          def filter(controller)
            filter = @filters[controller.request.format.to_sym] || @filters[:html]
            if filter.kind_of?(Proc)
              filter.call(controller)
            else
              controller.send!(filter)
            end
          end
          
          # implements the "format.#{mime_type}" part of the filter
          def method_missing(mime_type, method_name = nil, &block)
            if block_given?
              @filters[mime_type.to_sym] = block
            else
              @filters[mime_type.to_sym] = method_name.to_sym
            end
          end
        end
      end

    end
  end
end

Snippet!