Спасибо — да действительно при замене потомка просто обновляет запись промежуточной таблицы.
Как я помню коварство has_one в том, что он не гарантирует что потомок будет только один. Из нескольких выбирает через SELECT LIMIT 1 и при таком обновлении наверно будут неприятные штуки.
Да, эта связь действительно не гарантирует что потомок будет один.
Но например какой-нибудь update has_one post.update(author: some_author) удалит старую связь.
Гарантировать уникальность можно другими способами, например добавить уникальный индекс в связующей таблице.
Метапрограммирование в реальной задаче