DoubleRenderError when delivering message with mail_form - ruby-on-rails

The following code sample is part of a Rails 3.2.16 app running on Ruby 1.9.3p484.
Whenever a new location is created or one is updated a message should be sent as defined in the after_filter.
class LocationController < InheritedResources::Base
respond_to :json
after_filter :notify_location_contact, only: [:create, :update]
def create
#location.user = current_user if current_user
create!
end
def update
update!
end
private
def notify_location_contact
message = MailForm.new
deliver_location_message(message)
end
def deliver_location_message(location_message)
begin
if location_message.deliver
render json: { message: "Successfully delivered" }, status: 201
else
render json: { error: "Delivery failure" }, status: 500
end
rescue => e
if e.is_a?(ArgumentError)
render json: { error: "Invalid Recipient" }, status: 422
else
render json: { error: e.message }, status: 500
end
end
end
end
The message itself is sent. Though, deliver_location_message first renders the "Successfully delivered" block and after the last block rendering the error message. This causes an internal server error:
Completed 500 Internal Server Error
AbstractController::DoubleRenderError - Render and/or redirect were
called multiple times in this action. Please note that you may only
call render OR redirect, and at most once per action. Also note that
neither redirect nor render terminate execution of the action, so if
you want to exit an action after redirecting, you need to do something
like "redirect_to(...) and return".
For sending the message the mail_form gem ~> 1.5.0 is used.
The DoubleRenderError seems to happen because create and update both render the JSON response when they finished there work. After, .deliver renders its JSON response to inform about success or failure.

As the error points out you need to return after calling render because you have multiple calls to render in your deliver_location_message(message) method. The reason for the error is because Rails continues execution until the end of the method regardless of render or redirect.
Please try the following. Note the return on each render line.
def deliver_location_message(message)
begin
if message.deliver
# Here
return render json: { message: "Successfully delivered" }, status: 201
else
# Here
return render json: { error: "Delivery failure" }, status: 500
end
rescue => e
if e.is_a?(ArgumentError)
# Here
return render json: { error: "Invalid Recipient" }, status: 422
else
# Here
return render json: { error: e.message }, status: 500
end
end
end
Alternative syntax to:
return render json: { message: "Successfully delivered" }, status: 201
is:
render json: { message: "Successfully delivered" }, status: 201 and return

Related

Rspec fails because too few arguments when rescuing error

In a system spec, I'm trying to test the correct handling of a database timeout. When that happens a new TinyTds::Error is raised.
Here my controller (EMData handles the DB connection)
class Json::ChartController < ApplicationController
rescue_from TinyTds::Error, with: :connection_timed_out
def index
data = EMData.call(params)
respond_to do |format|
format.json { render json: data }
end
end
def connection_timed_out(_error)
format.json { head :request_timeout }
end
end
Here my spec
context 'when the SQL Server connection times out' do
let(:data_class) { class_spy('EMData').as_stubbed_const }
it 'a feedback message is displayed' do
allow(data_class).to receive(:call).and_raise(TinyTds::Error.new('message'))
...
SUBMIT FORM VIA JS
...
expect(page).to have_content("Some Content")
end
The spec seems pretty straightforward to me. However, when I run it , I get
Rack app error handling request { GET /json/chart/ }
/app/controllers/json/chart_controller.rb:24:in `format' ....
Failure/Error: format.json { head :request_timeout }
ArgumentError:
too few arguments
Am I misisng anything here?
You're missing the respond_to do |format| in connection_timed_out(_error). It should be like:
def connection_timed_out(_error)
respond_to do |format|
format.json { head :request_timeout }
end
end

How to render json message before returning from the function

How can I render a json message just before return?
With the following code, it returns after the last function is completed:
def create_company
begin
company = current_user.company
result = company.create_users
render(json: {message: result[0]}, status: :ok)
company.send_email(result[1])
rescue => e
render(json: { error: e.message }, status: :unprocessable_entity)
end
end
I expected to get the rendering result and then send an email, not waiting until the email to be sent in order to render the result.
If this is a controller method, you'll want render to be the last thing you call, not company.send
def create_company
begin
company = current_user.company
result = company.create_users
company.send_email(result[1])
render(json: {message: result[0]}, status: :ok)
rescue => e
render(json: { error: e.message }, status: :unprocessable_entity)
end
end
edit
Based on your comments, I see that you want the email job to go out after the render happens. To do this, you'll need to setup an async service. Depending if you're using rails and what version, you can use ActiveJob. That in it's own right is a process to setup if you've never done it before, so you'll want to read up on it.
Your controller method would look like the following
def create_company
begin
company = current_user.company
result = company.create_users
company.send_email(result[1]).deliver_later
render(json: {message: result[0]}, status: :ok)
rescue => e
render(json: { error: e.message }, status: :unprocessable_entity)
end
end
deliver_later is an active job method that you can call when sending an email if you have active job configured.

Where vs Find in Rails Api

Let's say I have a student Rails API which having an endpoint that looks like http://www.example.com/students/1
What is the preferred way to implement?
review = Review.find(inputs[:review_id])
To handle exceptions,
rescue_from Exception, :with => :internal_error
def internal_error(e)
render json: {error: {message: "Internal Error"} }, :status => 500
end
OR
review = Review.where(inputs[:review_id]).first
if review.nil?
render json: {error: {message: "Internal Error"} }, :status => 500
end
My question is which is better way for handling non-existent id through the url.
You should go with the first approach
# reviews_controller.rb
review = Review.find(inputs[:review_id])
And
# application_controller.rb
# rescue_from Exception, :with => :internal_error
# OR Prefer ActiveRecord::RecordNotFound
rescue_from ActiveRecord::RecordNotFound, :with => :internal_error # Prefer this one
def internal_error(e)
render json: {error: {message: "Internal Error"} }, :status => 500
end
To make it generic, Add it to application_controller.rb
NOTE:
This way you don't have to rescue it in every controller (the second approach you have to)
You can add a global rescue_from in your base controller (ApplicationController for example) and then use the find method (Best way to retrieve only one record) :
rescue_from ActiveRecord::RecordNotFound do |e|
render status: :not_found, json: { error: { message: e.message } }
end
Every time you try to retrieve a record, if he doesn't exist you will render an error message and a 404 status which stand for a non-existent resource.
You should use rescue for manage error
def action_name
review = Review.find(inputs[:review_id])
render json: review, status: :ok
rescue # for ever not found
render json: {}, status: :not_found,nothing: true
end
doc for status list
and you can use rescue_from on header but this works for every action
rescue_from ActiveRecord::RecordNotFound,with: action_name
Neither. You can just do something like:
unless review = Review.find_by(id: inputs[:review_id])
render json: {error: {message: "record not found"} }, status: :not_found
end
Benefits:
Does't require endless nil checking as mentioned in comments.
Avoids unnecessary exception handling.
Returns a more informative error message.

Refactoring multiple render in controller

In my rails controller, I have to check after getting #group with before_action that this group is not system.
But I have lot's of repetition in my controller. I've tried to turn into a separate method but I get the classic :
Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".
Here is a part of my code without the separate method who give me the error.
def destroy
if #group.is_system?
render json: { errors: 'You can\'t delete a group system' }, status: 403
return
end
...
end
def update
if params[:group] && !params[:group].empty?
if #group.is_system?
render json: { errors: 'You can\'t edit a group system' }, status: 403
return
end
...
else
render json: { errors: 'Missing correct parameters' }, status: :unprocessable_entity
end
end
.....
You could have in a parent controller:
def render_errors(errors, status)
render json: { errors: Array(errors) }, status: status
end
def render_403(errors)
render_errors(errors, 403)
end
def render_422(errors)
render_errors(errors, 422)
end
then in your action:
before_action :check_system
def check_system
# I assume you already defined #group
render_403('You can\'t delete a group system') if #group.is_system?
end
Notice I changed a bit of your code: having errors key which is only a string is very misleading, should be an array.

Respond with a status unauthorised (401) with Rails 4

Given the following Rails 4.2 controller:
class Api::UsersController < ApplicationController
def index
respond_to do |format|
format.html do
flash[:error] = 'Access denied'
redirect_to root_url
end
format.json do
render json: {}, status: :unauthorised
end
end
end
end
When, with RSpec 3, I try to call this index action and expect to have the status 401 I always have the status 200.
The only moment where I got the 401 is to replace the index action content with head 401 but I would like to respond with the error 401 and also build a "nice" body like { error: 401, message: 'Unauthorised' }.
Why is the status: :unauthorised ignored ?
Use error code instead of it's name:
render json: {}, status: 401
I had to replace my controller with this following:
class Api::UsersController < ApplicationController
def index
respond_to do |format|
format.html do
flash[:error] = 'Access denied'
redirect_to root_url
end
format.json do
self.status = :unauthorized
self.response_body = { error: 'Access denied' }.to_json
end
end
end
end
Using render is not preventing the called action to be executed. Using head :unauthorized is returning the right status code but with a blank body.
With self.status and self.response_body it's working perfectly.
You can see have a look to the source code my gem where I had this issue here: https://github.com/YourCursus/fortress
Replace unauthorised by unauthorized

Resources