Wednesday, 21 June 2006

How to register a new MIME type in Rails

« eclipse 3.2 | Main | Strange interaction between Mozilla and WEBrick »

It's almost summer and it's a lot better playing volleyball under the sun than writing about technical stuffs. Anyway I've a blog and I must feed it so I'll try to add a post every one/two months. Hence, this is for June and July :-) .

The REST web-service support in Rails 1.1 is nice and powerful but how to add a new MIME type management, specifically a text/json [1] one? I haven't found any documentation about this and so I dug into the source code searching for a solution. I ended up with the following (after not few troubles):

require 'json'

# register a new Mime::Type
Mime::JSON = Mime::Type.new 'text/json', :json
Mime::LOOKUP[Mime::JSON.to_str] = Mime::JSON

# its default handler in responder
class ActionController::MimeResponds::Responder
  
  DEFAULT_BLOCKS[:json] = %q{
    Proc.new do 
      render(:action => "#{action_name}.rjson", :content_type => Mime::JSON, :layout => false) 
    end  
  }  
  
  for mime_type in %w( json )
    eval <<-EOT
      def #{mime_type}(&block)
         custom(Mime::#{mime_type.upcase}, &block)
      end
    EOT
  end
end

# its param parser
ActionController::Base.param_parsers[Mime::JSON] = Proc.new do |data|
  {:resource => JSON.parse(data)}
end

require 'json' is for the json library by Florian Frank.

Note that inserting the new Mime::Type into the LOOKUP Hash is fundamental because otherwise:

  1. one can't register the param parser using the constant but has to write ActionController::Base.param_parsers[Mime::Type.lookup('text/json')] = ... ;
  2. a lot more subtle, registering the param parser with this last instruction create a completely new Mime::Type object o and o.hash == Mime::JSON is false, consequently the responder isn't able to find a match!
    In fact the class Mime::Type doesn't opportunely redefine the Object#hash method though it modifies Object#eql? in order to make true o.eql? Mime::JSON. I think that this isn't appropriated: [...] must have the property that a.eql?(b) implies a.hash == b.hash [...] (from Ruby core RDoc documentation)

Now in controllers you can write something like:

  respond_to do |wants|
    wants.json 
    wants.xml
    wants.html
  end

Moreover, if your request has the right content type you'll find in param[:resource] a Ruby Hash corresponding to your JSON object.

So far, so good but I wonder if there isn't a cleaner way to do this ...

[1] actually, the official MIME type for JSON is application/json but dojo recognizes text/json. Anyway my troubles with dojo are material for another post that maybe I'll write in August ;-) .

Posted by Nicola Piccinini at 8:32 AM CEST in devel/
Pingbacks:
2006-Sep-10 CEST: Superfluo
2007-Jan-23 CET: Dewavrin Luc &#8217;s web site
2007-Jun-04 CEST: Dewavrin Luc &#8217;s web site &raquo; Gwt client with rails backend integration tip

Comments on this entry:

Left by Deepak at 23 Sep 4:09 AM

Just a question. Where do I add this code? I tried adding it to the controller, but the first time I run the controller, Rails throws a error.

I was thinking of putting it in environment.rb. Just wondering if there is a better place.

Left by pic at 23 Sep 7:09 AM

indeed I put it in environment.rb . For better order, you can require it from an external file (I believe)