MIME Responder before_filter for Rails
I spent last week integrating Mobtropolis 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!