Rails - Rubocop - Begin + Rescue syntax - ruby-on-rails

I have the following code:
def payload
begin
#payload ||= Warden::JWTAuth::TokenDecoder.new.call(token)
rescue JWT::ExpiredSignature => e
Rollbar.warning(e)
end
end
From brief reading of a few blogs I'm supposed to be using begin rescue and end to handle the error as I'm doing above, however I'm getting a redundant 'begin' rubocop warning.
Is begin only used when specifying a bit of code that may cause an error within a larger block? And is it therefore redundant here?
Thanks in advance
EDIT: And if I don't need it, is it written as
def payload
#payload ||= Warden::JWTAuth::TokenDecoder.new.call(token)
rescue JWT::ExpiredSignature => e
Rollbar.warning(e)
end
?

Do this when the begin would be the first thing in your method
def payload
#payload ||= Warden::JWTAuth::TokenDecoder.new.call(token)
rescue JWT::ExpiredSignature => e
Rollbar.warning(e)
end

Method bodies, block bodies, and lambda bodies are implicit exception blocks. You don't need to wrap the entire code of a method body, block body, or lambda body in a begin / rescue / else / ensure / end exception block, since it is already implicitly one. So, whenever you have something like
def foo
begin
rescue
end
end
or
foo do
begin
rescue
end
end
or
-> do
begin
rescue
end
end
you can replace it with just
def foo
rescue
end
or the equivalent for blocks and lambdas.

Related

Where and how should I handle multiple exceptions?

I have problem with handling exceptions. I know how to do it, but I'm not sure what's the correct place to rescue them. For example:
class ExampleService
def call
...
raise ExampleServiceError, 'ExampleService message'
end
end
class SecondExampleService
def call
raise SecondExampleServiceError if something
ExampleService.call
rescue ExampleService::ExampleServiceError => e ---> should I rescue it here?
raise SecondExampleServiceError, e.message
end
end
Class ExampleController
def update
SecondExampleService.call
rescue ExampleService::ExampleServiceError, SecondExampleService::SecondExampleServiceError => e
render json: { error: e.message }
end
end
As you can see in example I have two Services. ExampleService raises his own exception. SecondExampleService call the ExampleService, can raise exception and is used in ExampleController. Where should I rescue ExampleServiceError? In ExampleController or SecondExampleService? If in ExampleController, what about when we use such a Service in multiple controllers (rescue code will be repeated many times)?
When the controller only calls SecondExampleService directly and doesn't know anything about ExampleService, then it should not rescue from ExampleServiceError.
Only SecondExampleService knows that ExampleService is used internally, and therefore SecondExampleService should rescue from ExampleServiceError and translate it into a SecondExampleServiceError.
That is my interpretation of the law of demeter.

How to forward a block to a method that generates methods

