Когда класс Ruby не является тем классом Ruby?
-
16-09-2019 - |
Вопрос
У меня есть этот код в моем контроллере для приложения Rails:
def delete
object = model.datamapper_class.first(:sourced_id => params[:sourced_id])
if object.blank?
render :xml => "No #{resource} with sourced_id #{params[:sourced_id]}", :status => :not_found and return
end
object.destroy
render :xml => "", :status => :no_content
rescue MysqlError => e
puts "raised MysqlError #{e.message}"
render :xml => e.message, :status => :unprocessable_entity and return
rescue Mysql::Error => e
puts "raised Mysql::Error #{e.message}"
render :xml => e.message, :status => :unprocessable_entity and return
rescue Exception => e
puts "not a MysqlError, instead it was a #{e.class.name}"
render :xml => e.message, :status => :unprocessable_entity and return
end
Когда я запускаю свою спецификацию, чтобы убедиться, что мои ограничения внешнего ключа работают, я получаю следующее:
not a MysqlError, instead it was a MysqlError
Что здесь может происходить?
Некоторая информация о предках:Когда я изменю rescue, чтобы получить это:
puts MysqlError.ancestors
puts "****"
puts Mysql::Error.ancestors
puts "****"
puts e.class.ancestors
Это то, что я получаю:
Mysql::Error
StandardError
Exception
ActiveSupport::Dependencies::Blamable ...
****
Mysql::Error
StandardError
Exception
ActiveSupport::Dependencies::Blamable ...
****
MysqlError
StandardError
Exception
ActiveSupport::Dependencies::Blamable ...
Может ли быть псевдоним в глобальном пространстве имен, который делает класс MysqlError недоступным?
Решение 2
Это была простая ошибка переопределения класса. Руби позволяет переопределить постоянную верхнюю часть, но не разрушает оригинальную константу, когда вы это делаете. Объекты, которые все еще содержат ссылки на эту константу, все еще могут использовать ее, поэтому их все еще можно использовать для создания исключений, как в том вопросе, который у меня был.
Поскольку мое переопределение происходило в зависимостях, я решил это, поиск исходного класса в пространстве объектов и придерживаясь ссылки на него, чтобы использовать при ловли исключений. Я добавил эту строку в свой контроллер:
ObjectSpace.each_object(Class){|k| @@mysql_error = k if k.name == 'MysqlError'}
Это получает ссылку на оригинальную версию Mysqlerror. Тогда я смог сделать это:
rescue @@mysql_error => e
render :xml => e.message, :status => :unprocessable_entity and return
Это происходит потому, что драгоценный камень MySQL загружается после того, как MySQLerror уже определен. Вот некоторая тестовая консоль радость:
Loading test environment (Rails 2.3.2)
>> MysqlError.object_id
=> 58446850
>> require 'mysql'
C:/Ruby/lib/ruby/gems/1.8/gems/mysql-2.7.3-x86-mswin32/ext/mysql.so: warning: already initialized constant MysqlError
=> true
>> MysqlError.object_id
=> 58886080
>> ObjectSpace._id2ref(MysqlError.object_id)
=> Mysql::Error
Вы можете сделать это в IRB без необходимости довольно легко; Вот трюк, который работает, потому что IRB не смотрит хэш по имени каждый раз, когда вы объявляете хэш -литерал:
irb(main):001:0> Hash = Class.new
(irb):1: warning: already initialized constant Hash
=> Hash
irb(main):002:0> hash = {:test => true}
=> {:test=>true}
irb(main):003:0> hash.class
=> Hash
irb(main):004:0> hash.is_a? Hash
=> false
Я понимаю, почему вы, возможно, захотите сделать это, его можно использовать, как Alias_method_chain для глобального пространства имен. Вы можете добавить мутекс в класс, который не является Threadsafe, например, и не нуждается в изменении старого кода, чтобы ссылаться на вашу версию Threadsafe. Но я бы хотел, чтобы RSPEC не замолчал это предупреждение.
Другие советы
Классы Ruby - это просто объекты, поэтому сравнение основано на идентификации объекта (т. Е. на одном и том же указателе под капотом).
Не уверен, что происходит в вашем случае, но я бы попробовал отладку в нескольких местах и посмотрел, какие идентификаторы объектов и предки вы получаете для MysqlError.Я подозреваю, что в разных модулях есть два таких объекта, и ваше предложение catch ссылается не на тот.
Редактировать:
Это довольно странно.Теперь я предполагаю, что MysqlError или один из его предков был включен в двух разных точках цепочки классов вашего контроллера, и это каким-то образом приводит к перехвату исключения.
Теория № 2 заключалась бы в том, что, поскольку rails переопределяет const_missing для выполнения автоматических запросов, где вы ожидали бы получить UndefinedConstant exception в предложениях обработки исключений, вместо этого вы находите что-то с таким именем бог знает где в исходном дереве.Вы должны быть в состоянии увидеть, так ли это, протестировав с автоматическим требованием выключения (т. Е. выполните некоторые отладки как в режиме разработки, так и в режиме prod).
Существует синтаксис для принудительного запуска вашей ссылки из корневого каталога, который может оказать некоторую помощь, если вы сможете определить, на какой из них ссылаться:
::Foo::Bar
Разглагольствовать:
Именно в таких вещах, я думаю, проявляются некоторые недостатки ruby.Под капотом объектной модели Ruby и ее области видимости находятся все объектные структуры, указывающие друг на друга, что очень похоже на javascript или другие языки, основанные на прототипах.Но это непоследовательно проявляется в синтаксисе класса / модуля, который вы используете в языке.Похоже, что при некотором тщательном рефакторинге вы могли бы сделать этот материал более понятным, а также упростить язык, хотя это, конечно, было бы крайне несовместимо с существующим кодом.
Подсказка:
При использовании puts для отладки попробуйте выполнить puts foo.inspect, поскольку это отобразит его так, как вы привыкли из irb.