Когда класс Ruby не является тем классом Ruby?

StackOverflow https://stackoverflow.com/questions/1134752

  •  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.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top