Handling Unique Validation with Status Accepted - ruby-on-rails

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.

Related

Rails rescue standard error doesn't get sent

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

Where vs Find in Rails Api

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.

How to handle a JSON Array POST request with Rails?

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!

Rails validation exclusion

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

Rails - Which is a better approach to save model updates

I've found that both methods work for saving data. Is there an advantage to using one method over the other? What are the advantages/disadvantages?
First version:
begin
#user.save!
render json: "User #{#user.email} added", status: :created
rescue StandardError => e
render json: #user.errors.full_messages, status: :unprocessable_entity
end
Second version:
if #user.valid?
#user.save!
render json: "User #{#user.email} added", status: :created
else
render json: #user.errors.full_messages, status: :unprocessable_entity
end
I would go with a third alternative, because on save, the model is validated automatically:
if #user.save
render json: "User #{#user.email} added", status: :created
else
render json: #user.errors.full_messages, status: :unprocessable_entity
end
Its comes down to performance. Your first approach is more expensive because the full call stack has to be unwound to construct the exception details and you don't even use it.
Exceptions are expensive
When you catch an exception, the exception has the full stack trace where exactly the error occurred and what was the sequence of method calls that led to that event. Building this information requires Ruby to go to the previous method call and then to the previous-to-previous method call and so on recursively. This is a fairly expensive operation and since you are already within the method, you don't really need this information.
Validating twice is unnecessary
So, out of your two approaches, the second version is better. However jvperrin's answer is even better. In your second approach, you call #user.isvalid? which runs through all the model validations. And when you call #user.save, it again runs through the same validations. Instead you could just call #user.save directly and look at the return value, which is true when everything went well and false when there were validation errors.
Hope that helps.
You can use like this
if #user.valid?
if #user.save!
render json: "User #{#user.email} added", status: :created
else
render json: #user.errors.full_messages, status: :unprocessable_entity
end
else
##some code
end
Because if user is not valid no need of entering save block.
Couldn't you just add validations to your User model as a 3rd, simpler alternative?
Something like,
class User < Activerecord::Base
validates :email, presence: true
end

Resources