Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Railsアプリの例外をスタックトレースで追跡してバグを特定する方法

Railsアプリケーションでエラーが発生したとき、例外メッセージとスタックトレースは「どこで」問題が起きたのかを教えてくれる重要な手がかりです。しかし、発生場所がわかった後には、その「なぜ」を突き止める必要があります。この記事では、実際の例外を例に、スタックトレースを使ってRailsアプリケーションのバグを特定する手順を解説します。

例外メッセージを読み解く

まず、次のような例外が発生したケースを見てみましょう。

NoMethodError (undefined method `request_uri' for #<URI::Generic >):

app/models/product.rb:8:in `download_image!'
app/controllers/products_controller.rb:5:in `create'

この例では、undefined method `request_uri' for #<URI::Generic >というメッセージとともにNoMethodErrorが発生しています。このメッセージだけでは問題の原因がすぐには分からないため、スタックトレースを詳しく調べていく必要があります。

スタックトレースをたどる

app/models/product.rb:8:in `download_image!'
app/controllers/products_controller.rb:5:in `create'

スタックトレースを見ると、例外はProductモデルのdownload_image!メソッドから発生したことが分かります。ここからコードを調査し、スタックトレースを下へ辿りながら、何が問題なのかを突き止めていきましょう。

モデルファイルを開くと、例外が発生した8行目でNet::HTTP.get(uri)が呼び出されています。つまり、変数uriが期待しているオブジェクトではない可能性が高いです。

require 'net/http'

class Product < ApplicationRecord
  after_save :download_image!

  def download_image!
    uri = URI(image_url)
    contents = Net::HTTP.get(uri)

    File.open("public#{local_image_path}", 'wb') do |file|
      file.write contents
    end
  end

  def local_image_path
    "/product_#{id}.png"
  end
end

download_image!メソッドはafter_saveコールバックとして登録されているため、新しいProductレコードが保存された直後に実行されることが分かります。

7行目を見ると、変数uriimage_urlというメソッドから生成されています。この値がどこから来ているのかを確認するために、再びスタックトレースに目を向けると、ProductsController#createからProduct#create(保存処理)が呼び出されていることが読み取れます。

class ProductsController < ApplicationController
  def create
    @product = Product.new(product_params)

    if @product.save
      redirect_to @product, notice: 'Product was successfully created.'
    else
      render :new
    end
  end

  private
    def product_params
      params.require(:product).permit(:title, :description, :image_url, :price)
    end
end

なるほど!ProductsController#createは、product_paramsを使って新しい商品を作成しています。そして、そのパラメータの中には探していた:image_urlが含まれています。

原因の特定と再現

これで、壊れたURIの生成元がimage_url属性であることが確定しました。商品作成時にimage_urlフィールドを空のまま送信してみると、問題を確実に再現できます。

この場合、空文字列を渡してURIオブジェクトを生成すると、URLの形式を判定できないため、URI::HTTPではなくURI::Genericオブジェクトが返されます。URI::Genericには#request_uriメソッドが存在しないため、Net::HTTP.getの呼び出し時にNoMethodErrorが発生する、というのが一連の流れです。

修正の方針

プロジェクトの要件によりますが、フィールドが空でないことを保証するバリデーション(presence: trueなど)を追加すれば、まずこの問題は解決できます。ただし、「画像URLが空でないこと」だけの検証では、この実装のすべての問題を解消できるわけではありません(URL以外の文字列が渡された場合にも例外が発生します)。それでも、エラーの根本原因に対する最初の一歩としては有効な対策です。

まとめ:スタックトレースで例外を追跡する

Railsのログは、問題のデバッグに役立つ強力なツールです。発生した例外は一見すると意味が分かりにくいこともありますが、コードがその問題地点にたどり着くまでの経路を丁寧に遡って確認することで、原因がアプリのやや深い場所に埋もれていても、何が誤っていたのかを効率的に発見できます。

この記事が役に立ったかどうか、疑問点や今後読みたいテーマなどがあれば、ぜひ@AppSignalまでお知らせください。

  1. AWS LambdaでRailsアプリを動かす方法!Lambyを使ったデプロイ手順を徹底解説

    サーバーレスコンピューティングは、サーバーの管理やプロビジョニングの作業をクラウドプロバイダーにオフロードできる仕組みとして、多くのテックチームで急速に標準になりつつあります。その代表格がAWS Lambdaです。LambdaはNode.js、Java、Python、Rubyといった主要なプログラミング言語を幅広くサポートしています。しかし、言語そのものだけでなく、それらの言語で構築されたフレームワークの機能に依存するサーバーレス関数を実行したいケースもあるでしょう。 本記事では、RailsアプリケーションをAWS Lambda上で実行する方法を解説します。サーバーレスコンピューティングとAW

  2. Rails5でのAngularの使用

    あなたは前にその話を聞いたことがあります。分散型で完全に機能するバックエンドAPIと、通常のツールセットで作成されたフロントエンドで実行されているアプリケーションがすでにあります。 次に、Angularに移動します。または、AngularをRailsプロジェクトと統合する方法を探しているだけかもしれません。これは、この方法を好むためです。私たちはあなたを責めません。 このようなアプローチを使用すると、両方の世界を活用して、たとえばRailsとAngularのどちらの機能を使用してフォーマットするかを決定できます。 構築するもの 心配する必要はありません。このチュートリアルは、この目的のた