如何解决Rails 6中创建分类时控制器的SystemStackError问题
Rails 6创建分类时触发SystemStackError的诊断与解决
问题场景
在Rails 6的API控制器中创建分类时触发SystemStackError (stack level too deep),服务器日志显示分类已成功创建,但在执行render json: @category步骤时出现栈溢出,同时日志中也出现了index方法的错误回溯。
服务器日志片段
Started POST "/api/v1/categories" for ::1 at 2023-09-07 00:30:36 +0530 Processing by Api::V1::CategoriesController#create as JSON Parameters: {"name"=>"Electronics", "category"=>{"name"=>"Electronics"}} Can't verify CSRF token authenticity. User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."token" IS NULL LIMIT $1 [["LIMIT", 1]] ↳ app/controllers/application_controller.rb:13:in `authenticate_request' TRANSACTION (0.9ms) BEGIN ↳ app/controllers/application_controller.rb:15:in `authenticate_request' User Update (1.1ms) UPDATE "users" SET "sign_in_count" = $1, "current_sign_in_at" = $2, "updated_at" = $3 WHERE "users"."id" = $4 [["sign_in_count", 2], ["current_sign_in_at", "2023-09-06 19:00:36.703908"], ["updated_at", "2023-09-06 19:00:36.704756"], ["id", 1]] ↳ app/controllers/application_controller.rb:15:in `authenticate_request' TRANSACTION (1.4ms) COMMIT ↳ app/controllers/application_controller.rb:15:in `authenticate_request' TRANSACTION (0.8ms) BEGIN ↳ app/controllers/api/v1/categories_controller.rb:17:in `create' Category Create (1.1ms) INSERT INTO "categories" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["name", "Electronics"], ["created_at", "2023-09-06 19:00:36.729039"], ["updated_at", "2023-09-06 19:00:36.729039"]] ↳ app/controllers/api/v1/categories_controller.rb:17:in `create' TRANSACTION (0.9ms) COMMIT ↳ app/controllers/api/v1/categories_controller.rb:17:in `create' ActiveStorage::Attachment Load (1.8ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = $1 AND "active_storage_attachments"."record_type" = $2 AND "active_storage_attachments"."name" = $3 LIMIT $4 [["record_id", 2], ["record_type", "Category"], ["name", "image"], ["LIMIT", 1]] ↳ app/controllers/api/v1/categories_controller.rb:18:in `create' Completed 500 Internal Server Error in 85ms (ActiveRecord: 21.6ms | Allocations: 40064) SystemStackError (stack level too deep): app/controllers/api/v1/categories_controller.rb:18:in `create' **SystemStackError (stack level too deep): app/controllers/api/v1/categories_controller.rb:7:in `index'
相关控制器代码
Api::V1::CategoriesController
class Api::V1::CategoriesController < ApplicationController before_action :set_category, only: [:show, :update, :destroy] def index @categories = Category.all # render json: @categories end def show render json: @category end def create @category = Category.new(category_params) if @category.save render json: @category, status: :created else render json: @category.errors, status: :unprocessable_entity end end def update if @category.update(category_params) render json: @category else render json: @category.errors, status: :unprocessable_entity end end def destroy @category.destroy # head :no_content end private def set_category @category = Category.find(params[:id]) end def category_params params.require(:category).permit(:name, :image) end end
ApplicationController
class ApplicationController < ActionController::Base include JsonWebToken protect_from_forgery with: :null_session before_action :authenticate_request before_action :configure_permitted_parameters, if: :devise_controller? attr_reader :current_user private def authenticate_request token = request.headers['Authorization']&.split(' ')&.last user = User.find_by(token: token) if user.present? sign_in user, store: false else render json: { error: 'Invalid token' }, status: :unauthorized end end protected def configure_permitted_parameters added_attrs = [:mobile_number, :email, :first_name, :last_name] devise_parameter_sanitizer.permit :sign_up, keys: added_attrs devise_parameter_sanitizer.permit :account_update, keys: added_attrs end end
原因诊断
- JSON序列化循环引用:这是最常见的栈溢出原因。如果
Category模型定义了自关联(如父分类/子分类的belongs_to :parent和has_many :children),直接调用render json: @category会触发递归序列化——父分类包含子分类,子分类又包含父分类,无限循环直到栈溢出。 - ActiveStorage附件序列化问题:模型中
has_one_attached :image的序列化逻辑可能存在隐性循环,比如自定义的附件处理方法触发了重复的模型序列化。 - index方法未显式渲染:
index方法中注释了render json: @categories,Rails会尝试寻找对应的视图模板,若模板不存在或模板中存在递归逻辑,可能引发异常(日志中的index错误大概率是栈溢出后的回溯干扰)。
解决步骤
1. 排查并修复序列化循环引用
- 检查
Category模型的关联定义,若存在自关联,修改render语句排除循环字段:# 在create方法中修改 render json: @category, except: [:parent_id, :created_at, :updated_at], status: :created # 若需要包含子分类,需限制子分类的序列化字段避免循环 render json: @category, include: { children: { except: [:parent_id] } }, status: :created - 推荐使用Active Model Serializers自定义序列化规则,彻底避免循环:
生成序列化器:
编辑序列化器,只定义需要返回的字段:rails generate serializer Category
在控制器中使用序列化器:class CategorySerializer < ActiveModel::Serializer attributes :id, :name # 若需要包含子分类,添加以下配置(排除parent关联) # has_many :children, serializer: CategorySerializer endrender json: @category, serializer: CategorySerializer, status: :created
2. 修复index方法的渲染逻辑
取消index方法中render json: @categories的注释,避免Rails自动查找视图模板引发异常:
def index @categories = Category.all render json: @categories end
3. 调试ActiveStorage附件问题
临时替换create方法的render语句,只返回基础字段,验证是否是附件序列化导致的问题:
render json: { id: @category.id, name: @category.name }, status: :created
若此操作不再报错,说明image附件的序列化存在问题,可检查模型中是否有自定义的附件回调或方法,或在序列化器中排除附件字段,按需单独返回附件URL。
4. 排查Devise登录逻辑
虽然日志中authenticate_request执行正常,但sign_in user, store: false可能触发了用户模型的回调,若用户模型与分类模型存在关联且回调中有递归逻辑,也可能引发栈溢出。可临时注释sign_in语句验证是否解决问题,再逐步排查回调逻辑。
内容的提问来源于stack exchange,提问作者Prakash Chand
相关产品推荐
相关产品推荐

