Почему операторы Ruby ERB if устанавливают переменные в ноль?

Я использую ERB для генерации набора инструкций с данными о местоположении и хочу принять смещение во входных данных. Однако когда я пытаюсь проверить, присутствует ли это смещение, оно устанавливает его значение равным нулю. Если кто-нибудь может помочь объяснить, что происходит, мы будем очень признательны!

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

<%
    # apply a default value of 0 to the offset hash
    p offset.nil?
    p offset
    p offset.nil?
    p offset

    if offset.nil?
        p "I ran the code inside the if statement"
        offset = {}
        p "offset has been initialized, since it was nil"
    end

    p offset.nil?
    p offset
%>

и вывод:

false
{"z"=>-10}
false
{"z"=>-10}
true
nil

Если я удалю оператор if, я получу ожидаемый результат:

false
{"z"=>-10}
false
{"z"=>-10}
false
{"z"=>-10}

но наличие этого оператора if (или подобного ему) очень важно. Я нашел обходной путь с помощью блока начала восстановления, который по сути делает то, что должен был делать If, но мне очень любопытно, почему это происходит.

ты уверен, что у тебя есть if offset.nil?, а не что-то вроде if offset = nil

Alex 14.06.2024 22:24

Тройная проверка того, какой код вы на самом деле выполняете.

Schwern 14.06.2024 22:26

if offset == nil дает тот же результат; и после тройной проверки это код, который я выполняю

Daniel Cusumano 14.06.2024 22:36

извини @Алекс, я неправильно понял твой вопрос, я использую if offset.nil? точно, без опечатки

Daniel Cusumano 14.06.2024 22:38

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

Daniel Cusumano 14.06.2024 22:38

Я скопировал ваш код, добавил offset = :foo в начале и запустил его с erb file.erb, но не могу это воспроизвести.

Dogbert 14.06.2024 22:50

Какая версия эрба и рубина?

Schwern 14.06.2024 22:52

Ruby 2.7.0, я не знаю, как проверить мою версию erb

Daniel Cusumano 14.06.2024 22:57

@Dogbert Я тоже попробовал, и кажется, что если я определяю смещение в ERB, оно работает нормально, проблемы возникают только тогда, когда смещение передается в шаблон.

Daniel Cusumano 14.06.2024 23:00

Похоже, проблема с вашим отступом. Попробуйте вместо этого: написать каждую строку между <% %> вместо использования нескольких строк и перейти оттуда, чтобы выяснить, почему ваша не сработала.

Himmelspiegel 14.06.2024 23:04

@DanielCusumano, можете ли вы создать минимальный пример, воспроизводящий проблему? stackoverflow.com/help/minimal-reproducible-example

Dogbert 14.06.2024 23:27

Ruby 2.7.0 сильно устарел, возможно, вы просто столкнулись с ошибкой Ruby. Попробуйте хотя бы последнюю версию 2.7, 2.7.8, в которой будут исправлены ошибки.

Schwern 15.06.2024 01:30
gem list покажет вам все ваши драгоценные камни и их версии.
Schwern 15.06.2024 01:32

Пожалуйста, покажите нам, как вы управляете Эрбом.

Schwern 15.06.2024 01:34
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
1
14
107
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я предполагаю, что вначале при вызове offset вы вызываете метод, который возвращает значение {"z"=>-10}, например. метод во включенном вспомогательном модуле.

Теперь в Ruby локальные переменные имеют приоритет перед одноименными методами. Таким образом, если существует локальная переменная с именем offset, при ссылке на это имя вы получаете значение этой переменной, а не результат вызова метода с тем же именем.

Теперь в Ruby, когда вы определяете любой код, который может устанавливать переменную, эта переменная инициализируется с помощью nil, даже если код, который устанавливает начальное значение, фактически никогда не запускается. Это похоже на подъем переменных в JavaScript (но не полностью):

defined?(some_variable)
# => nil

if false
  some_variable = 'value'
end

defined?(some_variable)
# => "local-variable"

Таким образом, поскольку вы устанавливаете offset = {} в теле вашего блока if, впоследствии у вас существует локальная переменная offset, которая затеняет метод offset.

Если вы хотите всегда вызывать метод offset, вы можете указать Ruby вызывать этот метод, добавив круглые скобки (которые в противном случае не являются обязательными).

p offset
# => {"z"=>-10}

if offset.nil?
  offset = {}
end

p offset
# => nil
p defined?(offset)
# => "local-variable"

p offset()
# => {"z"=>-10}
p defined?(offset())
# => "method"

Чтобы реализовать ваш реальный вариант использования, вы также можете использовать что-то вроде этого вместо оператора if, чтобы установить переменную offset либо как результат метода offset, либо как пустой хеш, если метод возвращает nil или false:

offset = offset() || {}

Это имеет смысл, и ваша реализация работает отлично. Спасибо за ответ!

Daniel Cusumano 18.06.2024 18:03

@DanielCusumano Если ответ оказался для вас полезным, вы можете рассмотреть возможность принять его.

Holger Just 18.06.2024 18:08

сделанный! Извините, я новичок в переполнении стека

Daniel Cusumano 19.06.2024 20:27

Другие вопросы по теме