неопределенный метод `%' для nil:NilClass

Я получаю "NoMethodError: неопределенный метод `%' для nil:NilClass" для следующего блока кода:

class Timer
    attr_accessor :seconds

    def initialize
        @seconds = 0
    end

    def time_string

        if seconds < 10
            return "00:00:0" + seconds.to_s
        elsif seconds < 60
            return "00:00:" + seconds.to_s
        elsif seconds < 540
            minutes = seconds / 60
            seconds %= 60
            #seconds = seconds - (minutes * 60)
            return "00:0" + minutes.to_s + ":0" + seconds.to_s
        end
    end

    def timer
        @timer          
    end
end

Я знаю, что «секунды» — это Fixnum, потому что я получаю ошибку NoMethod: Fixnum, когда пытаюсь #поставить секунды без #to_s. Кроме того, операция «/» над «секундами» в предыдущей строке работает нормально. Так почему же я получаю сообщение об ошибке NoMethod:nilclass?

Почему я вообще получаю сообщение об ошибке? Разве «%» не должно работать везде, где работает «/»?

Работает следующий код:

        if seconds < 10
            return "00:00:0" + seconds.to_s
        elsif seconds < 60
            return "00:00:" + seconds.to_s
        elsif seconds < 540
            minutes = @seconds / 60
            seconds = @seconds % 60
            return "00:0" + minutes.to_s + ":0" + seconds.to_s
        end

Это как-то связано с переменными экземпляра и моим непониманием переменных экземпляра. хотел бы знать, как ноль попал туда.


person alightholder    schedule 27.08.2014    source источник
comment
Приведенное выше должно работать нормально, см.: rubyfiddle.com/riddles/dbaa9.   -  person Donal    schedule 28.08.2014
comment
Спасибо. Это сводит с ума.   -  person alightholder    schedule 28.08.2014
comment
Обновлено, чтобы показать весь класс, а не только метод. Эта закомментированная строка вызывает ту же ошибку. Как будто все операторы вдруг просто перестают работать.   -  person alightholder    schedule 28.08.2014
comment
Код работает, смотрите здесь: rubyfiddle.com/riddles/5e554. Ваша проблема где-то в другом месте, мне кажется.   -  person Donal    schedule 28.08.2014
comment
Я увидел это и заменил код rubyfiddle на свой собственный. получил тот же результат. Понятия не имею, где еще может быть проблема, это учебник, так что буквально только это и файл rake.   -  person alightholder    schedule 28.08.2014
comment
Как ты это называешь? Это от контроллера рельсов?   -  person Donal    schedule 28.08.2014
comment
В этом сообщении об ошибке должна быть указана конкретная строка. Ошибка указывает на то, что вы пытаетесь применить оператор % к объекту nil, что здесь явно не так, поскольку в противном случае seconds / 60 потерпит неудачу. Таким образом, сообщение об ошибке должно быть применимо к какой-то другой части кода, которую вы, возможно, не показываете. Вы говорите, что код состоит только из этого и файла rake. Какой рейк-файл? В файле rake может быть много кода.   -  person lurker    schedule 28.08.2014
comment
Попробуйте self.seconds %= 60   -  person Jaugar Chang    schedule 28.08.2014


Ответы (2)


Это взаимодействие между методами, вызываемыми в self, локальными переменными и отсутствием в Ruby синтаксических различий между этими вещами.

Если вы измените строку на эту:

         self.seconds %= 60

Тогда он работает нормально.

Проблема в том, что когда Ruby видит присвоение неполного имени, он создает локальную переменную с этим именем, а не ищет метод доступа.

Вот простая демонстрация:

irb(main):001:0> def foo=(n)
irb(main):002:1>  puts "Calling foo!"
irb(main):003:1>  @foo=n
irb(main):004:1> end    #=> nil
irb(main):005:0> foo=1    #=> 1
irb(main):006:0> @foo    #=> nil
irb(main):007:0> self.foo=2
Calling foo!
=> 2
irb(main):008:0> @foo    #=> 2
person Mark Reed    schedule 28.08.2014
comment
Спасибо. Это концепция, которую мне очень трудно обдумать. - person alightholder; 29.08.2014
comment
Это довольно просто. Когда Ruby видит квалифицированное присвоение, такое как foo.bar = baz, он ищет метод доступа bar= для объекта, на который ссылается foo. Но когда он видит неквалифицированное присваивание, такое как bar=baz, он не ищет никаких методов доступа, потому что объект отсутствует. Он просто создает локальную переменную. Тот факт, что неквалифицированный явный вызов метода будет отправлен на self, не применяется. - person Mark Reed; 30.08.2014

Я не уверен на 100% в этом, но я считаю, что это вопрос приоритета.

Поскольку операторы всегда имеют приоритет над методами, %= 2 оценивается до того, как оценивается метод получения seconds. Я полагаю, это и будет причиной NoMethodError.

Это также объясняет, почему использование @seconds работает, поскольку вы напрямую ссылаетесь на переменную экземпляра, а не используете метод получения, который attr_accessor создает за кулисами.

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

Редактировать: то, что я сказал выше, не может быть правильным, потому что методы, подобные этому, работают:

class Book
  attr_accessor :title, :length

  def midpoint
    length / 2
  end
end

Я думаю, что это намного проще, чем это, на самом деле. Я предполагаю, что %= работает так же, как и другие операторы присваивания, такие как +=, в том смысле, что запись seconds %= 60 аналогична записи seconds = seconds % 60.

Что, вероятно, происходит, так это то, что, поскольку вы присваиваете что-то seconds, ruby ​​​​интерпретирует это как новую локальную переменную с именем seconds. Когда %= "расширяется" до seconds = seconds % 60, seconds с правой стороны интерпретируется как та же самая локальная переменная, которая в настоящее время имеет значение nil. Следовательно, NoMethodError.

person Zajn    schedule 28.08.2014
comment
Правка - правильный ответ. Если вы замените seconds либо на self.seconds (явный вызов методов доступа), либо на @seconds (обход методов доступа и непосредственное обращение к переменной экземпляра), код будет работать так, как задумано. - person Mark Reed; 28.08.2014