ruby-on-railsjsonpdryruby-on-rails-3respond-with

How to DRY up Rails 3 controllers by overriding methods like respond_with?


I'm trying to create a JSONP API for my Rails 3 application. Right now in my controllers, I have a lot of actions which follow this pattern:

# This is from my users_controller.rb, as an example

def index
  @users = User.all
  respond_with(@users, :callback => params[:callback])
end

While this works as is, I would like to DRY it up by not having to repeat the :callback => params[:callback] in every action's call to respond_with. How can I do this?

Update: One thing I've realized that is ugly about my above code is that the :callback => params[:callback] option will be passed for any response format, not just JSON. The following code is probably more correct:

def index
  @users = User.all
  respond_with(@users) do |format|
    format.json { render :json => @users, :callback => params[:callback]}
  end
end

There are a couple ways I've considered to address this problem, but I can't figure out how to make them work:

def index
  @users = User.all
  respond_with(@users) do |format|
    format.json { render :jsonp => @users}
  end
end

Solution

  • Thanks to samuelkadolph for helping me in the #rubyonrails IRC channel today. He provided a solution in this gist, copied below for convenience:

    def custom_respond_with(*resources, &block)
      options = resources.extract_options!
    
      if options[:callback]
        old_block = block
        block = lambda do |format|
          old_block.call(format) if block_given?
          format.json { render :json => [] }
        end
      end
    
      respond_with(*(resources << options), &block)
    end
    

    I haven't tried this in my application yet, but I can see that it should work. He also confirmed that I could similarly override the respond_with method itself simply by changing the name of this method and changing the last line of the definition to super(*(resources << options), &block).

    I think this will work for me. However, I'm still interested in knowing how to write a custom responder to do the job. (It would be a more elegant solution, IMHO.)

    Update: I tried this in my application and it works with some minor changes. Here is the version I'm using now in the private section of my ApplicationController, designed to automatically provide the :callback => params[:callback] option to JSON requests:

    def custom_respond_with(*resources, &block)
      options = resources.extract_options!
    
      if params[:callback]
        old_block = block
        block = lambda do |format|
          old_block.call(format) if block_given?
          format.json { render :json => resources, :callback => params[:callback] }
        end
      end
    
      respond_with(*(resources << options), &block)
    end
    

    Note that I had to change if options[:callback] to if params[:callback] in order to get it working.