I want to update #some_instance unless the user does not meet some criteria. I put the criteria check and the #some_instance.save! in a transaction block so that if either of them fail, no transaction is made. This works well, but I am having trouble returning the correct error message. If the user does not meet the criteria, I want to return the reason why, OR if the #some_instance doesn't save, I want to return that error.
My code:
#some_controller.rb
begin
Some_Class.transaction do
return render json: { error: #user.errors }, status: :payment_required,
location: new_payment_path unless #user.meets_criteria
#some_instance.save!
end
rescue ActiveRecord::RecordInvalid => exception
render :json => { :error => exception.messages }, status: :unprocessable_entity
rescue => error
# handle some other exception
end
#User.rb
def meets_criteria
self.errors[:base] << "Does not meet criteria"
return false
end
The problem I'm facing is this: When the meets_criteria method returns false, I expect the return render json line to execute. Instead it catches an error in "rescue => error".
The return render json is never executed.
UPDATE:
#Gen suggested using a before_action instead of calling the meets_criteria in the transaction do block. I think this is a much better implementation, however I'm still curious why the return render is never called. Is it because ActiveRecord raises an error? If so shouldn't that be caught in the RecordInvalid exception?
#TheJKFever, I am not sure that your code supposed to jump to any rescue block (well, and I actually confirmed by running it).
Your some_validation method returns false, and therefore "unless #user.some_validation" evaluates to true, and the render is executed with the following log output:
Completed 402 Payment Required in 128ms
{"error":{"base":["Some error message"]}}
You can refer to ActiveRecord::RecordInvalid API for details about RecordInvalid. Namely, "Raised by save! and create! when the record is invalid".
So, your "rescue ActiveRecord::RecordInvalid => exception" is supposed to handle exceptions in the "#some_instance.save!" statement and not in your custom validation.
In your validation you don't actually have the code that raises the ActiveRecord::RecordInvalid exception and probably fails with another error, which is easy to check by outputing it in details.
In order to use some_validation with "self.errors[:base] <<" properly first your need to add the following statement to your user model:
validate :some_validation
In this case, if you call "#user.save!" instead of "#some_instance.save!", you would fall into that "rescue ActiveRecord::RecordInvalid => exception" block.
PS: #TheJKFever, I saw one of your comments below and wanted to clarify something. A validation has a well defined purpose to validate a model before saving, and what you need then is not a model validation. What you actually need is a before_action on your controller that will check that your user is ready to be used in such and such action (consider it as controller validation). And yes, you probably will need some method on your user model to do that check.
Updated (after question update)
#TheJKFever, as I mentioned earlier, when I implemented your code I was able to execute "return render json: { error: #user.errors }...". So, if it fails for you, it must be due to some exception during meets_criteria call, but it is not RecordInvalid exception. Since you wrapped meets_criteria into transaction, it means that it probably does some database changes that you want to rollback if #some_instance.save! was unsuccessful. It would be a good idea to wrap your meets_criteria with the same rescue blocks too and find out. Do you create! or save! something in meets_criteria? If so, then it also can throw RecordInvalid exception. The problem is that in your code RecordInvalid can be thrown by meets_criteria and by #some_instance.save!, and there is no way to see in the code which one. In any case, if you wrap your meets_criteria with rescue, you will be able to send your render errors request from it. And if you decide to go with before_action filter then you will have to move whole your transaction into it (assuming that it requires the integrity of the data).
The point is that ActiveRecord::RecordInvalid exception will only be thrown in case of save! or create! failure due to invalid data. It might not be the case in your code, and some other exception is thrown, and you end up in "rescue => error" block.
You are adding error to #user model and handling exception raised on #some_instance. Try #user.errors.messages[:base]
Custom validations aren't usually invoked via a public instance method. They're usually written as a private method and invoked by ActiveRecord during valid? if you register them in your model with, e.g. validate :some_validation.
In your case, the User model would need the following:
validate :some_validation
…
private
def some_validation
errors.add :base, "Some error message" if some_error_happens?
end
Then, in the controller, you would do:
Some_Class.transaction do
return render json: { error: #user.errors }, status: :payment_required,
location: new_payment_path if #user.valid?
#some_instance.save!
end
Related
I'm new to RoR and I'm now confusing about updating data to databases.
And I studied RoR just recently, if the question is unclear please let me know.
Assume I created a table "book", with 3 columns: "name", "price", and "author".
When implementing action update in rails, I'll do the following:
def update
#book = Book.find(params[:id])
if #book.update({'name': 'Ruby tutorial'})
redirect_to #book
else
render 'edit'
end
end
This will update the record in database by
if #article.update({'name': 'Ruby tutorial'})
In order to test some failure cases, I modified the database column "name" to "nane" on purpose,
I thought the statement if #article.update will fail due to the wrong table field name and the code will go to else block.
Because I thought the statement is for checking whether rails saves the record into the database successfully.
However, my code throws exception because of the wrong field name instead of going to the else block.
I got confused about this behavior, what kinds of situation will cause
if #article.update(params) fail and go to the else block?
Is if #article.update(params) for just validating form data? i.e, checking whether the hash key sent from the form is correct, as for database side(field name or database connection error) , is not the business of this statement so it will throw exception.
Thanks a lot.
#book.update(nane: 'name')
will throw an exception ActiveModel::UnknownAttributeError: unknown attribute.
if is not an exception handler.
The purpose of using if and else block here is to handle the negative scenario (update failed due to some validation failures).
update(name: '')
with presence validation on name column will not throw an exception. It will return false. If you do not have the if / else block here and simply update statement, you will not know whether the update has succeeded or not.
The other way of handling this is to use update with !. Adding ! makes the statement to throw an exception for any failures.
begin
update!(name '')
rescue Exception
render 'edit'
end
Rescuing exception like above is a bad style of handling exception.I have removed
rescue Exception
//some action //
end
You must learn why from here Why is it bad style to `rescue Exception => e` in Ruby?.
You can also handle specific RecordInvalid exception like this.
begin
update!(name '')
rescue ActiveRecord::RecordInvalid => invalid
// some action //
end
Should exceptions raised due to missing params be rescued?
For example, in the following code in FriendRequestsController:
def update
#request = FriendRequest.find(params[:id])
if #request.update(friend_request_params)
flash[:notice] = "Friend request updated successfully."
redirect_to current_user
else
flash[:errors] = #request.errors.full_messages
redirect_to current_user
end
end
private
def friend_request_params
params.require(:friend_request).permit(:status)
end
If the call to update on the model fails, the error message(s) will be stored in flash. But if there is something wrong with params such that an exception is raised in the friend_request_params helper method, the app will fail.
Is it the convention to allow that? Is there a better way to do this?
In short the answer is no. When you call
def friend_request_params
params.require(:friend_request).permit(:status)
end
You have established a contract with any action that uses the friend_request_params. If that action calls friend_request_params and doesn't send a :friend_request, the action has violated the contract and an exception should be raised. Your application is legitimately not working as designed and the exception is your canary in the coal mine so to speak.
No, they shouldn't. If an exception happens with your params it is because something weird or not expected is happening. It can be a bug, or it can be someone messing with the form. If friend_request_params raises an exception, just show a nice webpage to your users such as public/500.html or public/400.html.
You can look at your logs to track the exceptions, or implement a catch all to log them in a more convenient way using rescue_from.
I want to check that a user is included in a group. In an attempt to do this, I have the following statement:
user_id = current_user.id
unless (group.user.find(user_id))
redirect_to different_path
end
Looking at the docs, "A failure to find the requested object raises a ResourceNotFound
exception if the find was called with an id." How do I write the unless statement so that it operates properly?
If you defined a relation between group and users, you can call current_user.group.present? This will return false if there is no related object.
You can handle the redirect as part of Exception handling
redirect_to path if group.user.find(user_id)
rescue ResourceNotFound
redirect_to differenct_path
end
alternatively and probably a better way would be to build your logic around
user.groups.include?(group)
In my controller, I have code that looks like the following:
#mymodel.transaction do
for a in arr
#mymodel.some_method(a)
end
end
in #mymodel#some_method I could throw an ActiveRecord::Rollback exception which in the db does what it needs to do, however I then simply get an HTTP 500 and no way to catch the exception to let the user know in an elegant way what went wrong.
I've tried wrapping #mymodel.transaction do in a begin/rescue block, but that won't do it either. What's the best way to catch the exception so I can present the proper view to the user?
From the ActiveRecord::Base documentation:
Normally, raising an exception will cause the transaction method to rollback the database transaction and pass on the exception. But if you raise an ActiveRecord::Rollback exception, then the database transaction will be rolled back, without passing on the exception.
A small example:
class ThrowController < ApplicationController
def index
status = ActiveRecord::Base.connection.transaction do
raise ActiveRecord::Rollback.new
end
Rails.logger.info "followed transaction"
end
end
then:
>> c = ThrowController.new.index
=> "followed transaction \n"
As you can see, the ActiveRecord:::Rollback exception is swallowed by the transaction block.
It seems to me that something else is going on with your code that we're not aware of.
I am making an activeresource call to a service, and I'd like some custom error messages as feedback. I have some validations that aren't normal model validations, so I can't just return #object.errors.
So, for instance, one of my validations is this. Two objects have a many to many relationship, but I want to restrict one object to only have a limited number (say 2) of relationships to other objects. Here's some code:
In the client:
response = Customer.find(customer_id).put(:add_user, :user_id => user_id)
This puts a request to add a user to the customer. Then in the service I want to check that this addition is valid.
def add_user
#user = User.find(params[:user_id])
#customer = Customer.find(params[:id])
if #customer.users.length > 2
render :xml => "ERR_only_2_users_allowed", :status => :unprocessable_entity
end
end
Here's my problem. In active resource, if the return status is an error, the client side completely fails. I could change the status to 200 and I get back the body err msg fine, but this seems to defeat the purpose of having error reponse codes.
I can put the whole request call from the client in a begin/rescue block
begin
response = Customer.find(customer_id).put(:add_user, :user_id => user_id)
rescue ActiveResource::ResourceInvalid => e
#return error code
end
but when I catch the 422 (unprocessable_entity) response, I get nothing of the body back, so I don't get my custom error message. response = nil
Does anyone know how I can achieve these custom error message with the proper response codes?
This may or may not be your problem, but both of ours seem very close. I'm using a custom put method, but his should work for you too. What's going on is that the code that does this:
rescue ResourceInvalid => error
errors.from_xml(error.response.body)
end
Is only working with the standard save method. If you want errors added when other methods are called it looks like you need to do it yourself.
I had to add it to
vendor/rails/activeresource/lib/active_resource/custom_methods.rb
Here is what my diff from git looks like:
old code:
def put(method_name, options = {}, body = '')
connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
end
new code:
def put(method_name, options = {}, body = '')
begin
connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
rescue ResourceInvalid => error
errors.from_xml(error.response.body)
end
self
end
So look at the stack trace when get the exception thrown for the 422 and see which method it's calling exactly. Then add something like what I have and you should be good to go.
Don't ask me why the activeresource folks thought validations should only work with their save method. the save method does a create or update, but calling 'put or post' is the exact same thing, IMO. If we want validations to work on save we want them to work on put and post...anyway give it a shot.
I'm not sure if i need the self at the end...i may not. I'm not totally done with this as I just figured out how to make it work.
Erik
I think that your problem might be the response isn't an xml document but just a plain string. Try changing your render statement to something like:
render :xml => { :error => "ERR_only_2_users_allowed" }, :status => :unprocessable_entity