Using an if statement with Rails ActiveRecord find method - ruby-on-rails

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)

Related

Updating database records in Rails

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?

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.

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

rails: display errors for multiple models

I have a situation where a user can create multiple records at the same time, so my controller action looks like this:
if Unit.create multiple_unit_params.values
redirect_to units_path
else
render :new
end
The validation can fail for any one of the records. So how am I supposed to render errors in the view if I don't know what record validation failed since there are multiple records?
One way to conquer this is to use the create! method that raises exceptions. The exception object contains information on the model that failed.
The pattern I tend to use looks like this:
def create
#unit = Unit.new(multiple_unit_params.values)
#unit.save!
redirect_to(units_path)
rescue ActiveRecord::RecordInvalid => e
# ... Deal with exception
end
If you're creating multiple records you may want to encapsulate that in a transaction so you don't end up with some created, some uncreated. It'll be an all-or-none thing. Exceptions automatically unwind transactions.

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

Resources