I have a controller method as follows
def create
#game = Game.create_new_game(game_params)
render 'show', status: 200
rescue StandardError => e
render json: {
status: 500,
error: e.to_s
}
end
I added a binding.pry and I can clearly see the following error in my console:
#<ActiveRecord::RecordInvalid: Validation failed: Name can't be blank, Duration can't be blank>
But it still sends the status:200 to the client side. Is there a different way the errors are supposed to be handled?
EDIT:
create_new_game method in Game
def self.create_new_game(prms)
#player = Player.find(prms[:player].to_i)
#game = #player.games.create!(
name: prms[:name],
duration: prms[:duration]
)
#game.save!
end
ActiveRecord does not normally raise an exception when a record is invalid. Its only when you use the "bang" methods like .create! that an exception is raised. Its also impossible for us to know what is going on inside Game.create_new_game. These are used for example in seed files wherethe developer should be aware that the validation failed or in nested transactions where it should trigger a rollback.
But what you're doing is not a good practice since exceptions should be used for exceptional events - not normal control flow.
Instead you should check the return value of saving/updating the record and determine the response.
def create
#game = Game.create(game_params)
if #game.save
status: :created, location: #game
# or
render json: #game
else
render json: {
status: 500,
error: #game.errors.full_messages
}
end
end
Related
In my update and create method for my game controller, I am creating some game_events. There are then after_save callbacks on my game model that should run. These callbacks generate data that I need, and are saved in their respective columns. However, the issue I'm having is that the game_events are being created it seems, after the callbacks are being called. So that my "set_events_user" callback doesn't seem to acknowledge that there are any game_events. However, if from the console I save the game a second time, the callbacks acknowledge these game_events, and the events_user data is generated fine.
Essentially the reason my controller methods for update and create look so weird is because I've tried different ways of rearrangthe order of events so that when the callbacks run, the game_events are acknowledged. I've tried extracting the bit of code where it iterates over the events_user array from the params into a seperate method, then calling save a second time in one method to get it to acknowledge the events.
def create
#game = Game.create(game_params)
authorize #game
#game.organization_id = current_user.organization_id
params[:events_user].each do |e|
#game.game_events.create({
event_type: e["event_type"],
event_id: e["event_id"],
})
end
#game.save
render json: #game, status: :ok
rescue StandardError => e
render json: #game.errors, status: :unprocessable_entity
end
def update
authorize #game
#game.game_events.destroy_all
params[:events_user].each do |e|
#game.game_events.create({
event_type: e["event_type"],
event_id: e["event_id"],
})
end
#game.update(game_params)
render json: #game, status: :ok
rescue StandardError => e
render json: #game.errors, status: :unprocessable_entity
end
after_save :set_events_user
def set_events_user
#sequence = []
self.game_events.by_sequence.each do |e|
#e = {}
#e[:id] = e.id
#e[:event_id] = e.event_id
#e[:event_type] = e.event_type
#e[:question_text] = e.event.question_text
#e[:a1] = {es: e.event.a1, en: e.event.a1 }
#e[:a2] = {es: e.event.a2, en: e.event.a2 }
#e[:a3] = {es: e.event.a3, en: e.event.a3 }
#e[:correct_answer] = e.event.correct_answer if e.event_type == 'Question'
#sequence << #e
end
#sequence.to_json
self.events_user = #sequence
end
No error message, but I expect that the gme_events are created, the callback runs upon being saved, and the game is saved with the generated data. However, unless I save it manually from the console a second time, the first time the data is empty because it won't acknowledge game events.
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.
I am trying to create multiple database entries using only one JSON request.
Each entry consists only of two values, a type (of action) and a time (when the action happened). To get multiple of those into one request, I am using a JSON Array.
This is what my create action in the controller looks like:
def create
respond_to do |format|
#actions = []
save_succeeded = true
params[:action].each do |action|
new_action = Action.new(type: action.type, time: action.time)
save_succeeded = false unless new_action.save
#actions << new_action
end
if save_succeeded
format.json { render json: #actions, status: :created }
else
format.json { render json: #actions.errors, status: 501 }
end
end
end
When I send a post request to the controller (/actions.json) like this:
[{ "type": 0, "time": 1234567890 },{ "type": 0, "time": 1234567891 }]
I get back an empty array [] and a status code of 201 Created.
This means, the save_succeeded variable is still true, but the actions did not get added to the array. Furthermore, the actions are not in my database.
What am I doing wrong? What am I overlooking?
I would refactor the code a bit:
def create
actions = params[:action].inject([]) do |memo, action|
memo << Action.create!(type: action[:type], time: action[:time])
end
render json: #actions, status: :created
rescue ActiveRecord::RecordInvalid => e
render json: e.message, status: 501
end
end
Couple of notable changes:
use create! and rescue ActiveRecord::RecordInvalid - create! will raise a ActiveRecord::RecordInvalid if the save fails. Then, the rescue block will rescue the exception and you can render a nice error message.
you cannot use action.time, because params is a Hash, not an object.
if you want to build an array to render later, you can use inject.
if you would like to have some atomicity to this (either everything is created or nothing!), you can wrap the whole thing in a transaction block.
It's worth mentioning that I haven't tested the code above, but it should give you a direction and (maybe) it will be a drop-in replacement.
Hope that helps!
I am making Rails backend api for mobile app, and want to validate unique record with accepted status code.
#Why?
When I save entries from mobile app, I want to avoid saving duplicate records in Rails, but I also want to send "accepted status code" from Rails to mobile app instead of error status not to stop running bulk saving from mobile.
Model
class Entry < ActiveRecord::Base
validates :uuid, uniqueness: true
end
Controllers
def create
# Check if the same record already exists
entry = Entry.find_by(uuid: entry_params[:uuid])
if entry.present?
render json: {errors: {message: 'The same uuid already exists.'}}, status: :accepted
return
end
entry = Entry.new
entry.attributes = entry_params
if entry.save
render json: entry, status: :created
else
render json: entry.errors, status: :unprocessable_entity
end
end
The above code works as I want, but I am wondering if I can use validations of Rails properly instead.
This is what I tried, but couldn't get unique uuid error unfortunately. Is there any better way to handle this situation?
ApplicationController
rescue_from ActiveRecord::RecordNotUnique, with: :record_not_unique
def record_not_unique
render json: {errors: {message: 'Record not unique.'}}, status: :accepted
end
The uniqueness validation will not raise an exception. It just sets the errors object on the validated entry record and prevents saving it. You have two options in principle:
Get rid of the uniqueness validation in your model, and set up a UNIQUE constraint on the column in the underlying database table. Then, the save call would raise the ActiveRecord::RecordNotUnique exception triggered by the database. And then you indeed should be able to rescue_from it the way you posted.
Do without exceptions (dump rescue_from) and leave the uniqueness validation in place and process the errors manually in your controller, something along the lines of:
def create
entry = Entry.new(entry_params)
if entry.save
render json: entry, status: :created
elsif entry.errors.count == 1 && entry.errors[:uuid].first == "has already been taken"
render json: {errors: {message: 'The same uuid already exists.'}}, status: :accepted
else
render json: entry.errors, status: :unprocessable_entity
end
end
You will not be able to use rescue_from because a triggered exception will effectively fail-fast your request. You should cache/log these results of successes/failures (non-uniques) instead.
You can send status 201 when a record has been created, and send status 202 when duplicate record was determined. Bucket each status and at the end of the batch, execute any post-processing at the 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.