2

我的控制器中有一个 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 类无法访问的别名?

4

2 回答 2

3

Ruby 类只是对象,因此比较基于对象标识(即,引擎盖下的相同指针)。

不确定您的情况发生了什么,但我会尝试在几个位置进行调试,并查看您为 MysqlError 获得的对象 ID 和祖先。我怀疑在不同的模块中有两个这样的对象,而您的 catch 子句引用了错误的对象。

编辑:

这很奇怪。我现在的猜测是 MysqlError 或其祖先之一已包含在您的控制器自己的类链中的两个不同点,这以某种方式触发了异常捕获。

理论 #2 是因为 rails 重新定义了 const_missing 以执行自动要求,因此您希望在异常处理子句中获得 UndefinedConstant 异常,而不是在源代码树中找到具有该名称的东西,天知道在哪里。您应该能够通过在 auto 要求关闭的情况下进行测试来查看是否是这种情况(即在 dev 和 prod 模式下进行一些调试)。

有一种语法强制您的引用从根开始,如果您能找出正确的引用,这可能会有所帮助:

::Foo::Bar

咆哮:

这种事情是我认为 ruby​​ 的一些缺陷表现出来的。在底层,Ruby 的对象模型和作用域是所有指向彼此的对象结构,其方式与 javascript 或其他基于原型的语言非常相似。但这在您在语言中使用的类/模块语法中表现得不一致。似乎通过一些仔细的重构,您可以使这些东西更清晰并简化语言,尽管这当然与现有代码高度不兼容。

小费:

使用 puts 进行调试时,请尝试 puts foo.inspect,因为这会以您习惯于 irb 的方式显示它。

于 2009-07-16T01:08:40.620 回答
3

这是一个简单的类重定义错误。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

发生这种情况是因为在 MysqlError 已经定义之后加载了 mysql gem。这是一些测试控制台的乐趣:

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 没有消除该警告。

于 2009-07-16T03:19:31.820 回答