getting ActiveRecord::RecordNotSaved error while saving - ruby-on-rails

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

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...

How to know why a transaction was rolled back?

I have an Account model. It has usual validations and after_save callbacks. There is a requirement, and account objects are to be created following some additional validation strategy. I have the following snippet, which works fine:
def special_account_creation
Account.transaction do
a = Account.create(params)
resp = update_third_party(a) # Throws exception if unable to update
validate_amount(a, resp) # Throws exception if `a` is invalid
# rescue Rollback ??? <--- How to do this
# msg = <transaction_error_details>
# Rails.logger.info("Special Account not created due to: #{msg}")
end
end
a account is destroyed if anything goes wrong while calling a third party API or during validation.
I want to know how I can log the error to know why the transaction was rolled back.
ActiveRecord transaction catches any exception being raised inside and rolls back the transaction. After that, if the exception is ActiveRecord::Rollback, it is supressed otherwise any other exception is raised above the call stack.
Ref: https://api.rubyonrails.org/classes/ActiveRecord/Rollback.html
So you could do something like this:
Account.transaction do
begin
a = Account.create(params)
resp = update_third_party(a) # Throws exception if unable to update
validate_amount(a, resp) # Throws exception if `a` is invalid
rescue StandardError => e # <- assuming all errors need to be logged
Rails.logger.info("Special Account not created due to: #{msg}")
raise e # <-- Don't forget to raise the error!
end
end
I suggest raise the error from the rescue block - this will rollback the transaction and if the error is something other than ActiveRecord::Rollback, it will be raised outside the transaction block. These errors you can handle in rescue_from block in base controller.
Also prefer catching StandardError and not Exception: https://robots.thoughtbot.com/rescue-standarderror-not-exception
I am assuming we want to catch all possible errors in that block, hence the use of StandardError, otherwise use any specific errors that need to be intercepted.
ActiveRecord does not raise Rollback on failed update and/or failed validation.
You should rescue what was raised and then there are two opportunities: either raise Rollback to just rollback a transaction and continue normal execution flow, or re-raise what was raised to indeed break the normal flow.
def special_account_creation
Account.transaction do
a = Account.create(params)
begin
resp = update_third_party(a) # Throws exception if unable to update
validate_amount(a, resp) # Throws exception if `a` is invalid
rescue => e # might be more specific
Rails.logger.info("Transaction failed with a message #{e.message}")
raise ActiveRecord::Rollback, e.message # just to rollback
# raise e # for re-raising the exception
end
end
end

Rspec testing methods in transaction

I am trying to execute two things inside a transaction and I'm not sure how I should test it in rspec. My code looks something like this:
Implementation:
def method
begin
ActiveRecord::Base.transaction do
...some implementation...
model1.save!
model2.save!
end
rescue => e
exception_info = {:class => e.class, :message => e.message, :backtrace => e.backtrace}
#logger.warn("Error. Rolling back.", :exception => exception_info)
end
end
Tests:
it "model1 object is not created if model2 fails to save" do
Model1.any_instance.should_receive(:save).and_raise("model1 save error!!")
method
Model2.all.should == []
end
it "" do
Model2.any_instance.should_receive(:save).and_raise("model2 save error!!")
method
Model1.all.should == []
end
I want both the models to be saved or none. My rspec tests check both the cases but I keep getting errors. If I add (:requires_new => true) to the transaction, it works. I thought it was meant for nested transactions, not something like this. Am I missing something?
ActiveRecord transactions only rollback if an exception is raised. Otherwise they persist whatever records were successfully created.
In your case, you want to use save! instead of save to interrupt the transaction. This will raise an ActiveRecord::RecordInvalid exception which you need to rescue and handle.
begin
ActiveRecord::Base.transaction do
...some implementation...
model1.save!
model2.save!
end
rescue ActiveRecord::RecordInvalid
end

Rails: "You have a nil object when you didn't expect it"

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

Resources