Однажды я искал ошибку в коде и нашел это:

Проблема с этим кодом в строке 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).

Удачного взлома! Спасибо.