I am looking for a best-practise / solution to render responses with different http-response codes than 422 - unprocessable entity.
I have a simple validator:
validates :name, presence: true, uniqueness: {message: 'duplicate names are not allowed!'}
I want to return status code 409 - Conflict (:conflict) when this validation fails. Possible solution:
Add status code to errors hash, e.g. errors.add(status_code: '409'). Then either render the status code from errors, or render 422 if multiple exists.
The problem with the above solution is that I do not know how to call the errors.add function on a 'standard' validator.
My render code:
if model.save
render json: model, status: :created
else
render json: model.errors, status: :unprocessable_entity
end
Which I would like to extent that it can render different status codes based on validation results.
In this case, creating a custom validator might be one approach and you could always expand the complexity
validates_with NameValidator
Custom validator
class NameValidator < ActiveModel::Validator
def validate(record)
if record.blank? || Model.where(name: record.name).exists?
record.errors.add(:base, "Duplicate names not allowed!")
end
end
end
Related
I'm trying to get my Rails API to render all JSON responses in camelCase. Currently I am using Netflix Fast JSON API for my serializer and rendering errors like so:
render json: { errors: command.errors }, status: :unauthorized
For the Netflix Fast JSON API serializers I've been adding set_key_transform :camel_lower to every serializer, which seems to do the trick (although if anyone knows how to make that a default it would be much appreciated).
For rendering errors however I'm not sure the best way to go about camel casing. If anyone has any experience with this please let me know how you go about it! Ideally there is a method of doing this that doesn't add too much syntax to every render call being made.
UPDATE
In serializing errors I added a helper method on the application controller:
def render_error(errors_params, status)
render json: {
errors: errors_params
}.deep_transform_keys { |key| key.to_s.camelize(:lower) }, status: status
end
For the Netflix Fast JSON API I took the suggestion of #spickermann and added an application serializer for the other serializers to inherit from:
class ApplicationSerializer
include FastJsonapi::ObjectSerializer
set_key_transform :camel_lower
end
class SomeSerializer < ApplicationSerializer
attributes :attribute, :other_attribute
end
You could create an ApplicationSerializer and all other serializers could inherit from it:
class ApplicationSerializer
include FastJsonapi::ObjectSerializer
set_key_transform :camel_lower
end
class FooBarSerializer < ApplicationSerializer
attributes :buzz, :fizz
# ...
end
You could monkey patch the serializer
Rails.application.config.to_prepare do
FastJsonapi::ObjectSerializer.class_eval do
set_key_transform :camel_lower
end
end
and for handling errors you can probably create an error serializer
render serializer: ErrorSerializer, json: {status: : unauthorized, errors: resource.errors
Have a look here and here
I'm building an authentication system in Rails5 and I have a User for which I want to check the uniqueness and correct format for the email field. I've seen that both errors throw the same exception, ActiveRecord::RecordInvalid and I'd like to know how could I manage both cases in a good and elegant way.
This would be the important part of my model User:
validates_format_of :email, with: URI::MailTo::EMAIL_REGEXP
validates_presence_of :email
validates_uniqueness_of :email
And this would be the controller:
begin
user.save!
render status: :created, json: { msg: 'User was created.' }
rescue ActiveRecord::RecordInvalid => err
render status: conflict, json: { msg: err }
end
What I'd like to do is to differentiate between Uniqueness error (to return a 409) and format error (to return a 400).
I'll appreciate any comment or help about how to do this check in the better way.
Instead of doing user.save!, handle user.save
if user.save
render status: :created, json: { msg: 'User was created.' }
else
render status: conflict, json: { msg: user.errors.full_messages.join(', ') }
end
the bang version (save!) raises the exception if the object is not saved for whatever reason, while the non-bang version (save) returns true if object is saved and false, if it's not.
To further distinguish between type of validation fail, you can use #added?:
user.errors.added? :email, :taken => true if email uniqueness error
user.errors.added? :email, :invalid => true if email formatting error.
Catching the ActiveRecord::RecordInvalid exception is not the best way to handle this case. Instead, I would suggest to run validation explicitly and checking the errors object.
Check Rails API documentation for ActiveModel::Validations(https://api.rubyonrails.org/classes/ActiveModel/Validations.html#method-i-errors) and ActiveModel::Errors (https://api.rubyonrails.org/classes/ActiveModel/Errors.html).
After running user.valid?, you can investigate user.errors object to detect exact kind of validation error. Then you can reply with different response codes as you want:
if !user.valid?
if user.errors[:email] == [" ... uniqueness error ..."]
# return 409
elsif user.errors[:email] == [" ... format error ..."]
# return 400
else
# ...
end
else
user.save!
# ...
end
I'm having a problem with validations at rails, i have a parameter call propose that cannot be blank, when I try to create it work, but when update do not, this is my validations:
class Propose < ActiveRecord::Base
validates_presence_of :propose, :date, :meeting_type
end
For some reason, this only works when a object is create, then propose cannot be blank, but if I try update for empty it works when should not. I tried this solution, but did not work as well:
validates_length_of :propose, :minimum => 1, allow_blank: false
In both cases, my rspec returns this
Failure/Error: expect(response.body).to be_empty
expected `"{\"id\":508,\"propose\":\"\",\"business_id\":442,\"date\":\"2017-05-24T00:00:00.000-03:00\",\"meetin...\":\"2017-05-24T14:13:42.120-03:00\",\"updated_at\":\"2017-05-24T14:13:42.120-03:00\",\"status\":0}".empty?` to return true, got false
This is the test:
it "Should not be able to update propose because propose is blank" do
#propose = Fabricate(:propose, company: #current_user.return_company)
put :update, propose_id: #propose.id, propose: ''
expect(response.body).to be_empty
expect(response.status).to eq(422)
end
This is my controller:
def update
begin
propose = Propose.find(params[:propose_id])
propose.update_attributes(update_params)
render(json: propose.to_json, status: :ok)
rescue => e
render(json: {error: e.message}, status: :unprocessable_entity)
end
end
private
def update_params
params.permit(:propose, :date, :meeting_type, :status)
end
So, that is the problem, propose cannot be blank when update or create, but the validations appears only to work for create. I know that I can use something like:
raise "empty propose" if params[:propose].empty?
But I want to use rails methods if possible to the code don't be fill with manually validations when there is a way to do using rails already.
While you could use "dangerous" .update! which raises an exception if the record is invalid:
def update
begin
propose = Propose.find(params[:propose_id])
propose.update!(update_params)
render(json: propose.to_json, status: :ok)
# Never use rescue without an exception type!
rescue ActiveRecord::RecordInvalid => e
render(json: {error: e.message}, status: :unprocessable_entity)
end
end
This is not a good practice since exceptions should be used for exceptional events. Not the normal application flow.
Instead use the safe .update method and check the return value:
def update
propose = Propose.find(params[:propose_id])
if propose.update(update_params)
render(json: propose.to_json, status: :ok)
else
render(json: { errors: propose.errors.full_messages }, status: :unprocessable_entity)
end
end
First of all response.body will never be empty based on what you have and generally shouldn't be as it offers no context to the situation.
Secondly you are not checking that update_attributes returned false you are just returning the object. That object is in an invalid state and does not reflect the persisted object since validation failed.
update_attributes will call assign_attributes before save so the in memory propose will reflect the new assignment with propose.propose being blank but that is not actually what is stored in the database.
Consider changing update to
def update
begin
propose = Propose.find(params[:propose_id])
if propose.update_attributes(update_params)
render(json: propose.to_json, status: :ok)
else
# modify this portion as you see fit
render(json: {error:{errors: propose.errors.full_messages}}.to_json,status: :unprocessable_entity)
end
end
end
If you want an Error to be raised as your initial intent implies then use
# expanded based on #ma_il's answer
def update
begin
propose = Propose.find(params[:propose_id])
propose.update_attributes!(update_params) #notice the bang (!)
render(json: propose.to_json, status: :ok)
# notice we are only rescuing ActiveRecord::RecordInvalid rather than
# Exception which is considered poor form and could expose information in the json that you don't intend
rescue ActiveRecord::RecordInvalid => e
render(json: {error: e.message}, status: :unprocessable_entity)
end
end
You're calling update_attributes but never checking its return value. Since it looks like you're expecting an error to be raised, you should use update_attributes! (with an exclamation mark at the end) instead.
Note that it is advisable to rescue only from those errors you're actually expecting to be thrown. In this case, you should rescue ActiveRecord::RecordInvalid => e.
Alternatively, you can also use the rescue_from method to execute error handling in a separate method. This is helpful if your error handler is more complex, for example because it has its own respond_to block.
Quoting Rails documentation on update a.k.a update_attribute
Updates a single attribute and saves the record. This is especially
useful for boolean flags on existing records. Also note that
Validation is skipped.
Callbacks are invoked.
updated_at/updated_on column is updated if that column is available.
Updates all the attributes that are dirty in this object.
This method raises an ActiveRecord::ActiveRecordError if the attribute
is marked as readonly.
So there's that, update skips the validation steps, if you want to validate your models either call valid? yourself or use save instead of update.
I have the following code
if user.update_attributes(user_params)
render json: user , status: "success"
else
render json: user.errors , status: "failed"
end
It updates the values if the object is correct . But let's say that email is duplicate then it does not goes in else condition . instead throws an exception.
ActiveRecord::RecordNotUnique (Mysql2::Error: Duplicate entry
'test#test.lo'
But as far i think . It should be a kind of thing which i could get like user.errors.full_messages .
I tried it using the following
if User.new(user_params).valid?
user.update_attributes(user_params)
render json: user , status: "success"
else
render json: user.errors , status: "failed"
end
It gets into else condition but user.errors.messages are equal to {}
You need to rely on model-level validation:
validates :email, uniqueness: true
If you rely on database-level constraints for validation, you're going to get exceptions upon write. You can capture those exceptions and attempt to turn them into a human-readable error message, but this isn't how Rails is meant to work.
If you want validation error messages, use validators in your models.
Two issues here. First is that I need to access a model's id before all of its attributes are defined. Meaning that this:
class Search < ActiveRecord::Base
validates_presence_of :name
validates_presence_of :color_data
end
throws an error unless I removed the second line, which is not a good thing to do. My second issue is that I don't want to render json until a model has both attributes. This is my controller:
def create
#search = Search.create( name: (params[:name]) )
Resque.enqueue(InstagramWorker, #search.id)
respond_to do |format|
if #search.save
format.json { render json: #search }
format.html { redirect_to root_path }
else
format.html { redirect_to root_path }
end
end
end
Should I write some logic in the model to check for name && color_data before saving? And is there a workaround for accessing an id without breaking validations?
You probably can use conditional validations, like
class Search < ActiveRecord::Base
validates_presence_of :name
validates_presence_of :color_data, if: :some_condition?
private
def some_condition?
# condition logic here
end
end
You can't do this.
By calling Resque.enqueue(InstagramWorker, #search.id) you're telling resque to do something, but not as part of this request. So this could complete now, it could complete in 2 hours from now.
If you need to ensure that this completes before the request has finished, take it out of Resque.
What you could do is only validate the color_data on update, rather than create. Presumably your resqueue job calls #search.save. So by adding
validates :color_data, presence: true, on: :update
But this wouldn't stop the json being rendered, you can't get past the fact that this is not part of the request without taking it out of resqueue.