I have created a validation on a table but want to exclude the validation on a specific column in my controller create method. How would I go about doing this?
def validate_cookie_brand
render json: 422, unless valid_cookie_brand?
end
def valid_cookie_brand?
CookieBrand.exists?(cookie_brand: cookie_create_request_params[:cookie_brand]))
end
It will not be the whole solution but few things which you should keep in mind
you should respond in your action whenever cookie brand is valid or not.
secondly, probably you are trying to enforce 422 HTTP status, not 422 as the response body.
thirdly, one should not separate unless condition from execution by ','
code with all cautions above fixed:
def validate_cookie_brand
if valid_cookie_brand?
render json: { message: 'valid cookie' } # status is by default 200
else
render json: { message: 'invalid cookie brand' }, status: 422
end
end
Related
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
Let's say I have a student Rails API which having an endpoint that looks like http://www.example.com/students/1
What is the preferred way to implement?
review = Review.find(inputs[:review_id])
To handle exceptions,
rescue_from Exception, :with => :internal_error
def internal_error(e)
render json: {error: {message: "Internal Error"} }, :status => 500
end
OR
review = Review.where(inputs[:review_id]).first
if review.nil?
render json: {error: {message: "Internal Error"} }, :status => 500
end
My question is which is better way for handling non-existent id through the url.
You should go with the first approach
# reviews_controller.rb
review = Review.find(inputs[:review_id])
And
# application_controller.rb
# rescue_from Exception, :with => :internal_error
# OR Prefer ActiveRecord::RecordNotFound
rescue_from ActiveRecord::RecordNotFound, :with => :internal_error # Prefer this one
def internal_error(e)
render json: {error: {message: "Internal Error"} }, :status => 500
end
To make it generic, Add it to application_controller.rb
NOTE:
This way you don't have to rescue it in every controller (the second approach you have to)
You can add a global rescue_from in your base controller (ApplicationController for example) and then use the find method (Best way to retrieve only one record) :
rescue_from ActiveRecord::RecordNotFound do |e|
render status: :not_found, json: { error: { message: e.message } }
end
Every time you try to retrieve a record, if he doesn't exist you will render an error message and a 404 status which stand for a non-existent resource.
You should use rescue for manage error
def action_name
review = Review.find(inputs[:review_id])
render json: review, status: :ok
rescue # for ever not found
render json: {}, status: :not_found,nothing: true
end
doc for status list
and you can use rescue_from on header but this works for every action
rescue_from ActiveRecord::RecordNotFound,with: action_name
Neither. You can just do something like:
unless review = Review.find_by(id: inputs[:review_id])
render json: {error: {message: "record not found"} }, status: :not_found
end
Benefits:
Does't require endless nil checking as mentioned in comments.
Avoids unnecessary exception handling.
Returns a more informative error message.
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.