Rails render return do not stop execution - ruby-on-rails

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

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.

Rails update action fails to check params

I am building a Rails API and found out that put request passes without required parameters. That is weird for me as app won't allow post request without parameters. Moreover, when I’m trying to update the spending without attributes via Rails console, it fails. But via Postman/CURL request passes successfully
The controller looks like this:
class SpendingsController < ApplicationController
before_action :find_spending, only: %i[show update destroy]
def create
spending = Spending.new(spending_params)
spending.user = current_user
spending.category = Category.find_by(id: spending_params[:category_id])
if spending.valid?
spending.save
render json: SpendingSerializer.new(spending), status: :ok
else
render json: ActiveRecordErrorsSerializer.new(spending), status: :bad_request
end
end
def index
spendings = Spending.where(user_id: current_user.id).order("#{sort_spendings}")
total_value = Spending.where(user_id: current_user.id).pluck(:amount).sum
render json: {spendings: SpendingSerializer.new(spendings), total_amount: total_value}, status: :ok
end
def show
if #spending.valid?
render json: SpendingSerializer.new(#spending), status: :ok
else
render json: ActiveRecordErrorsSerializer.new(#spending), status: :not_found
end
end
def update
if #spending.valid?
#spending.update(spending_params)
render json: SpendingSerializer.new(#spending), status: :ok
else
render json: ActiveRecordErrorsSerializer.new(#spending), status: :bad_request
end
end
def destroy
if #spending.destroy
head :no_content
else
render json: ActiveRecordErrorsSerializer.new(#spending), status: :not_found
end
end
private
def spending_params
params.require(:spending).permit(:description, :amount, :category_id)
end
def find_spending
begin
#spending = Spending.find(params[:id])
rescue ActiveRecord::RecordNotFound
render json: {errors: "Spending with id #{params[:id]} not found"}, status: :not_found
end
end
def sort_spendings
sort = { sort_by: "created_at", sort_dir: "desc"}
sort[:sort_by] = params[:sort_by].split(" ").first if params[:sort_by].present?
sort[:sort_dir] = params[:sort_by].split(" ").last if params[:sort_by].present?
sort.values.join(" ")
end
end
And my model:
class Spending < ApplicationRecord
belongs_to :user
belongs_to :category
validates :description,
presence: true
end
I’m really out of ideas, why is that happening. Any guesses what can that be related to?
First thing that I noticed is your update method. You check validation before updating the model. #spending.valid? always returns true in this case. My suggestion to modify it. #spending.update(spending_params) returns true if it update is successful and false if it fails.
def update
if #spending.update(spending_params)
render json: SpendingSerializer.new(#spending), status: :ok
else
render json: ActiveRecordErrorsSerializer.new(#spending), status: :bad_request
end
end
created method an be also optimised. You don't need find and assign category separately. It will be assigned as all spending_params.
def create
spending = Spending.new(spending_params)
spending.user = current_user
spending.save
render json: SpendingSerializer.new(spending), status: :ok
else
render json: ActiveRecordErrorsSerializer.new(spending), status: :bad_request
end
end

Redirect to another page in Ruby on Rails

I want to redirect to another page admin_antenna_reader_rfids_path at the end of the create method. I did:
def create
#antenna_reader_rfid = AntennaReaderRfid.new(antenna_reader_rfid_params)
if #antenna_reader_rfid.save
render json: {status: true}
redirect_to admin_antenna_reader_rfid_path(q#antenna_reader_rfid)
else
render json: {errors: #antenna_reader_rfid.errors.full_messages, status: false}
end
end
I get an 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".
How can I solve this?
You have to remove the line render json: {status: true} as currently you're trying to make your controller render a json and redirect to an HTML page at the same time. You have to pick one.
To handle multiple request format, you can use respond_to
if #antenna_reader_rfid.save
respond_to do |format|
format.json { render json: { status: true } }
format.html { redirect_to where_you_want_path }
end
else
# same way as above
end
Within the respond_to block, you can render all the request formats as you want, then based on the request header, the controller will choose the corresponding logic to respond to you.
You can't render nor return more than once in a method.

Rails render status: :not found missing template error

While developing an app for a course,I hit upon a stumbling block:
The error screen
And here is my Stocks Controller error, where the error appears:
class StocksController < ApplicationController
def search
if params[:stock]
#stock = Stock.find_by_ticker(params[:stock])
#stock ||= Stock.new_from_lookup(params[:stock])
end
if #stock
render json: #stock
#render partial: 'lookup'
else
render status: :not_found ,nothing: true
end
end
end
On the course, they have the same code as I do,but for them, it works.The only difference I am aware of is that they are working on Rails 4(Nitrous),and that I am working on Rails 5(Mac OS X/Atom IDE/GitLab repository).Please help me if you can!Thank you in advance!
:nothing option is deprecated and will be removed in Rails 5.1. Use head method to respond with empty response body.
Try this:
render body: nil, status: :not_found
or:
head :not_found
Please don't post errors as images, copy-past the text
The problem here is that you are not rendering json in else clause and hence Rails for looking for a HTML view which is not present. To fix this, update the code something as below:
class StocksController < ApplicationController
def search
if params[:stock]
#stock = Stock.find_by_ticker(params[:stock])
#stock ||= Stock.new_from_lookup(params[:stock])
end
if #stock
render json: #stock
#render partial: 'lookup'
else
render :json => {:error => "not-found"}.to_json, :status => 404
end
end
end

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.

Resources