Should exceptions raised due to missing params be rescued? - ruby-on-rails

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.

Related

self.errors[:base] << inside an ActiveRecord transaction block

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

Using an if statement with Rails ActiveRecord find method

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)

show error messages from two models when updating Rails

I am updating the attributes of two models from one form.
User.transaction do
begin
#user.update_attributes!(params[:user])
#board.update_attributes!(params[:board])
rescue ActiveRecord::RecordInvalid
end
end
When the #user.update_attributes produces an error the transaction is stopped and the error message is shown in the view.
However I want to try to update both #user and #board and get the error messages for both so that the user can correct all their mistakes one time.
How can I do this?
Thank you very much in advance.
All you need to do is not use the bang (!) version of update_attributes. Try:
User.transaction do
if #user.update_attributes(params[:user]) && #board.update_attributes(params[:board])
...do stuff...
else
render ... (go back to the action the user got here from)
end
end
Then in your view code put an errors block using a conditional like if #user.errors.any?.
The bang version of update_attribtues and most activerecord methods means it will raise an error if it fails, the regular version just returns false and doesn't save, so you can use that whenever it's ok to fail you just need to take action based on success/failure. Only use the bang version when it's not ok to fail, or when you want to show a 500 screen or something for some reason...
You could do the following:
User.transaction do
#user.update_attributes(params[:user])
#board.update_attributes(params[:board])
raise ActiveRecord::Rollback unless #user.valid? && #board.valid?
end
This will ensure that both update attribute methods run so that you can get error messages for both objects. If either object is invalid though no changes will be persisted to the database.
I think there is some mistake in the method name #user.update_attributes!(params[:user]) it should be like this #user.update_attributes(params[:user]) or once you need cross check your params values whether it is correct or not else your code looks correct. You can have a help with this update_attributes

Am i creating a nil object? (undefined method ___ for nil:NilClass) error - ruby on rails

My validations were working for a while, or so I thought. Now I come back to them after a while doing something else and I am getting the error above. I think it means I am creating a nil object with the .new method but I am just lost. It seemed to be a problem with the creation of a new object by the controller because even if I # out the validation the next validation in the tree on another attribute of #thing throws up the same error. However even if I create the object in the console and test it's there the .save method throws up the error still - undefined method 'user_id' for nil:NilClass
ThingsController:
def create
#thing = Thing.new(params[:thing])
#thing.user_id = #currentuser_id
if #thing.save
flash[:notice] = "Successfully created thing."
redirect_to #thing
else
#flash[:notice] = "Your thing did not get created."
render 'otherthings/show'
end
end
Thing.rb
validate :user_valid
def user_valid
errors.add("you must be logged in to add a thing") unless #thing.user_id?
end
I'm a bit of a ruby on rails noob (i.e. <8weeks) and this is my first stackoverflow question, so go easy on me if this is stupidly obvious. I've tried params["thing"] too, as that worked in the console after manually creating params to match the log files (whereas params [:thing] didn't) but that doesn't change anything in terms of the error message I get.
When you are calling unless #thing.user_id?, it's flipping out because there's no #thing (it doesn't get passed from your controller to your model as an instance variable).
I don't remember how exactly it works, but I'm pretty sure that when calling validate :user_valid, it will pass along the record to be validated for you. If that's indeed the case you could try:
def user_valid
errors.add("you must be logged in to add a thing") unless user_id?
end

How do I raise an exception in Rails so it behaves like other Rails exceptions?

I would like to raise an exception so that it does the same thing a normal Rails exception does. Specially, show the exception and stack trace in development mode and show "We're sorry, but something went wrong" page in production mode.
I tried the following:
raise "safety_care group missing!" if group.nil?
But it simply writes "ERROR signing up, group missing!" to the development.log file
You don't have to do anything special, it should just be working.
When I have a fresh rails app with this controller:
class FooController < ApplicationController
def index
raise "error"
end
end
and go to http://127.0.0.1:3000/foo/
I am seeing the exception with a stack trace.
You might not see the whole stacktrace in the console log because Rails (since 2.3) filters lines from the stack trace that come from the framework itself.
See config/initializers/backtrace_silencers.rb in your Rails project
You can do it like this:
class UsersController < ApplicationController
## Exception Handling
class NotActivated < StandardError
end
rescue_from NotActivated, :with => :not_activated
def not_activated(exception)
flash[:notice] = "This user is not activated."
Event.new_event "Exception: #{exception.message}", current_user, request.remote_ip
redirect_to "/"
end
def show
// Do something that fails..
raise NotActivated unless #user.is_activated?
end
end
What you're doing here is creating a class "NotActivated" that will serve as Exception. Using raise, you can throw "NotActivated" as an Exception. rescue_from is the way of catching an Exception with a specified method (not_activated in this case). Quite a long example, but it should show you how it works.
Best wishes,
Fabian
If you need an easier way to do it, and don't want much fuss, a simple execution could be:
raise Exception.new('something bad happened!')
This will raise an exception, say e with e.message = something bad happened!
and then you can rescue it as you are rescuing all other exceptions in general.

Resources