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クラスは単なるオブジェクトであるため、比較はオブジェクトID(つまり、内部の同じポインター)に基づいています。

あなたのケースで何が起こっているのかわかりませんが、いくつかの場所でデバッグして、MysqlErrorで取得するオブジェクトIDと祖先を確認してみます。異なるモジュールにそのようなオブジェクトが2つあり、catch句が間違ったオブジェクトを参照していると思われます。

編集:

それはかなり奇妙です。私の推測では、MysqlErrorまたはその祖先の1つが、コントローラー自体のクラスチェーンに沿った2つの異なるポイントに含まれているため、例外キャッチが発生します。

理論#2は、railsがconst_missingを再定義して自動要求を実行するため、例外処理句でUndefinedConstant例外が発生することが予想される場合、代わりに、神がソースツリーのどこにあるかを知っている名前で何かを見つけることです。autoをオフにしてテストすることで、それが当てはまるかどうかを確認できるはずです(つまり、devモードとprodモードの両方でいくつかのデバッグを実行します)。

参照をルートから開始するように強制するための構文があります。これは、参照する適切なものを見つけられる場合に役立つ可能性があります。

::Foo::Bar

暴言:

このようなことは、ルビーの欠点のいくつかが示されていると私が思うところです。内部的には、Rubyのオブジェクトモデルとスコープは、JavaScriptや他のプロトタイプベースの言語と非常によく似た方法で、相互にポイントするすべてのオブジェクト構造です。しかし、これは、言語で使用するクラス/モジュール構文で一貫性のない形で表されます。いくつかの注意深いリファクタリングを使用すると、このことをより明確にし、言語を単純化できるようですが、これはもちろん既存のコードとは非常に互換性がありません。

ヒント:

デバッグにプットを使用する場合は、プット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がすでに定義された後にmysqlgemがロードされるために発生します。テストコンソールの喜びは次のとおりです。

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が名前でハッシュを検索するわけではないため、これが機能するトリックです。

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 に答える