Непоследовательное поведение "LoadError" с использованием "lib" namespacing/autoloading
Мы только что создали новый файл в '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 дает нам следующую ошибку, когда она попадает в первый "описать":
/Users/my_stuff/.rvm/gems/[email protected]_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/[email protected]_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:183:in `block in const_missing'
from /Users/my_stuff/.rvm/gems/[email protected]_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:181:in `each'
from /Users/my_stuff/.rvm/gems/[email protected]_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:181:in `const_missing'
from /Users/my_stuff/.rvm/gems/[email protected]_project/gems/rspec-core-2.5.1/lib/rspec/core/backward_compatibility.rb:20:in `const_missing'
from /Users/my_stuff/.rvm/gems/[email protected]_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/[email protected]_project/gems/rspec-core-2.5.1/lib/rspec/core/configuration.rb:386:in `load'
from /Users/my_stuff/.rvm/gems/[email protected]_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/[email protected]_project/gems/rspec-core-2.5.1/lib/rspec/core/configuration.rb:386:in `map'
from /Users/my_stuff/.rvm/gems/[email protected]_project/gems/rspec-core-2.5.1/lib/rspec/core/configuration.rb:386:in `load_spec_files'
from /Users/my_stuff/.rvm/gems/[email protected]_project/gems/rspec-core-2.5.1/lib/rspec/core/command_line.rb:18:in `run'
from /Users/my_stuff/.rvm/gems/[email protected]_project/gems/rspec-core-2.5.1/lib/rspec/core/runner.rb:55:in `run_in_process'
from /Users/my_stuff/.rvm/gems/[email protected]_project/gems/rspec-core-2.5.1/lib/rspec/core/runner.rb:46:in `run'
from /Users/my_stuff/.rvm/gems/[email protected]_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/[email protected]_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:492:in `load_missing_constant'
from /Users/my_stuff/.rvm/gems/[email protected]_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:183:in `block in const_missing'
from /Users/my_stuff/.rvm/gems/[email protected]_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:181:in `each'
from /Users/my_stuff/.rvm/gems/[email protected]_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:181:in `const_missing'
from /Users/my_stuff/.rvm/gems/[email protected]_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:503:in `load_missing_constant'
from /Users/my_stuff/.rvm/gems/[email protected]_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:183:in `block in const_missing'
from /Users/my_stuff/.rvm/gems/[email protected]_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:181:in `each'
from /Users/my_stuff/.rvm/gems/[email protected]_project/gems/activesupport-3.0.4/lib/active_support/dependencies.rb:181:in `const_missing'
from (irb):1
from /Users/my_stuff/.rvm/gems/[email protected]_project/gems/railties-3.0.4/lib/rails/commands/console.rb:44:in `start'
from /Users/my_stuff/.rvm/gems/[email protected]_project/gems/railties-3.0.4/lib/rails/commands/console.rb:8:in `start'
from /Users/my_stuff/.rvm/gems/[email protected]_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" с похожим пространством имен, которые никогда не вызывали никаких проблем. Может кто-нибудь объяснить, что здесь происходит?
Ответы
Ответ 1
У нас была аналогичная проблема, которая после копания оказалась вызвана изменениями в 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 определит Base
vs. Foo::Base
. Это, по-видимому, основной недостаток: load_missing_constant
вызывает search_for_file
, который возвращает любой файл, имя которого совпадает (например, в моем примере, foo/base.rb был возвращен по совпадению base.rb).
Трудно сказать, является ли это ошибкой в Rails, поскольку она нарушает отображение пространства имен в каталог, предполагаемое Ruby, или неправильное использование autoload_paths.
Мы уже работали над этим, удалив lib/**
из нашего autoload_paths
и добавив необходимые необходимые инструкции в application.rb.
Ответ 2
Я просмотрел источник рельсов, и есть
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
Ответ 3
Указывается в правильном направлении Бренданом, проверьте свои 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"]
Ответ 4
Я страдал от той же самой проблемы, когда классы с именами были автоматически загружены автозагрузкой. Это ключ к его решению (из ответа Брендана):
Поскольку наши 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
И хотя это немного ручно, оно работает как для приложений, так и для консольных и тестовых режимов.
Ответ 5
Еще один момент, который стоит отметить, заключается в том, что у вас есть правильный путь в папке служб.
По правильному пути я имею в виду
ENGINE_NAME/приложение/услуги/ENGINE_NAME/your_service.rb
Я случайно забыл папку engine_name внутри служб и получил это странное поведение.