Rails: "You have a nil object when you didn't expect it" - ruby-on-rails

In my controller:
Article.author_ids_in(params[:filters][:author_ids])
This of course returns an error ("You have a nil object...") if those particular params have not been passed. Here is the model method:
def self.author_ids_in(filters)
unless filters.nil?
where(:author_id + filters)
else
scoped
end
end
As you can see, I'm already prepared for a nil object, so how can I get Ruby to allow nil to be passed?

params[:filters] is most likely nil so it's raising an exception when you try to go into it to get [:author_ids]
Either check before you call the scope, or rescue the exception:
begin
Article.author_ids_in(params[:filters][:author_ids])
rescue NoMethodError
# do something else here
end

Related

Ruby on Rails Exception usage

Upon finding a failed validation, invalid? will return false and exit.
If all validations pass, invalid? will return true and the code will continue.
Does the rescue code only run if all validations pass?
If so, what raised errors will it catch?
Lastly why is there no Begin?
def save
return false if invalid? # invalid? triggers validations
true
rescue ActiveRecord::StatementInvalid => e
# Handle exception that caused the transaction to fail
# e.message and e.cause.message can be helpful
errors.add(:base, e.message)
false
end
Does the rescue code only run if all validations pass?
Blockquote
No, it will run if the call of invalid? throws an exception of type StatementInvalid
what raised errors will it catch?
Blockquote
the call of invalid? here is what raises the error
why is there no Begin?
in ruby, you can remove begin if you rescue from any exception that is raised from methods body so
def method
begin
#some code
rescue
#handle
end
end
equal to
def method
some code
rescue
# handle
end
but the second syntax shorter and cleaner
Note: it doesn't same right to me to rescue from ActiveRecord::StatementInvalid
inside an override to save

Brakeman does not like rescue

I have this method inside a model with this code inside. It calls a gem and returns either the object I want or a 404 resource not found. if I do a method on a 404 then I need to rescue it as shown below. If I just use rescue the linter fails. If I do this brakeman fails.
find_object
return_object = Rails.cache.fetch(cache_key + '/variableInsideObject') do
GemClient.find(id).variableInsideObject
rescue HttpServices::ResourceNotFoundError
raise ApplicationController::ExternalServiceError,
"variable inside object not found for id: #{id}"
end
end
How can I rescue this error without failing the linter and brakeman.
Imo this is a more Ruby-esque implementation of this code:
def find_object
return_object = begin
Rails.cache.fetch(cache_key + '/variableInsideObject') do
GemClient.find(id).variableInsideObject
end
rescue HttpServices::ResourceNotFoundError => e
Rails.logger.error(e)
raise ApplicationController::ExternalServiceError,
"variable inside object not found for id: #{id}"
end
end
Of course, it's hard to say without knowing what the linter or brakeman are complaining about exactly.... but this should be better. You don't of course need to use begin end blocks, but sometimes linters/community finds it is neater...

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 update_attributes method return nil

This is the code:
def update
if #user.update_attributes(params[:user])
redirect_to my_account_path
else
redirect_to account_path
end
end
#user.update_attributes should return true or false when validation failes but it returns nil.
Using Rails 3.1.2 & Ruby 1.9.2.
attr_accessible setup on the model?
http://apidock.com/rails/ActiveRecord/Persistence/update_attributes
Sometimes when I run into issues like this I'll update change update_attributes to update_attributes! -- that can forces an immediate exception to be thrown and can point you at exactly why the value being returned is an error saving.
It should be returning false if the data isn't saved. According to the docs:
If saving fails because the resource is invalid then false will be returned.
And here's this snippet from the rails source (note that update_attibutes calls save under the covers):
def save(*)
begin
create_or_update
rescue ActiveRecord::RecordInvalid
false
end
end
So it specifically returns false, not nil if there's a problem saving the record.

getting ActiveRecord::RecordNotSaved error while saving

While creating a new object i am getting ActiveRecord::RecordNotSaved error on before_save.
But i want to fetch the proper message other than ActiveRecord::RecordNotSaved error message.
How may i fetch the proper error message and pass it to the rescue?
begin
#some logic
raise unless object.save!
rescue ActiveRecord::RecordNotSaved => e
# How may fetch proper message where my object is failing here ..
# like object.errors.message or something like that.
end
begin
#some logic
#object.save!
rescue ActiveRecord::RecordNotSaved => e
#object.errors.full_messages
end
Why raise the exception and not just check if save or not ?
unless object.save
object.errors
end

Resources