使用'lib'命名空间/自动加载不一致的“LoadError”行为

时间:2011-08-16 16:46:44

标签: ruby-on-rails-3 namespaces

我们刚刚在'lib'中创建了一个新文件,它产生了一系列涉及加载错误的麻烦。

/lib/response_set.rb:

module MyCompany
  class ResponseSet < Array
    ...
  end
end

/spec/lib/response_set_spec.rb

require 'spec_helper'

describe MyCompany::ResponseSet do
  describe "..." do
    ...
  end
end

在Rspec中运行此规范时,在到达第一个'describe'时会出现以下错误:

/Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:492:in `load_missing_constant': Expected /Users/my_stuff/projects/my_project/lib/response_set.rb to define ResponseSet (LoadError)
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:183:in `block in const_missing'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:181:in `each'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:181:in `const_missing'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/rspec-core-2.5.1/lib/rspec/core/backward_compatibility.rb:20:in `const_missing'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/rspec-expectations-2.5.0/lib/rspec/expectations/backward_compatibility.rb:6:in `const_missing'
    from /Users/my_stuff/projects/my_project/spec/lib/response_set_spec.rb:4:in `<top (required)>'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/rspec-core-2.5.1/lib/rspec/core/configuration.rb:386:in `load'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/rspec-core-2.5.1/lib/rspec/core/configuration.rb:386:in `block in load_spec_files'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/rspec-core-2.5.1/lib/rspec/core/configuration.rb:386:in `map'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/rspec-core-2.5.1/lib/rspec/core/configuration.rb:386:in `load_spec_files'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/rspec-core-2.5.1/lib/rspec/core/command_line.rb:18:in `run'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/rspec-core-2.5.1/lib/rspec/core/runner.rb:55:in `run_in_process'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/rspec-core-2.5.1/lib/rspec/core/runner.rb:46:in `run'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/rspec-core-2.5.1/lib/rspec/core/runner.rb:10:in `block in autorun'

无论其!我们长期使用许多其他文件具有相同的结构。例如,这是另一个自创建以来一直运行良好的:

/lib/smart_set.rb

module MyCompany
  class SmartSet < Array
    ...
  end
end

和/spec/lib/smart_set_spec.rb

require 'spec_helper'

describe MyCompany::SmartSet do
  describe "..." do
    ...
  end
end

此文件具有相同的结构,但完全没有问题。

ResponseSet(问题类)显然有加载问题,没有明显的原因。在rails控制台中,我第一次尝试创建一个,我收到一个错误,但之后我可以创建一个:

