Raised error is not caught by rescue block - ruby-on-rails

We are having a production problem while sending emails using action mailer.
NameError
undefined local variable or method `to_ary' for #<Mail::Part:0x0000000008e0c998>
Did you mean? to_addrs
Error appears totally random and behaves completely same way like https://yehudakatz.com/2010/01/02/the-craziest-fing-bug-ive-ever-seen/
What I want to do is to monkey patch the missing_method to make it work.
So I created some tests including
let(:part) { Mail::Part.new }
it 'returns self' do
allow_any_instance_of(Mail::Message).to receive(:method_missing).and_raise(NameError, 'error part')
expect([part].flatten).to eq [part]
end
and created monkey patch
require 'mail'
module Mail
class Part < Message
# monkey patched due to same behaviour like
# https://yehudakatz.com/2010/01/02/the-craziest-fing-bug-ive-ever-seen/v
def method_missing(name, *args, &block)
begin
super
pp :hello
rescue NameError => e
return [self] if name.try(:to_sym) == :to_ary
raise e
end
end
end
end
The intention of this test is that Array#flatten calls to_ary on it's content. Mail::Part#method_missing is normally not defined, but I created one to handle possible NameError from Mail::Message#method_missing and return the right value.
The problem is, that Mail::Part#method_missing is called, super is called and raises NameError, but rescue does not handle anything. pp :hello is skipped, because of the raised error.
So the test ends up with
NameError: error part
0) Mail::Part call flatten with NameError returns self
Failure/Error: expect([part].flatten).to eq [part]
NameError:
error part
# ./spec/lib/core_ext/mail/part_spec.rb:13:in `flatten'
# ./spec/lib/core_ext/mail/part_spec.rb:13:in `block (4 levels) in <top (required)>'
# ./spec/active_record_spec_helper.rb:19:in `block (2 levels) in <top (required)>'
Another problem also is, that the test ends up in infinite recursion, when I try
def method_missing(name, *args, &block)
return [self]
end
The same with
def method_missing(name, *args, &block)
begin
raises_name_error
rescue NameError => e
return [self] if name.try(:to_sym) == :to_ary
raise e
end
end
def raises_name_error
raise NameError, 'error part'
end
but in this case, the rescue block handles the NameError.
Any idea for solution?

Resolved by
module Mail
class Part < Message
def to_ary
[]
end
end
end
My initial tries for solution were little overkill.

Related

Mock scope method from ActiveRecord in Rails/RSpec?

