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