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:
1 2 class PostController < ActionController::Base 3 before_filter :website_authenticate_filter, :except => [:index, :list] 4 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.
1 2 class PostController < ActionController::Base 3 before_respond_to_filter :except => [ :index, :list ] do |format| 4 format.html :website_authentication_filter 5 format.fbml :facebook_authentication_filter 6 end 7 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:
1 2 class PostController < ActionController::Base 3 before_responds_to_filter :only => :home do |format| 4 format.html do |controller| 5 return if controller.logged_in? 6 controller.send(:redirect_to, :controller => :home) 7 end 8 format.fbml :ensure_application_is_installed_by_facebook_user 9 end 10 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:
1 2 # init.rb 3 require 'mime_responder_filter' 4 ActionController::Base.send :include, Threecglabs::Filters::MimeResponderFilter
1 2 # mime_responder_filter.rb 3 module Threecglabs 4 module Filters 5 6 # MimeResponderFilter 7 module MimeResponderFilter 8 9 def self.included(mod) 10 mod.extend(ClassMethods) 11 end 12 13 # Filters can respond to different mime types, so that you can use 14 # different filters depending on which mime type is being requested 15 # 16 # before_responds_to_filter :except => [:login, :signup, :forgot, :invite_request, :profile] do |format| 17 # format.html :authentication_filter 18 # format.fbml :ensure_application_is_installed_by_facebook_user 19 # end 20 # 21 # This way, one can take the appropriate actions in setting up authentication 22 # from different mime types, and still separate the implemenation of the different 23 # kinds of implementations 24 # 25 # The formats also take blocks, like regular filters 26 # 27 # before_responds_to_filter :only => :home do |format| 28 # format.html do |controller| 29 # return if controller.logged_in? 30 # controller.send(:redirect_to, :controller => :home) 31 # end 32 # format.fbml :ensure_application_is_installed_by_facebook_user 33 # end 34 # 35 # NOTE: an :all format defaults to :html, therefore, a format.html is required 36 module ClassMethods 37 def before_respond_to_filter(options = {}, &block) 38 before_filter MimeResponderFilter.new(&block), options 39 end 40 41 private 42 # This is a call that implements a MIME responder filter 43 class MimeResponderFilter#:nodoc: 44 attr_reader :filters 45 46 def initialize(&block) 47 @filters = {} 48 block.call(self) 49 end 50 51 def filter(controller) 52 filter = @filters[controller.request.format.to_sym] || @filters[:html] 53 if filter.kind_of?(Proc) 54 filter.call(controller) 55 else 56 controller.send!(filter) 57 end 58 end 59 60 # implements the "format.#{mime_type}" part of the filter 61 def method_missing(mime_type, method_name = nil, &block) 62 if block_given? 63 @filters[mime_type.to_sym] = block 64 else 65 @filters[mime_type.to_sym] = method_name.to_sym 66 end 67 end 68 end 69 end 70 71 end 72 end 73 end
Snippet!