SystemStackErrorの忍び (EOTD No.16)

公開:2021/01/06
更新:2021/01/07
4 min読了の目安(約3600字TECH技術記事

こちらAmetaです!16回目のEOTDです。

今回は初見のエラーメッセージでした。

本日のエラー

シチュエーション

ユーザーがお気に入り登録したシェフをユーザーのマイページで一覧表示できるようにするように実装していました。

コントローラー

class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    @chefs = @user.chefs
  end
end

ビューファイル

# users/show.html.erb

<%= render "shared/header" %>
<% if user_signed_in? && current_user.id == @user.id %>
  <div class="chef__inner">
    <div class="chef__wrapper">
      <h1>
        <%= "ようこそ、#{@user.nickname}さん"%>
      </h1>
      <h2 class="page-heading">
        <%= "お気に入りのシェフ"%>
      </h2>
      <div class="dish-lists">
      <%= render partial: 'chef', collection: @chefs %>
      </div>
    </div>
  </div>
<% end %>
# users/_chef.html.erb

<div class='list'>
  <%= link_to chef_path(chef.id) do %>
  <div class='dish-img-content'>
    <% if chef.profile.image.attached? %>
    <%= image_tag chef.profile.image, class: "dish-img" %>
    <% end %>
  </div>
  <div class='dish-info'>
    <div>
      <h3 class='dish-name'>
        <%= chef.name %>シェフ
      </h3>
      <div class='dish-price'>
        <span><%= chef.profile.age.name %><%= chef.profile.gender.name %>/<%= chef.profile.genre.name %>が得意</span>
      </div>
    </div>
    <div id="chef_<%= chef.id %>">
    <%= render 'chef', chef: chef %>
    </div>
  </div>
  <% end %>
</div>

上記の設定でユーザーのマイページを開くと...延々と続くLoading。

その後、今回のエラーがターミナル上で表示されいる事に気がつきました。

考察

エラーメッセージを再確認。

SystemStackError (stack level too deep):

stackは"積み重ね"、"level"は"階層"。
つまりstack levelは階層の積み重ねの事を表していそう。

cssにおける"stack level"は要素の重なり順を意味していて、z-indexプロパティで重なり順を調整できます。

そのように、ある要素が重なりすぎて階層が深くなりすぎているというエラーかな?

SystemStackError (stack level too deep):

app/views/users/_chef.html.erb:5
app/views/users/_chef.html.erb:2
app/views/users/_chef.html.erb:18
app/views/users/_chef.html.erb:2
app/views/users/_chef.html.erb:18
app/views/users/_chef.html.erb:2
app/views/users/_chef.html.erb:18
app/views/users/_chef.html.erb:2
app/views/users/_chef.html.erb:18
...

その後部分テンプレート_chef.html.erbの2行目と18行目が繰り返されている。
この箇所を確認してみると解決の糸口を掴めました。

解決

2行目と18行目の処理を確認。

# users/_chef.html.erb

<div class='list'>
  # ↓2行目
  <%= link_to chef_path(chef.id) do %>
  <div class='dish-img-content'> 
    <% if chef.profile.image.attached? %>
    <%= image_tag chef.profile.image, class: "dish-img" %>
    <% end %>
  </div>
  <div class='dish-info'>
    <div>
      <h3 class='dish-name'>
        <%= chef.name %>シェフ
      </h3>
      <div class='dish-price'>
        <span><%= chef.profile.age.name %><%= chef.profile.gender.name %>/<%= chef.profile.genre.name %>が得意</span>
      </div>
    </div>
    <div id="chef_<%= chef.id %>">
    # ↓18行目
    <%= render 'chef', chef: chef %>
    </div>
  </div>
  <% end %>
</div>

ブロック処理の頭と部分テンプレートをレンダリングしている箇所が繰り返されている様子。
この部分テンプレートどこかで見覚えがある。。

# users/_chef.html.erb
<%= render 'chef', chef: chef %>
# users/show.html.erb
<%= render partial: 'chef', collection: @chefs %>

ユーザーのshowアクションで呼び出すために新たに作った部分テンプレートの名前が、_chef.html.erbの内にある部分テンプレートの名前と同じになっている。

これが原因で_chef.html.erb_chef.html.erb自身を呼び出してしまい、無限ループが生まれてしまったようです。

この後、_chef.html.erb内の部分テンプレート名を変更するとエラーは解消しました。

SOTD(Summary Of The Day)

アプリ内でビューファイルを部分的に使いまわしていると、いわゆる再起的ループ(recursive loop)が発生してしまいがち。

無限ループにおいてはローディングがいつまで経っても終わらない一方でブラウザ上ではエラーメッセージも表示されないので、まずターミナルを確認してみる事が解決の近道だと感じました。

今日の学びです。。