NoMethodError при попытке доступа к хешу Ruby, но работает в irb - PullRequest
1 голос
/ 08 июля 2019

Я строю кассовый аппарат в Ruby со следующим хешем:



    @change = [
                  { :denomination => 0.01, :amount => 5 },
                  { :denomination => 0.02, :amount => 5 },
                  { :denomination => 0.05, :amount => 5 },
                  { :denomination => 0.10, :amount => 5 },
                  { :denomination => 0.20, :amount => 5 },
                  { :denomination => 0.50, :amount => 5 },
                  { :denomination => 1.00, :amount => 5 },
                  { :denomination => 2.00, :amount => 5 }
        ]

Когда пользователь совершает покупку, монеты, которые он использует, передаются в качестве аргумента в виде массива.


    def pay(coins = [])
        coins.each do |coin|
          coin = @change.find { |x| x[:denomination] == coin }
          coin[:amount] += 1
        end
     end

Это прекрасно работает, когда я вручную тестирую в irb со своего терминала.Однако, когда я пытаюсь запустить rspec, происходит сбой с NoMethodError:

 Failure/Error: coin[:amount] += 1

     NoMethodError:
       undefined method `[]' for nil:NilClass

Я действительно не могу понять, почему это происходит с NoMethodError, тем более, что он отлично работает в терминале.Может кто-нибудь, пожалуйста, помогите?

Спасибо :)

ОБНОВЛЕНИЕ:

Спецификация для этого:

describe '#pay' do
    it 'updates the change' do
      till.pay([0.5])
      expect(till.change).to include(:denomination=>0.5, :amount=>6)
    end
  end

1 Ответ

3 голосов
/ 08 июля 2019

Ошибка возникает при вызове [] в nil объекте.

Когда вы передаете наименование, которого нет в переменной экземпляра @change, результат @change.find { ... } будет nil,и вызов [] в объекте nil вызовет ошибку NoMethodError.

Это случай, который вы не рассмотрели, и это можно решить с помощью next в перечислителе each:

coins.each do |coin|
  current_denomination = @change.find { |x| x[:denomination] == coin }
  next unless current_denomination

  current_denomination[:amount] += 1
end

p pay([0.05, 0.05, 2.0, 100])

В случае, если нет изменений с той же монетой и номиналом, вы просто «переходите» к следующему элементу в монетах.

Обратите внимание, что текущая реализация @change позволяет вам повторитьхэши в массиве, которые могут привести к ошибкам, если номинал отличается от первоначального значения количества.Вы можете просто использовать Hash для хранения как значения, так и суммы в качестве значения ключа:

p [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1.0, 2.0].to_h { |a| [a, 5] }
# {0.01=>5, 0.02=>5, 0.05=>5, 0.1=>5, 0.2=>5, 0.5=>5, 1.0=>5, 2.0=>5}

Это Ruby 2.6 +.


В качестве незначительной настройки вашей реализации выможно переместить @change в отдельный метод, который вы можете затем вызвать и использовать, вызвав для него tap:

DENOMINATIONS = [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1.0, 2.0].freeze
HASH_KEYS = %i[denomination amount].freeze

def initial_coins
  DENOMINATIONS.zip(Array.new(8, 5)).map { |coin| HASH_KEYS.zip(coin).to_h }
end

def pay(coins = [])
  initial_coins.tap do |this|
    coins.each do |coin|
      current_denomination = this.find { |initial_coin| initial_coin[:denomination] == coin }
      next unless current_denomination

      current_denomination[:amount] += 1
    end
  end
end

pp pay([0.05, 0.05, 2.0, 100])
# [{:denomination=>0.01, :amount=>5},
#  {:denomination=>0.02, :amount=>5},
#  {:denomination=>0.05, :amount=>7},
#  {:denomination=>0.1, :amount=>5},
#  {:denomination=>0.2, :amount=>5},
#  {:denomination=>0.5, :amount=>5},
#  {:denomination=>1.0, :amount=>5},
#  {:denomination=>2.0, :amount=>6}]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...