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.
Related
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.
i know this error AbstractController::DoubleRenderError (Render and/or redirect were called multiple times in this action occur when render multi time
but already use solution and return
class FrequentMethodController < ApplicationController
def post_exist?(post_id)
post = Post.find_by_id(post_id)
render_json('post_not found', 400, 'msg') unless post
return post
end
def render_json(data, status_code, main_key = 'data')
render json: { "#{main_key}": data }, status: status_code and return
end
end
class PostController < FrequentMethodController
def view_post
post_id = params[:post_id]
post = post_exist?(post_id)
# test code
render_json(post, 1, 'data')
end
end
and try using render direct but not work
render :json => { :msg => 'post not found ' },:status => 400 and return
Use 'rails', '5.1.4'
class FrequentMethodController < ApplicationController
def post_exist?(post_id)
post = Post.find_by_id(post_id)
render_json('post_not found', 400, 'msg') unless post
return post # <------------------------------------------------------------ THIS
end
def render_json(data, status_code, main_key = 'data')
render json: { "#{main_key}": data }, status: status_code and return # <--- AND THIS
end
end
Rails sees a return first in the render_json method and, when it exits that method it sees another return in post_exists?
Try moving your return outside of the render_json method:
class FrequentMethodController < ApplicationController
def post_exist?(post_id)
post = Post.find_by_id(post_id)
render_json('post_not found', 400, 'msg') and return unless post
return post
end
def render_json(data, status_code, main_key = 'data')
render json: { "#{main_key}": data }, status: status_code
end
end
You could also eliminate the and return with an if statement:
class FrequentMethodController < ApplicationController
def post_exist?(post_id)
post = Post.find_by_id(post_id)
if post
return post
else
render_json('post_not found', 400, 'msg')
# if this is the only place render_json is used, I wouldn't bother making it a method
# render json: { 'msg': 'post_not found' }, status: 400
end
end
def render_json(data, status_code, main_key = 'data')
render json: { "#{main_key}": data }, status: status_code
end
end
UPDATE:
There's actually a TRIPLE return going on here when we start with PostController.
Rails visits PostController#view_post
Rails goes to FrequentMethodController#post_exists?
While still inside FrequentMethodController#post_exists?, Rails goes to FrequentMethodController#render_json? and finds return #1
Rails goes back up to FrequentMethodController#post_exists?and finds return #2
Rails goes back to PostController#view_post and sees render_json (AGAIN!!)
Rails goes to FrequentMethodController#post_exists? and finds return #3
This code is spaghetti.
If FrequentMethodController is really just a helper file, then I think it should never have a return. Keep early returns to a minimum, and only in the main controller for the model.
Early returns are helpful to clean up complicated and nested if statements and potentially make code more readable, but you don't have that problem here. In fact, all these returns have made your code brittle, unpredictable, and overly complicated.
Overall, I think FrequentMethodController is a BAD idea.
I think post_exist?(post_id) should return either true or false.
I think render_json is simple enough that it shouldn't be it's own method.
AND, you've hijacked the normal CRUD structure
I would do something this instead:
class PostController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
# changed name from view_post
def show
if #post
render #post.as_json
else
render json: { 'msg': 'post_not found' }, status: 400
end
end
private
def set_post
#post = Post.find(params[:id])
end
end
Note that the above code has NO explicit returns and requires no manual helper files.
try this
def view_post
post_id = params[:post_id]
if post = post_exist?(post_id)
# test code
render_json(post, 1, 'data')
end
end
I have a controller as
class Api::V2::Events::WegSessionsController < Api::V2::Events::ApplicationController
def synchronize
if #service.valid_connection?
#service.delay.synchronize
render json: {
status: :ok,
message: #service.message
}, status: :ok
else
render json: {
status: :unprocessable_entity,
errors: #event.errors.full_messages
}, status: :ok
end
..Service instantiation and other code
end
end
and the service class looks like
class WegService
include ActiveModel::Model
def synchronize
if event.weg_synced_at.present?
update
else
create
end
event.update(weg_synced_at: Time.current)
end
...Some code
end
But I am not able to execute this since my method #service.delay.synchronize is throwing an error.
<ArgumentError: job cannot be created for non-persisted record:
What should I do here?
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
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.