为什么Ruby on Rails在编辑代码后虚假地提出"无法自动加载常量"?

Zan*_*aes 9 ruby ruby-on-rails

自动加载(和重新加载)通常在我的Ruby(2.3.0)on Rails(5.0.1)项目中正常工作.但是,在开发模式下,我偶尔会看到如下错误:

无法自动加载常量Foo :: Bar,预期/app/models/foo/bar.rb来定义它

这是意外的,因为:

  1. 第一个请求工作正常(它已经自动加载过一次).
  2. 它仅在编辑代码并发送新请求后出现.
  3. 它并不总是发生.我无法弄清楚为什么有时它无法重新加载.
  4. foo/bar.rb事实上,file()确实定义了Foo::Bar.

而且,代码foo/bar.rb很简单:

module Foo
  class Bar < CustomRecord
  end
end
Run Code Online (Sandbox Code Playgroud)

简单的解决方法是重新启动服务器,然后再次发送请求(总是成功).FWIW,我正在使用zeus server.

我最好的猜测是没有重新加载某些东西,但我不确定如何进行调试.我似乎无法指出导致问题的任何具体行动.有时编辑代码会导致它发生,有时不会.

小智 5

您可以找到有关Rails自动加载行为的一些很好的解释。该答案提供了有用的解释,该解释可从位于urbanautomaton.com的博客中获取

摘要:

基本上,Rails首先使用Ruby的常量查找。如果失败,则它具有自己的查找,如果

  • 它们不在自动加载路径中(如果config.autoload_paths += %W(#{config.root}/foo)未添加到config / application.rb中,Rails将找不到app / foo / bar.rb )
  • 不遵循目录名称/命名空间约定

Foo::Bar如果定义为,它将在app / foo / bar.rb中找到:

module Foo
  class Bar
  end
end
Run Code Online (Sandbox Code Playgroud)

但不是

module Foo
  module Bar
  end
end
Run Code Online (Sandbox Code Playgroud)
  • 引用了另一个名称空间中的常量,但该名称空间未在已自动加载的另一个文件中指示

从博客文章中引用我的意思的话:

至此,我们仅看到了单个常量名称如何映射到单个文件名。但是,正如我们所知道的,Ruby中的常量引用可以解析为许多不同的常量定义,这些常量定义根据引用所在的嵌套而有所不同。Rails如何处理呢?

答案是“部分”。由于Module#const_missing没有将嵌套信息传递给接收器,因此Rails不知道引用所在的嵌套,因此必须进行假设。对于对常量的任何引用Foo::Bar::Baz,它假定以下内容:

module Foo
  module Bar
    Baz # Module.nesting => [Foo::Bar, Foo]
end
Run Code Online (Sandbox Code Playgroud)

结束

换句话说,它假定给定常数参考的最大嵌套量。因此,示例参考与以下内容完全相同:

Foo::Bar::Baz # Module.nesting => []

module Foo::Bar
  Baz # Module.nesting => [Foo::Bar]
end
Run Code Online (Sandbox Code Playgroud)

尽管信息大量丢失,但是Rails确实可以使用一些额外的信息。它知道Ruby无法使用其常规查找来解析此特定的常量引用,这意味着它应引用的任何常量都无法加载。

Foo::Bar::Baz被称为,然后,Rails会尝试加载反过来下列常量,直到找到一个已加载:

  • Foo::Bar::Baz
  • Foo::Baz
  • Baz

一旦Baz 遇到已经加载的常量,Rails就会知道这不是Baz它要查找的常量,并且算法会引发一个NameError

在没有更多代码设置细节的情况下,很难说出到底是什么导致了您的问题,但是希望了解Rails中自动加载的情况可能会有所帮助。我强烈建议您阅读上面的博客文章和答案。

编辑为可能的解决方案:

确保将“ app / models / foo”添加到config / application.rb中的autoload_load路径中-或将“ foo”移至app / models / concerns,默认情况下应位于该目录中。

通常,添加显式require语句会有所帮助-尤其是当事情在开发中正确加载而不在测试中加载时。我最近通过添加一个需要所有嵌套文件的文件(我的目录结构没有镜像我的命名空间结构)来解决此问题,然后在我的测试助手中要求该文件。

第二次编辑

包括与问题有关的信息,为什么一个常数只能成功加载一次。

来自同一篇博客文章

如果仅在运行时首次遇到常量时才加载常量,那么必然地,其加载顺序取决于各个执行路径。这可能意味着相同的常量引用在相同代码的两次运行中解析为不同的常量定义。更糟糕的是,相同的常量引用连续两次可以给出不同的结果。

让我们回到最后一个例子。如果我们两次调用.print_qux会发生什么?

> Foo::Bar.print_qux
I'm in Foo!
=> nil
> Foo::Bar.print_qux
Run Code Online (Sandbox Code Playgroud)

NameError:未初始化的常量Foo :: Bar :: Qux这是灾难性的!首先,我们得到的结果是错误的,然后我们被错误地告知我们所引用的常量不存在。到底是什么导致了这一点?

像以前一样,第一次是由于嵌套信息的丢失。Rails不知道Foo :: Qux不是我们所追求的,因此一旦意识到Foo :: Bar :: Qux不存在,它就会很高兴地加载它。

但是,第二次,Foo :: Qux已经加载。因此,我们的引用不能涉及该常量,否则Ruby将解决该常量,并且永远不会调用自动加载功能。因此,即使我们的引用可以(并且应该)解析为尚未加载的:: Qux,查找也会以NameError终止。

我们可以通过首先引用:: Qux来解决此问题,并确保已加载它供Ruby解析该引用:

> Qux
=> "I'm at the root!"
> Foo::Bar.print_qux
I'm at the root!
=> nil
> Foo::Bar.print_qux
I'm at the root!
=> nil
Run Code Online (Sandbox Code Playgroud)

一个有趣的事情发生在这里。为了获得正确的行为,我们在使用它之前故意加载了所需的常量(尽管间接地是通过引用它,而不是加载定义它的文件)。

可是等等; 这不是可疑地接近用require显式加载我们的依赖项吗,自动加载本来可以使我们免于负担?

公平地说,我们也可以通过完全限定所有常量引用来解决此问题,即确保在.print_qux中引用的是:: Qux而不是模棱两可的Qux。但这仍然使我们失去了关于Ruby行为的现有直觉。而且,如果没有对自动加载过程的深入了解,我们将很难推断出这是必要的。