2009-07-16 9 views
2

J'ai ce code dans mon contrôleur pour une application Rails:Quand une classe Ruby n'est-elle pas une classe Ruby?

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 

Quand je lance mon spec pour vous assurer que mes clés étrangères fonctionnent, je reçois ceci:

not a MysqlError, instead it was a MysqlError 

Comment pourrait-on va ici?


Quelques informations ancêtre: Quand je change la rescousse pour me donner ceci:

puts MysqlError.ancestors 
puts "****" 
puts Mysql::Error.ancestors 
puts "****" 
puts e.class.ancestors 

Voici ce que je reçois:

Mysql::Error 
StandardError 
Exception 
ActiveSupport::Dependencies::Blamable ... 
**** 
Mysql::Error 
StandardError 
Exception 
ActiveSupport::Dependencies::Blamable ... 
**** 
MysqlError 
StandardError 
Exception 
ActiveSupport::Dependencies::Blamable ... 

Y aurait-il un alias dans l'espace de noms global cela rend la classe MysqlError inaccessible?

Répondre

3

Il s'agissait d'un simple bogue de redéfinition de classe. Ruby vous permet de redéfinir une constante de haut niveau, mais elle ne détruit pas la constante d'origine lorsque vous le faites.Les objets qui contiennent encore des références à cette constante peuvent toujours l'utiliser, de sorte qu'il peut toujours être utilisé pour générer des exceptions, comme dans le problème que je rencontrais. Depuis que ma redéfinition se produisait dans les dépendances, j'ai résolu ce problème en recherchant la classe originale dans l'espace objet, et en conservant une référence pour l'utiliser lors de la capture d'exceptions. J'ai ajouté cette ligne à mon contrôleur:

ObjectSpace.each_object(Class){|k| @@mysql_error = k if k.name == 'MysqlError'} 

Cela donne une référence à la version originale de MysqlError. Ensuite, j'ai pu le faire:

rescue @@mysql_error => e 
    render :xml => e.message, :status => :unprocessable_entity and return 

Cela se produit parce que le bijou mysql est chargé après se MysqlError a déjà été défini. Voici une console de test de joie:

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 

Vous pouvez le faire en IRB sans exiger assez facilement; voici une astuce qui fonctionne parce que RIR ne regarde pas Hash par nom chaque fois que vous déclarez une Hash littérale:

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 

je peux voir pourquoi vous pouvez le faire, il pourrait être utilisé comme alias_method_chain pour le mondial espace de nommage. Vous pourriez ajouter un mutex à une classe qui n'est pas threadsafe, par exemple, et pas besoin de changer l'ancien code pour référencer votre version threadsafe. Mais je souhaite que RSpec n'ait pas fait taire cet avertissement.

+0

Wow, c'est méchant. Super endroit. – tomafro

+0

+1 Des trucs fascinants. Je ne pense pas que vous pouvez reproduire cela dans Ruby. J'essayais juste de définir la classe Blah dans irb, et, étrangement, même si vous EXIT AND ENTER irb l'object_id pour une classe Blah nouvellement définie reste le même ... Cependant, si vous définissez Blah différemment au démarrage (par ex. , vous ajoutez une méthode, mais sur la première définition) alors l'object_id change. Je n'ai aucune idée de ce que cela signifie, mais je pense que cela pourrait aider ...? –

+0

Est-ce que MysqlError est modifié par singe avant le chargement de la gemme? – Scott

3

Les classes Ruby sont simplement des objets, donc la comparaison est basée sur l'identité de l'objet (c'est-à-dire le même pointeur sous le capot).

Vous ne savez pas ce qui se passe dans votre cas, mais j'essaierais de déboguer dans quelques endroits et de voir quels objets et quels ancêtres vous obtenez pour MysqlError. Je soupçonne qu'il y a deux objets de ce type dans différents modules et votre clause de catch fait référence à la mauvaise.

Éditer:

C'est assez étrange. Ma conjecture maintenant est que MysqlError ou l'un de ses ancêtres a été inclus à deux points différents le long de la chaîne de classe de votre contrôleur, et que cela déclenche l'attrape de l'exception. Théorie n ° 2 serait que puisque les rails redéfinit const_missing à faire automatiquement, où vous vous attendez à obtenir une exception UndefinedConstant dans les clauses de gestion des exceptions est plutôt de trouver quelque chose par ce nom dieu sait où dans l'arbre source. Vous devriez être en mesure de voir si c'est le cas en testant avec l'auto demandant de désactiver (c.-à-d. Faire certains débogages en mode dev et prod).

Il y a une syntaxe pour forcer votre référence à partir de la racine qui peut être d'une certaine aide si vous pouvez trouver le bon à référencer:

::Foo::Bar 

Rant:

Ce genre de la chose est où je pense que certains des défauts de ruby ​​montrent. Sous le capot, le modèle d'objet de Ruby et sa portée sont toutes les structures d'objets pointant les unes vers les autres, d'une manière assez similaire au javascript ou à d'autres langages basés sur des prototypes. Mais ceci est inconsistant dans la syntaxe de classe/module que vous utilisez dans la langue. Il semble qu'avec un refactoring soigné, vous puissiez rendre ces choses plus claires et simplifier le langage, bien que cela soit bien sûr très incompatible avec le code existant.

Astuce:

Lorsque vous utilisez met pour le débogage, essayez de faire met foo.inspect comme cela affichera dans la façon dont vous avez l'habitude de partir RIR.

+0

Oui, je pense aussi, et l'idée des ancêtres était bonne, j'ai mis plus d'infos dans la question. Mais comment puis-je référencer celui que je veux? –

Questions connexes