Loading development environment (Rails 3.0.4)
ruby-1.9.2-p136 :001 > rs = MyCompany::ResponseSet.new
LoadError: Expected /Users/my_stuff/projects/my_project/lib/response_set.rb to define ResponseSet
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:492:in `load_missing_constant'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:183:in `block in const_missing'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:181:in `each'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:181:in `const_missing'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:503:in `load_missing_constant'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:183:in `block in const_missing'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:181:in `each'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:181:in `const_missing'
    from (irb):1
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/railties-3.0.4/lib/rails/commands/console.rb:44:in `start'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/railties-3.0.4/lib/rails/commands/console.rb:8:in `start'
    from /Users/my_stuff/.rvm/gems/ruby-1.9.2-p136@my_project/gems/railties-3.0.4/lib/rails/commands.rb:23:in `<top (required)>'
    from script/rails:6:in `require'
    from script/rails:6:in `<main>'
ruby-1.9.2-p136 :002 > rs = MyCompany::ResponseSet.new
 => [] 

另外,添加

require 'response_set'
位于response_set_spec.rb顶部的

允许运行这些测试。但是smart_set_spec.rb不需要这样的东西。

application.rb中有以下内容:

config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]
config.autoload_paths += Dir["#{config.root}/app/models/**/"]

现在,我理解Rails对于文件结构如何与这些类型的事物的命名空间结构匹配有一些意见,我们为此重组了我们的模块和文件。它看起来已经解决了这个问题(虽然我们在运行完整的测试套件时会看到一些其他奇怪的负载错误 - 这些已经神秘地消失了)。尽管如此,这里的每个人都感到困惑,并且对Rails如此不一致并且我们想知道原因并不感到有点恼火。正如您所看到的那样,就命名空间和文件结构而言,有两个文件完全相同,完全不同。事实上,我们在“lib”的顶层有大约十几个其他文件,这些文件具有类似的命名空间,从未引起任何问题。谁能解释一下这里到底发生了什么?

5 个答案:

答案 0 :(得分:27)

我们遇到了类似的问题,经过挖掘后,Rails 3.x和autoload_paths发生了变化。

我们的案例仅出现在测试中(RAILS_ENV=test)。当Rails加载控制器时,它忙于寻找每个控制​​器的匹配模型(由于在初始化器中设置了ActionController :: Base.wrap_parameters)。最终它结束了上面提到的方法(load_missing_constant)。由于我们的autoload_paths包含lib和lib / **,因此Rails从lib下的所有子目录中提取了所有文件。不幸的是,从子目录加载时似乎忽略了隐含的命名空间。它期望foo / base.rb定义BaseFoo::Base。这似乎是核心缺陷:load_missing_constant调用search_for_file,它返回名称匹配的任何文件(例如,在我的示例中,返回foo / base.rb,因为它匹配base.rb)。

很难说这是否是Rails中的错误 - 因为它违反了Ruby假定的命名空间到目录的映射 - 或者滥用了autoload_paths。

我们现在解决了这个问题,方法是从lib/**中删除autoload_paths并向application.rb添加必要的require语句。

答案 1 :(得分:4)

我查看了rails源,并且有一个

if file_path && ! loaded.include?(File.expand_path(file_path)) # We found a matching file to load
  require_or_load file_path
  raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless local_const_defined?(from_mod, const_name)
  return from_mod.const_get(const_name)
elsif ...
load_missing_constant方法中的

子句。我可能会猜测,require_or_load之前调用了raise,这可能是您示例中第二次调用没有错误的原因......

看一个具有相同结构的两个文件表现不同的最小示例会很有趣。 在你的位置,我会复制一个应用程序,并在不一致的行为出现时继续从中删除部分,以查看最小的不一致的例子。

P.S。我在这里提交了一个类似的问题:http://www.ruby-forum.com/topic/2376956

答案 2 :(得分:4)

Brendan指出了正确的方向,检查你的autoload_paths。我有一个类似的错误,这是我在application.rb中的罪魁祸首:

config.autoload_paths += Dir["#{Rails.root}/app/models/[a-z]*"]

一旦我开始使用模块的模块和子目录,我的应用程序就不高兴了。我改变了我的自动加载只是非模块目录:

config.autoload_paths += Dir["#{Rails.root}/app/models/aaaaaaaaa"]
config.autoload_paths += Dir["#{Rails.root}/app/models/bbbbbbbbb"]

答案 3 :(得分:1)

我遇到了同样错误地自动加载命名空间类的问题。这是解决它的线索(来自Brendan的回答):

  

由于我们的autoload_paths包含了lib和lib / **,因此Rails从lib下的所有子目录中提取了所有文件。不幸的是,从子目录加载时似乎忽略了隐含的命名空间。它期望foo / base.rb定义Base vs. Foo :: Base。这似乎是核心缺陷:load_missing_constant调用search_for_file,它返回名称匹配的任何文件(例如,在我的示例中,返回foo / base.rb,因为它匹配base.rb)。

我已经通过将名为Events::Something的所有命名空间类移动到app/components/events/*并使用以下内容创建文件app/components/events.rb来解决此问题:

%w(file1 file2 ...).each do |file|  
  require "events/#{file}"
end

虽然这只是一点手动,但它适用于应用程序,控制台和测试模式。

答案 4 :(得分:0)

值得注意的另一点是,您是否在服务文件夹中有正确的路径。

通过正确的路径,我的意思是

引擎名称/应用/服务/引擎名称/ your_service.rb

我不小心忘记了服务中的engine_name文件夹,并得到了这种奇怪的行为。