Однажды я искал ошибку в коде и нашел это:
Проблема с этим кодом в строке 12: при использовании find_or_initialize_by
метод block вызывается только для инициализированного экземпляра. Таким образом, существующий экземпляр таймлапса не передается в build_timelapse
и не обновляется в нашем случае.
Хорошо, давайте удалим этот блок из вызова find_or_initialize_by
. У нас должно получиться что-то вроде этого:
Самый очевидный способ — переместить метод build_timelapse
в модель Timelapse — в этом случае мы просто вызовем t.build_timelapse
или t.build_from_schedule
, т.е. переименованную версию. Но тут я начал себя мучить: это нужно делать только для запланированных таймлапсов, так зачем же добавлять этот метод в каждый инстанс? Давайте найдем другой способ, даже если он будет сложным![1]
Хорошо, если мы найдем способ вызвать этот метод в контексте таймлапса, нам даже не понадобится эта противная локальная переменная. Только это:
Но как мы это делаем? Очень известный способ — instance_eval
, но это не совсем то, что нам здесь нужно, так как мы собираемся устанавливать значения в нашем методе (вероятно, заменяя timelapse
на self
), что означает передачу параметров, а instance_eval
для этой задачи не подходит [2]. instance_exec
спешит на помощь?
Не так быстро! Документация говорит, что этот метод выполняет данный блок в контексте получателя, и self
устанавливается на объект получателя. И он также обрабатывает аргументы для блока. Отлично, но нам все еще нужно как-то пройти блок. Да, мы можем превратить метод в блок кода: что-то вроде &method(:build_timelapse).to_proc
. Но это не сработает. В этом случае метод не начнет вести себя как proc. Этот метод привязан к экземпляру ScheduleTimelapse, а не Timelapse, и он не будет правильно принимать вызов self
. Даже если мы не привязываемся, его можно привязать только к экземпляру класса ScheduleTimelapse.
Это было моим главным огорчением — не было возможности сделать именно то, что я планировал сделать, и мне пришлось преобразовать этот метод в лямбду, принимающую один аргумент schedule
. Наконец, я смог переписать код следующим образом:
[1] Мы можем сделать build_timelapse(timelapse, schedule)
и просто заставить этот метод возвращать в результате timelapse
. Это должно работать, но мне не нравится такой способ, так как он не позволит методу быть своего рода «чистой функцией».
[2] В документах есть примечание по поводу аргументов, но разница явно не указана. Вы можете найти дополнительную информацию в статье о геме FactoryGirl (теперь это FactoryBot).
Удачного взлома! Спасибо.