Rails 6 отношение has_one явное имя класса не работает - PullRequest
0 голосов
/ 17 февраля 2020

В настоящее время я обновляю приложение с rails 5.2 до 6.0.2. Использование ruby 2.6.3 Одна из проблем, с которыми я сталкиваюсь, связана с предполагаемым классом для отношений. У меня есть две модели

class Site::Page::Metadata < ApplicationRecord
  belongs_to :page, class_name: "Site::BlockPage"
end

и

class Site::BlockPage < ApplicationRecord
  has_one :metadata, dependent: :destroy, class_name: "Site::Page::Metadata", foreign_key: "page_id"
end

После перехода на рельсы 6 с настройкой, как указано выше, я получаю неинициализированную постоянную ошибку при попытке доступа к метаданным страниц. ActionView::Template::Error: uninitialized constant Site::BlockPage::Site::Page::Metadata Там, где не выглядит правильно выводить класс отношения. Однако, когда я размышляю об ассоциации, она выглядит правильно

irb(main):001:0> Site::BlockPage.reflect_on_association(:metadata)
=> #<ActiveRecord::Reflection::HasOneReflection:0x00007fbeffea7020 @name=:metadata, @scope=nil, @options={:dependent=>:destroy, :class_name=>"Site::Page::Metadata", :foreign_key=>"page_id", :autosave=>true}, @active_record=Site::BlockPage (call 'Site::BlockPage.connection' to establish a connection), @klass=nil, @plural_name="metadata", @type=nil, @foreign_type=nil, @constructable=true, @association_scope_cache=#<Concurrent::Map:0x00007fbeffea6ee0 entries=0 default_proc=nil>>

Затем я попытался обновить отношение на Site::BlockPage, чтобы оно было

has_one :metadata, dependent: :destroy, class_name: "::Site::Page::Metadata", foreign_key: "page_id"

С префиксом пространства имен root и оно теперь ссылается на класс ActiveStorageValidations::Metadata из active_storage_validations gem.

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

1 Ответ

3 голосов
/ 17 февраля 2020

Вероятно, это результат перехода на новый автозагрузчик Zeitwerk. В вашем коде обнаружена ошибка, из-за которой работал старый автозагрузчик Rails.

class Site::BlockPage
  has_one :metadata, dependent: :destroy, class_name: "Site::Page::Metadata", foreign_key: "page_id"
end

Здесь Site::Page::Metadata неоднозначно. Это может означать Site::BlockPage::Site::Page::Metadata или Site::Page::Metadata.

Если Site::Page::Metadata уже загружен, у вас все хорошо.

Если это не так, автозагрузчик Rails вступает во владение.

До Zeitwerk это работало из-за работы автозагрузчика Rails. Это будет ловить исключение, когда константа отсутствует, и затем искать путь к файлу, который соответствует. В этом случае он попытается Site::BlockPage::Site::Page::Metadata, получит исключение и будет искать site/block_page/site/page/metadata.rb. Этого не существует Этого не существует, поэтому он пытается Site::Page::Metadata и ищет site/page/metadata.rb. Это существует, и это то, что загружается.

Zeitwerk делает обратное. Когда Rails запускается, он сканирует ваши каталоги, выводит имя класса из имени файла и регистрирует его для автоматической загрузки из этого файла. Например, если он находит app/models/site/page/metadata.rb, он запускает Site::Page.autoload(:Metadata, app/models/site/page/metadata.rb), регистрируя ссылки на Site::Page::Metadata для загрузки app/models/site/page/metadata.rb. Константа Site::Page::Metadata существует, но ее содержимое еще не загружено.

Когда Rails 6 ищет Site::BlockPage::Site::Page::Metadata, он получает исключение и все. Zeitwerk не улавливает исключение и продолжает поиск, как это делал старый автозагрузчик.

Поскольку Zeitwerk создает константы для каждого класса при запуске, это намного надежнее. Это не зависит от порядка загрузки классов. Но он не совпадает с особенностями старого автозагрузчика Rails.

По этой и другим причинам предпочтительно указывать вложенные классы и модули. И очень важно, чтобы путь к файлу соответствовал ожиданиям Rails.

# app/models/site/block_page.rb
module Site
  class BlockPage < ApplicationRecord
    has_one :metadata, dependent: :destroy, class_name: "Site::Page::Metadata", foreign_key: "page_id"
  end
end

# app/models/site/page/metadata.rb
module Site
  class Page
    class Metadata < ApplicationRecord
      belongs_to :page, class_name: "Site::BlockPage"
    end
  end
end

Вы можете увидеть, как Zeitwerk загружает ваши классы с помощью Rails.autoloaders.main или бросая Rails.autoloaders.log! в config/application.rb. См. Загрузка и перезагрузка констант (режим Zeitwerk) - Устранение неполадок .

tl; dr

См. Понимание Zeitwerk в Rails 6 Марсело Касираги.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...