简体   繁体   中英

How to simplify large respond_to blocks in Rails

Does anyone have recommendations for how I can reduce respond_to blocks? It seems like my JSON formatting takes up a considerable amount of space. The controller, for the most part, responds with HTML but this specific method is called via Ajax and responds in JSON:

def create
  # initial setup

  respond_to do |format|
    unless paid_cash == true || PayPalPayments::OrderValidator.call(order_id)
      format.json do
        render json: {
          status: :unhandled_error,
          message: 'Invalid order ID supplied?'
        }, status: 400
      end
    end

    if @submitted_application.save(context: :create)
      MembershipMailer.with(application: @submitted_application).signup_confirmation.deliver_later

      format.json do
        render json: {
          status: :created,
          modal: render_to_string(
            partial: 'membership_confirmation_modal.html.erb'
          )
        }
      end
    else
      format.json do
        render json: {
          status: :validation_errors,
          errors: @submitted_application.errors
        }, status: 400
      end
    end
  end
end

If you're doing a lot of JSON responses, like you're building a JSON API, then it would make sense to make a method to simplify that pattern. For example, create a method like this:

def respond_json(content)
  status = content[:status]

  render(
    json: content,
    status: STATUS_CODE_REMAPPED[status] || status
  )
end

Where that works with the structure you've established and uses it to generate the correct render call. As this only works with data structured a specific way, it helps enforce consistency in your responses.

This depends on mapping the internal codes to the Rails response codes:

STATUS_CODE_REMAPPED = {
  created: :ok,
  unhandled_error: :bad_request,
  validation_errors: :bad_request
}

Where using the symbol codes helps the code become more self-explanatory.

Another thing to note is your order verification could be extracted into a before_action handler:

before_action :verify_order_id, only: [ :create ]

def verify_order_id
  return if paid_cash || PayPalPayments::OrderValidator.call(order_id)

  respond_json(
    status: :unhandled_error,
    message: 'Invalid order ID supplied?'
  )
end

Where if that handler renders something the chain stops as the request is considered serviced.

This dramatically reduces how much code is left in the controller action:

def create
  @submitted_application.save!(context: :create)

  MembershipMailer.with(application: @submitted_application).signup_confirmation.deliver_later

  respond_json(
    status: :created,
    modal: render_to_string(
      partial: 'membership_confirmation_modal.html.erb'
    )
  )

rescue ActiveRecord::RecordInvalid
  respond_json(
    status: :validation_errors,
    errors: @submitted_application.errors
  )
end

I've used save! here so that the expected path is much simpler, there's no branching. If/when an error occurs then you can go off into the exception handling area.

Consider adding a before_action to validate that the requestor wants JSON and handle it there instead of stubbing in lots and lots of respond_to calls in your controller actions.

Don't forget about rescue_from which can blanket rescue from common problems like invalid request types and so on. This can also reduce how much repetitive code you have to do.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM