I'm new to json and experience a problem with returning. I get the error below, even though everywhere I render
, I also return
. What causes this error?
AbstractController::DoubleRenderError in Api::V1::ArticlesController#show
Render and/or redirect were called multiple times in this action.
The error refers to render json: {errors: "not authorized" }, status: :unauthorized
in def authenticate_user
below. Instead, I would expect it to just render a json error: "not authorized". Any idea why this is not happening?
before_action :authenticate
def authenticate
unless authenticated?
render json: {errors: "not authorized" }, status: :unauthorized
return
end
end
This calls on the following helper methods:
def authenticated?
!current_user.nil?
end
def current_user
token = request.headers["Authorization"]
email = request.headers["HTTP_USER_EMAIL"]
if token
user = User.friendly.find_by(email: email)
if user
if user.token_expired?
render json: {errors: "Session expired, please login" }, status: :unauthorized
return
elsif user.authenticated?(token)
@current_user = user
end
end
else
nil
end
end
Update: The solutions provided to remove return
everywhere work, but I don't understand why. Suppose the helper method looks like the one below. Then it would be important to include return
, right? because otherwise if @node isn't found, the method would still continue and the message "node not found" would not be displayed. Could you explain?
def create
@node = Node.find_by(id: params[:node_id])
if @node.nil?
render json: { errors: "node not found" }, status: :bad_request
return
end
nodee = @node.create(create_params)
if nodee.save
render json: @node, status: :created
else
render json: @node, status: :bad_request
end
end
In other words, I would have expected that removing render
from def authenticate
would result in Rails just continuing to the create
method, since def authenticate
didn't tell it to step there (which is how I see render
).
Update2: I can also not remove render
as suggested in the answers, but instead move it to the beginning of the line: return render json: {errors: "not authorized" }, status: :unauthorized
. Would love to understand why this is.
Your core issue is that current_user
is doing a render itself - this is unusual and makes it very easy to accidentally render twice. The second thing is that a before filter halts processing of the action if render or redirect are called during the filter: returning from the filter doesn't directly impact things.
Removing the return from current_user
works largely by accident: this means that the return value from current_user
is the return value from render, ie is no longer nil. This means that authenticated?
returns true (even though the user isn't authenticated) so your before filter doesn't render a second time. Rails then stops execution of the action since render was called from within a filter so it appears that things have worked.
In general though you do need to call return to stop the rest of the method executing, however this obviously doesn't stop process inside the calling method.
Personally I would leave authenticate as is but change current_user so that it doesn't have any side effects (other than setting @current_user). If you really want to change the error message then track the reason why current_user is nil in a separate instance variable (in truth I probably wouldn't be rolling my own authentication but that's another story)