什么时候 Ruby 类不是那个 Ruby 类?
我的 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 类无法访问的别名?
I have this code in my controller for a Rails app:
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
When I run my spec to make sure my foreign key constraints work, I get this:
not a MysqlError, instead it was a MysqlError
What could be going on here?
Some ancestor information: When I change the rescue to give me this:
puts MysqlError.ancestors
puts "****"
puts Mysql::Error.ancestors
puts "****"
puts e.class.ancestors
This is what I get:
Mysql::Error
StandardError
Exception
ActiveSupport::Dependencies::Blamable ...
****
Mysql::Error
StandardError
Exception
ActiveSupport::Dependencies::Blamable ...
****
MysqlError
StandardError
Exception
ActiveSupport::Dependencies::Blamable ...
Could there be an alias in the global namespace that makes the MysqlError class unreachable?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
Ruby 类只是对象,因此比较基于对象标识(即底层的相同指针)。
不确定您的情况发生了什么,但我会尝试在几个位置进行调试,并查看您为 MysqlError 获得的对象 ID 和祖先。 我怀疑不同的模块中有两个这样的对象,并且您的 catch 子句引用了错误的对象。
编辑:
这很奇怪。 我现在的猜测是,MysqlError 或其祖先之一已包含在控制器自己的类链上的两个不同点上,这在某种程度上导致了异常捕获。
理论 #2 是,由于 Rails 重新定义了 const_missing 来执行 auto-requires,因此您希望在异常处理子句中获得 UndefinedConstant 异常,而不是在源代码树中找到具有该名称的内容,天知道在哪里。 您应该能够通过关闭自动要求进行测试来查看是否是这种情况(即在开发和生产模式下进行一些调试)。
有一种语法可以强制您的引用从根开始,如果您能找出要引用的正确语法,这可能会有所帮助:
Rant:
我认为这种情况是 ruby 表现出的一些缺陷。 在底层,Ruby 的对象模型和作用域是所有相互指向的对象结构,其方式与 javascript 或其他基于原型的语言非常相似。 但这在您在该语言中使用的类/模块语法中表现得不一致。 似乎通过一些仔细的重构,您可以使这些东西变得更清晰并简化语言,尽管这当然与现有代码高度不兼容。
提示:
当使用 put 进行调试时,请尝试执行 put foo.inspect,因为这将以您习惯的 irb 方式显示它。
Ruby classes are just objects, so comparison is based on object identity (ie, the same pointer under the hood).
Not sure what's happening in your case, but I'd try debugging in a few locations and seeing what object ids and ancestors you get for MysqlError. I suspect that there's two such objects in different modules and your catch clause is referencing the wrong one.
Edit:
That is quite strange. My guess now is that MysqlError or one of it's ancestors has been included at two different points along your controllers own class chain, and that's somehow tripping up the exception catching.
Theory #2 would be that since rails redefines const_missing to do auto-requires, where you'd expect to get an UndefinedConstant exception in the exception handling clauses is instead finding something by that name god knows where in the source tree. You should be able to see if that's the case by testing with the auto requiring off (ie do some debugs in both dev and prod mode).
There is a syntax for forcing your reference to start from the root which may be of some help if you can figure out the right one to be referencing:
Rant:
This sort of thing is where I think some of the flaws of ruby show. Under the hood Ruby's object model and scoping is all object structures pointing to each other, in a way that's quite similar to javascript or other prototype based languages. But this is surfaced inconsistently in the class/module syntax you use in the language. It seems like with some careful refactoring you could make this stuff clearer as well as simplify the language, though this would of course be highly incompatible with existing code.
Tip:
When using puts for debugging, try doing puts foo.inspect as this will display it in the way you're used to from irb.
这是一个简单的类重新定义错误。 Ruby 允许您重新定义顶级常量,但这样做时不会破坏原始常量。 仍然保留对该常量的引用的对象仍然可以使用它,因此它仍然可以用于生成异常,就像我遇到的问题一样。
由于我的重新定义是在依赖项中发生的,因此我通过在对象空间中搜索原始类并保留对它的引用以在捕获异常时使用来解决这个问题。 我将这一行添加到我的控制器中:
这获取了对 MysqlError 原始版本的引用。 然后我能够做到这一点:
发生这种情况是因为 mysql gem 是在 MysqlError 已经定义之后加载的。 这里有一些测试控制台的乐趣:
您可以在 IRB 中轻松地完成此操作,而无需要求; 这里有一个有效的技巧,因为 irb 不会在每次声明哈希文字时按名称查找哈希:
我明白为什么你可能想要这样做,它可以像全局名称空间的 alias_method_chain 一样使用。 例如,您可以将互斥锁添加到非线程安全的类中,并且不需要更改旧代码来引用您的线程安全版本。 但我确实希望 RSpec 没有消除这个警告。
This was a simple class redefinition bug. Ruby lets you redefine a top-level constant, but it doesn't destroy the original constant when you do it. Objects that still hold references to that constant can still use it, so it could still be used to generate exceptions, like in the issue I was having.
Since my redefinition was happening in dependencies, I solved this by searching for the original class in the Object space, and hanging on to a reference to it to use when catching exceptions. I added this line to my controller:
That gets a reference to the original version of MysqlError. Then I was able to do this:
This happens because the mysql gem is getting loaded after MysqlError has already been defined. Here is some test console joy:
You can do this in IRB without a require pretty easily; here's a trick that works because irb doesn't look up Hash by name every time you declare a Hash literal:
I can see why you might want to do this, it could be used like alias_method_chain for the global namespace. You could add a mutex to a class that isn't threadsafe, for example, and not need to change old code to reference your threadsafe version. But I do wish RSpec hadn't silenced that warning.