0

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
Severin
  • 8,508
  • 14
  • 68
  • 117

2 Answers2

2

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.

user12341234
  • 6,573
  • 6
  • 23
  • 48
0

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