I have an ActiveRecord class like:
class DBGroup < ActiveRecord:Base
# ...
scope :example_method, -> { // does something }
# ...
end
And now I have another method in another class, something like
def method1
do #open block for rescue method
DBGroup
.example_method
.group(:some_column)
.having("COUNT(*) > 5")
rescue StandardError => e
e
end
end
And in RSpec I am testing kind of like:
it "my test, want to test rescue block"
expect_any_instance_of(DBGroup).to receive(:example_method).and_raise(StandardError)
expect{subject.method1}.to raise_error(StandardError)
end
But I am getting the error like:
RSpec::Mocks::MockExpectationError: DBGroup does not implement #example_method
Scopes are not instance methods, they are class methods, so you should not use expect_instance_of, but expect.
Also, you are rescuing the StandardError inside the method, so the method itself does not raise a StandardError. Actually, a StandardError is raised inside the method and it's rescued inside of it aswel and your test is all about what's happening at it's exterior.
I hope you can see the difference:
def method_that_raises_an_error
raise(StandardError)
end
def method_that_returns_an_error
raise(StandardError)
rescue StandardError => e
e
end
As you can see, your code is more like the second. So, to test it you should do:
expect(DBGroup).to(receive(:example_method).and_raise(StandardError)
method1_return = subject.method1
expect(method1_return).to(be_instance_of(StandardError))

How to stub a method having a parameter and raise exception using mocha?

I am trying to stub an instance method of a class having a single parameter. Here is a simplified form of the class.
class GetPost
def call(post_id)
#some meaningful stuffs
end
end
class GetPreviewTemplate
def call(post_id)
begin
GetPost.new().call(post_id)
rescue => e
Post.find_by_ref(post_id).update(failed: true)
raise
end
end
end
And here is how I am trying to stub call method of GetPost to raise an error so that I can verify failing scenario in GetPreviewTemplateTest.
test 'that should NOT get the HTML content of non-existing dummy post from CMS' do
GetPost.any_instance.stubs(:call).with(any_parameters).raises(ArgumentError)
GetPreviewTemplate.new(#post.id).call
assert_not #post.status
end
I am getting following error while running the test
Wordpress::GetPreviewTemplateTest#test_that_should_NOT_get_the_HTML_content_of_non_existing_dummy_post_from_CMS:
ArgumentError: ArgumentError
app/services/wordpress/get_preview_template.rb:19:in `call'
Suggestions, please.

Ruby: Skip element in loop if an exception is raised

I have the following method:
def fetch_something
#fetch_something ||= array_of_names.inject({}) do |results, name|
begin
results[name] = fetch_value!(name)
rescue NoMethodError, RuntimeError
next
end
results
end
end
To its purpose: It fetches a value for a given name that can raise an error, in which case it will ignore the name and try the next one.
While this works fine I get an error from Rubocop that says:
Lint/NextWithoutAccumulator: Use next with an accumulator argument in
a reduce.
Googling that error leads me to http://www.rubydoc.info/gems/rubocop/0.36.0/RuboCop/Cop/Lint/NextWithoutAccumulator where it says to not omit the accumulator, which would result in a method looking like this:
def fetch_something
#fetch_something ||= array_of_names.inject({}) do |results, name|
begin
results[name] = fetch_value!(name)
rescue NoMethodError, RuntimeError
next(name)
end
results
end
end
The problem is, that this change breaks the otherwise working method. Any ideas on how to tackle that?
Update: Demonstrational example:
array_of_names = ['name1','name2','name3']
def fetch_value!(name)
# some code that raises an error if the name doesn't correspond to anything
end
fetch_something
# => {'name1' => {key1: 'value1', ...}, 'name3' => {key3: 'value3', ...}}
# 'name2' is missing since it wasn't found durcing the lookup
Using your example code, it seems that next(results) does fix the issue for me. I've used some test code that throws a KeyError instead of NoMethodError or RuntimeError, but the idea is still the same:
#array_of_names = ['name1','name2','name3']
def fetch_value!(name)
{'name1' => 'n1', 'name3' => 'n3'}.fetch(name)
end
def fetch_something
#fetch_something ||= #array_of_names.inject({}) do |results, name|
begin
results[name] = fetch_value!(name)
rescue NoMethodError, RuntimeError, KeyError
next(results)
end
results
end
end
p fetch_something
This code outputs:
{"name1"=>"n1", "name3"=>"n3"}
Also, I agree with #Алексей Кузнецов that each_with_object is probably the way to go whenever your block code mutates the data structure you're trying to build. So my preferred implementation of fetch_something might be more like this:
def fetch_something
#fetch_something ||= #array_of_names.each_with_object({}) do |name, results|
begin
results[name] = fetch_value!(name)
rescue NoMethodError, RuntimeError, KeyError
# error handling
end
end
end
Note that in my example the begin/end block is outside the assignment to results[name] in contrast to #Алексей Кузнецов's example which will assign nil to the hash every time an exception occurs.
Just use each_with_object
def fetch_something
#fetch_something ||= array_of_names.each_with_object({}) do |name, results|
results[name] ||= begin
fetch_value!(name)
rescue NoMethodError, RuntimeError
end
end
end

rescue_from being executed twice

I am attempting to rescue RuntimeErrors from within my controller using rescue_from, log it as a HealthNotice, and then redirect the user with a flash message, but I'm finding that in certain circumstances, the rescue_from method is being run twice (I end up with 2 duplicate HealthNotices). If the exception is rasied from a function called in a method (such as the test method below), it only creates one, but if I raise the exception directly within the method (such as test2), the rescue function is running twice (although strangely I don't get a double render error -- ??)
class Portal::BluerimController < PortalController
rescue_from RuntimeError, with: :error_handler
def error_handler(e)
hn_long_message = "#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}\n\nPARAMS:\n#{params}"
HealthNotice.create(name: "API Exception Handled", severity: "WARNING", short_message: e.message, long_message: hn_long_message)
flash[:notice] = e.message
redirect_to :action => :index and return
end
def test
Sotal.new.test # a method that raises a RuntimeError -- only 1 HealthNotice
end
def test2
raise "an error" # results in duplicate HealthNotice
end
end
Any suggestions on what I'm doing wrong? Thanks!

How to Rescue From An Error in Rake

How do I rescue from a
undefined method
error in this code
for user in #users do
#customer = Stripe::Customer.retrieve(user.stripe_customer_token)
if #customer.subscription.nil?
elsif #customer.subscription.plan.id == 2
user.silver_reset
elsif #customer.subscription.plan.id == 3
user.gold_reset
end
end
I have tried a plain rescue call, but rake isn't liking it.
What is way to rescue from the error?
UPDATE:
The way I was doing it
for user in #users do
#customer = Stripe::Customer.retrieve(user.stripe_customer_token)
rescue_from Exception => exception
# Logic
end
if #customer.subscription.nil?
elsif #customer.subscription.plan.id == 2
user.silver_reset
elsif #customer.subscription.plan.id == 3
user.gold_reset
end
end
The Error
/home/user/rails_projects/assignitapp/lib/tasks/daily.rake:25: syntax error, unexpected keyword_rescue, expecting keyword_end
rescue Exception => exception
Rake 0.9.2.2
Rails 3.2.5
Use try to wrap the problem method and return nil if it doesn't exist. E.g.:
unless #customer = Stripe::Customer.try(:retrieve, user.stripe_customer_token)
# Logic
end
Alternatively, this catches more errors:
unless #customer = Stripe::Customer.retrieve(user.stripe_customer_token) rescue nil
# Logic
end
Or this is more what you were trying for:
#users.each do |user|
begin
#customer = Stripe::Customer.retrieve(user.stripe_customer_token)
rescue StandardError => error
# handle error
end
end
I still haven't enough reputation to comment, but regarding the above answer's third option: Don't rescue from Exception!
Exception is the root of Ruby's exception hierarchy, so when you rescue Exception you rescue from everything, including subclasses such as SyntaxError, LoadError, and Interrupt.
If you want to learn more, check out Why is it a bad style to `rescue Exception => e` in Ruby?

Resources