In my current project, I am having the following repetitive pattern in some classes:
class MyClass
def method1(pars1, ...)
preamble
# implementation method1
rescue StandardError => e
recovery
end
def method2(pars2, ...)
preamble
# implementation method2
rescue StandardError => e
recovery
end
# more methods with the same pattern
end
So, I have been thinking about how to dry that repetive pattern. My goal is to have something like this:
class MyClass
define_operation :method1, pars1, ... do
# implementation method1
end
define_operation :method2, pars2, ... do
# implementation method2
end
# more methods with the same pattern but generated with define_wrapper_method
end
I have tried to implement a kind of metagenerator but I have had problems for forwarding the block that would receive the generator. This is more or less what I have tried:
def define_operation(op_name, *pars, &block)
define_method(op_name.to_s) do |*pars|
preamble
yield # how can I do here for getting the block? <-----
rescue StandardError => e
recovery
end
end
Unfortunately, I cannot find a way for forwarding block to the define_method method. Also, it is very possible that the parameters, whose number is variable, are being passed to define_method in a wrong way.
I would appreciate any clue, help, suggestion.
You do not need metaprogramming to achieve this. Just add a new method that wraps the common logic like below:
class MyClass
def method1(param1)
run_with_recovery(param1) do |param1|
# implementation method1
end
end
def method2(param1, param2)
run_with_recovery(param1, param2) do |param1, param2|
# implementation method2
end
end
private
def run_with_recovery(*params)
preamble
yield(*params)
rescue StandardError => e
recovery
end
end
Test it here: http://rubyfiddle.com/riddles/4b6e2
If you really wanted to do metaprogramming, this will work:
class MyClass
def self.define_operation(op_name)
define_method(op_name.to_s) do |*args|
begin
puts "preamble"
yield(args)
rescue StandardError => e
puts "recovery"
end
end
end
define_operation :method1 do |param1|
puts param1
end
define_operation :method2 do |param1, param2|
puts param1
puts param2
end
end
MyClass.new.method1("hi")
MyClass.new.method2("hi", "there")
Test this here: http://rubyfiddle.com/riddles/81b9d/2
If I understand correctly you are looking for something like:
class Operation
def self.op(name,&block)
define_method(name) do |*args|
op_wrap(block.arity,*args,&block)
end
end
def op_wrap(arity=0,*args)
if arity == args.size || (arrity < 0 && args.size >= arity.abs - 1)
begin
preamble
yield *args
rescue StandardError => e
recovery
end
else
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected #{arity < 0 ? (arity.abs - 1).to_s.+('+') : arity })"
end
end
def preamble
puts __method__
end
def recovery
puts __method__
end
end
So your usage would be
class MyClass < Operation
op :thing1 do |a,b,c|
puts "I got #{a},#{b},#{c}"
end
op :thing2 do |a,b|
raise StandardError
end
def thing3
thing1(1,2,3)
end
end
Additionally this offers you both options presented as you could still do
def thing4(m1,m2,m3)
#m1 = m1
op_wrap(1,'inside_wrapper') do |str|
# no need to yield because the m1,m2,m3 are in scope
# but you could yield other arguments
puts "#{str} in #{__method__}"
end
end
Allowing you to pre-process arguments and decide what to yield to the block
Examples
m = MyClass.new
m.thing1(4,5,6)
# preamble
# I got 4,5,6
#=> nil
m.thing2('A','B')
# preamble
# recovery
#=> nil
m.thing3
# preamble
# I got 1,2,3
#=> nil
m.thing1(12)
#=> #<ArgumentError: wrong number of arguments (given 1, expected 3)>

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 inside an each_line code block

I'm completely "fresh" to Ruby and coming from a .NET background.
I'm trying to create a really simple rake task to import file with json objects into my database.
My IDE is raising an error when I'm trying to rescue inside the code block of each_line
File.open('etc/metrics_json.log').each_line do |line|
metric_hash = JSON.parse(line)
Metric.create(metric_hash)
rescue RuntimeError => e
puts e.message
end
the error is "syntax error, unexpected keyword_rescue, expecting keyword_end"
what am I doing wrong?
You could use it like
File.open('etc/metrics_json.log').each_line do |line|
begin
metric_hash = JSON.parse(line)
Metric.create(metric_hash)
rescue RuntimeError => e
puts e.message
end
end
you can use method def as begin. For eg
def example_method
....
rescue
....
end
if not, you need to use begin end.
begin
metric_hash = JSON.parse(line)
Metric.create(metric_hash)
rescue RuntimeError => e
puts e.message
end

DRY code in rescue => e in various model files in Rails

In sidekiq_batch.rb,
def sidekiq_status
begin
something
rescue => e
Rails.logger.error("\nRESCUENIL in sidekiq_status #{e.class} #{e.message} in #{e.backtrace}")
# FIXME RESCUENIL
nil
end
end
In checkin.rb,
def attached_receipt_image
begin
something else
rescue => e
Rails.logger.error("\nRESCUENIL in attached_receipt_image #{e.class} #{e.message} in #{e.backtrace}")
# FIXME RESCUENIL
nil
end
end
In barcode.rb,
def receipt_check?
begin
some code
rescue => e
Rails.logger.error("\nRESCUENIL in receipt_check #{e.class} #{e.message} in #{e.backtrace}")
# FIXME RESCUENIL
nil
end
end
Need to DRY up the code. How can I write a common error-logging routine for all of these methods in my models?
You can write an abstraction for that, but you cannot return from there. You can write:
def with_log(name)
begin
yield
rescue => exc
Rails.logger.error("\nRESCUENIL in #{name} #{exc.class} #{exc.message} in #{exc.backtrace}")
false
end
end
with_log(:sidekiq_status) do
something
true # not needed if something returns a boolean with the success status
end or return
This true can also be moved to with_log, it depends on how you plan to use it.

Resources