Refactoring multiple render in controller - ruby-on-rails

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.

Related

Following code still works after error render

I have a concern module as below
concerns/onboarding.rb
module Onboarding
def status(onboard, params)
if onboard.finished? && params[:process].present?
render json: { error: 'You have already finished.' }, status: :not_implemented
end
end
end
I am using it below
module MyAccount::User
class OnboardingsController < ApplicationController
include Onboarding
def update
status(current_kyc, params)
current_kyc.update!(wizard_params)
end
private
def current_kyc
#wizard ||= current_user.onboardings.where(company_id: current_company.id).last
end
def wizard_params
params.require(:onboarding).permit(:step, :finished)
end
end
end
This issue is, after render json: { error: 'You have already finished.' }, status: :not_implemented, current_kyc.update!(wizard_params) is still executed. I don't know what the issue but current_kyc.update!(wizard_params) shouldn’t be implemented if render json: { error: 'You have already finished.' }, status: :not_implemented is executed.
Calling render doesn't return or stop execution of a controller method. It only sets what should happen after the method call.
You can use performed? to check if the render method was already called, like this:
def update
status(current_kyc, params)
current_kyc.update!(wizard_params) unless performed?
end
But I am not really sure if this improves readability and makes it easier to understand what is actually going on in the controller.

undefined method `render' for module in rails

module UserCheck
def self.status(onboarding, params)
if onboarding && params[:process].present?
render json: { status: :ok }
else
render json: { error: 'You have already finished your onboarding.' }, status: :not_implemented
end
end
end
module MyAccount::User
class UserController < MyAccountController
def update
UserCheck.status(wizard_onboarding, params)
end
end
end
In the users_controller, I am using the module UserCheck to check the onboarding status and return an error in the else case. But when the else condition runs it doesn’t render json error message but instead returns the undefined method 'render' for UserCheck:Module. Could you help me fix this issue?
I would pass the controller to that method and call then render on that controller, like this:
module UserCheck
def self.status(onboarding, params, controller)
if onboarding && params[:process].present?
controller.render json: { status: :ok }
else
controller.render json: { error: 'You have already finished your onboarding.' }, status: :not_implemented
end
end
end
module MyAccount::User
class UserController < MyAccountController
def update
UserCheck.status(wizard_onboarding, params, self)
end
end
end
Personally, I see no benefit in extracting such simple code from the controller into a module. It makes the controller much harder to understand and to debug. And to understand what the controller is returning, you need to look into a different file.

Override SessionsController to refuse access based on user attribute

I have a rails 4 project, where I'm using DeviseTokenAuth.
Everything works fine, but I'd like to refuse access to user with a specific status.
So basically
if user.status == :locked => Account :unauthorized
So this is what I've done so far
class SessionsController < DeviseTokenAuth::SessionsController
def new
super
end
def create
super
render json: { error: "Account is locked MOFO " }, status: :unauthorized if current_user.status.to_sym == :locked
end
end
But when I do that I get :
AbstractController::DoubleRenderError - Render and/or redirect were called multiple times in this action. Please notethat 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".:
Any idea ?
thanks
This error happens because double render methods are called from sessions controller create method. One option is to override render_create_success method to get the desired result.
class SessionsController < DeviseTokenAuth::SessionsController
protected
def render_create_success
if current_user.status.to_sym == :locked
render json: { error: "Account is locked MOFO " }, status: :unauthorized
else
super
end
end
end

Rails render return do not stop execution

Hi inmy app I has such a situation:
in some_controller.rb I has a code aout this:
def start
method1(param)
....
if some_case
render json: {ok: "ok"}
end
end
def method1
...
if some_case
render json: {error: "Some error"}
return
end
end
The thing is when it's time to render a json with error, I get a double render error. It advices me to use render .. and return. I've tried even that, and still get this error.
Is this because render do not breaks execution itself, but just returns smth to a caller method? If it's so, what can I do in my case? The thing is method1 is actually a big method and I surely want it to be separateed from start method. And in case there are no reasons to render an error there, I want an execution of start to be continued.
Thanx!
Consider using filter instead. This works:
before_action :method1, only: :start
def start
....
if some_case
render json: {ok: "ok"}
end
end
def method1
...
if some_case
render json: {error: "Some error"}
return
end
end
When render occurs in filter, it does not run action itself then, so no double render occurs.
Use this code
def method1
if some_case
render json: {error: "Some error", status: :unprocessable_entity }
end
end

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