Когда класс Ruby не тот класс Ruby? - PullRequest
2 голосов
/ 16 июля 2009

У меня есть этот код в моем контроллере для приложения 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

Что здесь может происходить?


Некоторая информация о предках: Когда я изменяю спасение, чтобы дать мне это:

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 ]

3 голосов
/ 16 июля 2009

Это была простая ошибка переопределения классов. Ruby позволяет вам переопределить константу верхнего уровня, но не разрушает исходную константу, когда вы это делаете. Объекты, которые все еще содержат ссылки на эту константу, все еще могут использовать ее, поэтому она все еще может использоваться для генерации исключений, как в проблеме, с которой я столкнулся.

Поскольку мое переопределение происходило в зависимостях, я решил эту проблему путем поиска исходного класса в пространстве объектов и привязки к ссылке на него для использования при перехвате исключений. Я добавил эту строку в мой контроллер:

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 не ищет Hash по имени каждый раз, когда вы объявляете Hash литерал:

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 для глобального пространства имен. Например, вы можете добавить мьютекс к классу, который не является потокобезопасным, и вам не нужно менять старый код для ссылки на вашу поточно-безопасную версию. Но я бы хотел, чтобы RSpec не отключил это предупреждение.

3 голосов
/ 16 июля 2009

Классы Ruby - это просто объекты, поэтому сравнение основано на идентичности объекта (т. Е. Тот же указатель под капотом).

Не уверен, что происходит в вашем случае, но я постараюсь отладить в нескольких местах и ​​посмотреть, какие идентификаторы объектов и предков вы получите для MysqlError. Я подозреваю, что есть два таких объекта в разных модулях, и ваше предложение catch ссылается на неправильный.

Edit:

Это довольно странно. Теперь я предполагаю, что MysqlError или один из его предков был включен в две разные точки в цепочке классов ваших контроллеров, и это как-то запускает перехват исключений.

Теория # 2 состояла бы в том, что, поскольку rails переопределяет const_missing для выполнения автоматических требований, в котором вы ожидаете получить исключение UndefinedConstant в предложениях обработки исключений, вместо этого нужно найти что-то под этим именем, которое бог знает где в дереве исходников. Вы сможете увидеть, так ли это, протестировав с автоматическим отключением (т. Е. Выполнить несколько отладок в режиме dev и prod).

Существует синтаксис, заставляющий вашу ссылку начинаться с корня, которая может быть полезна, если вы можете найти правильный, на который ссылаетесь:

::Foo::Bar

Рант:

В таких вещах, как мне кажется, есть некоторые недостатки рубинового шоу. Под капотом объектной модели и области видимости Ruby находятся все объектные структуры, указывающие друг на друга, таким образом, что это очень похоже на javascript или другие языки на основе прототипов. Но это проявляется непоследовательно в синтаксисе класса / модуля, который вы используете в языке. Кажется, что с помощью некоторого тщательного рефакторинга вы могли бы сделать это более понятным, а также упростить язык, хотя это, конечно, было бы крайне несовместимо с существующим кодом.

Совет:

Когда вы используете для отладки put, попробуйте делать так, чтобы положить foo.inspect, так как он будет отображаться так, как вы привыкли с irb.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...