Skip to content

Latest commit

 

History

History
154 lines (123 loc) · 4.08 KB

2014-09-25.md

File metadata and controls

154 lines (123 loc) · 4.08 KB

Writing custom rails responders

Default rails responders are not always useful, especially when navigation locations are to be customized. Lets take an example where we are editing an user.

  class UsersController < ApplicationController
    respond_to :html

    def edit
      @user = User.find(params[:id])
      respond_with(@user)
    end

    def update
      @user = User.find(params[:id])

      user_params = params.require(:user).permit(:name)
      if @user.update_attributes(user_params)
        flash[:notice] = 'success'
      else
        flash[:error] = 'failure'
      end

      respond_with(@user)
    end
  end

If an admin is editing an user, admin will go to edit page of user, and chages fields, and clicks update. If update succeeds, it redirects to show page of user, and if it fails, it renders edit page again.

But, there are some usecases, where we dont have a form, and we just want to perform some action on user. Say, admin is seeing profile page of user, and wants to suspend this user by clicking suspend button on profile page. Code looks like this:

  # routes.rb
  #
  # resources :users do
  #   put :suspend, on: :member
  # end
  class UsersController < ApplicationController
    respond_to :html

    def suspend
      @user = User.find(params[:id])
      if @user.suspend
        flash[:notice] = 'success'
      else
        flash[:error] = 'failure'
      end

      respond_with(@user)
    end
  end

In the above example, if suspend succeeded, admin will be redirected to profile page (even though, admin is already on profile page), and if it fails, admin will be taken to edit page, which is totally not required, and doesnot make any sense for edit action. What we need here is a custom behavior, that no matter what happens to suspend action, admin should be redirected to profile page, and flash message should be shown.

This can be easily achieved by a custom responder, where we can override to_html

  # checkout actionpack/lib/action_controller/metal/responder.rb for
  # more information.
  class UserHtmlResponder < AC::Responder
    def to_html
      default_render
    rescue ActionView::MissingTemplate => e
      redirect_to navigation_location
    end
  end

  class UsersController < ApplicationController
    respond_to :html

    def suspend
      @user = User.find(params[:id])
      if @user.suspend
        flash[:notice] = 'success'
      else
        flash[:error] = 'failure'
      end

      respond_with(@user, responder: UserHtmlResponder)
    end
  end

Now, no matter what is the status of suspend, admin will always be redirected to profile page, and flash message will be shown.

Lets take this one step further. Consider suspend action is present on:

  • profile page of user, where its html action.
  • users index page, where its js action.

Now, the same action should respond to both html and js.

  • In case of html request, page should be redirected to profile page
  • In case of js action, there is no redirect, but flash should be shown on the top with status.

We can modify the responder and override both to_html and to_js

  class UserHtmlJsResponder < AC::Responder
    def to_html
      controller.flash[:notice] = options[:notice]
      controller.flash[:error]  = options[:error]

      default_render
    rescue ActionView::MissingTemplate => e
      redirect_to navigation_location
    end

    def to_js
      # we modify flash.now here so that suspend.js.erb will
      # render flash in the response.
      controller.flash.now[:notice] = options[:notice]
      controller.flash.now[:error]  = options[:error]

      super()
    end
  end

  class UsersController < ApplicationController
    respond_to :html

    def suspend
      @user = User.find(params[:id])
      if @user.suspend
        @notice = 'success'
      else
        @error = 'failure'
      end

      respond_with(@user, responder: UserHtmlJsResponder
                   notice: @notice, error: @error)
    end
  end

In rails 4.2, it seems responders are extracted into separate repo. url: responders