Rails 7注册表单提交报错:ActiveRecord::NotNullViolation
解决Rails 7中ActiveRecord::NotNullViolation(PG::NotNullViolation)错误
问题场景
在Ubuntu 22系统的Rails 7应用中测试注册表单,提交POST请求时触发ActiveRecord::NotNullViolation错误,提示inscriptions表的club_id列违反非空约束,提交的club_id值为null。
错误日志
Started POST "/inscriptions" for 127.0.0.1 at 2023-06-18 15:44:51 +0200 Processing by InscriptionsController#create as TURBO_STREAM Parameters: {"authenticity_token"=>"[FILTERED]", "inscription"=>{"vorname"=>"Jade", "name"=>"Doe", "age"=>"13", "adress"=>"3 Marguerite Youcenar", "email"=>"jade_doe@gmail.com", "phone"=>"0768324654", "gender"=>"feminin"}, "commit"=>"Envoyer"} TRANSACTION (0.5ms) BEGIN ↳ app/controllers/inscriptions_controller.rb:27:in `block in create' Inscription Create (11.6ms) INSERT INTO "inscriptions" ("vorname", "name", "age", "adress", "email", "phone", "gender", "created_at", "updated_at", "club_id") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING "id" [["vorname", "Jade"], ["name", "Doe"], ["age", "13"], ["adress", "3 Marguerite Youcenar"], ["email", "jade_doe@gmail.com"], ["phone", "0768324654"], ["gender", "feminin"], ["created_at", "2023-06-18 13:44:51.944100"], ["updated_at", "2023-06-18 13:44:51.944100"], ["club_id", nil]] ↳ app/controllers/inscriptions_controller.rb:27:in `block in create' TRANSACTION (0.9ms) ROLLBACK ↳ app/controllers/inscriptions_controller.rb:27:in `block in create' Completed 500 Internal Server Error in 30ms (ActiveRecord: 13.0ms | Allocations: 5257) ActiveRecord::NotNullViolation (PG::NotNullViolation: ERREUR: une valeur NULL viole la contrainte NOT NULL de la colonne « club_id » dans la relation « inscriptions » DETAIL: La ligne en échec contient (15, Jade, Doe, 13, 3 Marguerite Youcenar, jade_doe@gmail.com, 0768324654, feminin, 2023-06-18 13:44:51.9441, 2023-06-18 13:44:51.9441, null). ):
相关代码
InscriptionsController
class InscriptionsController < ApplicationController before_action :set_inscription, only: %i[ show edit update destroy ] # GET /inscriptions or /inscriptions.json def index @inscriptions = Inscription.all end # GET /inscriptions/1 or /inscriptions/1.json def show end # GET /inscriptions/new def new @inscription = Inscription.new end # GET /inscriptions/1/edit def edit end # POST /inscriptions or /inscriptions.json def create @inscription = Inscription.new(inscription_params) respond_to do |format| if @inscription.save format.html { redirect_to inscription_url(@inscription), notice: "Inscription was successfully created." } format.json { render :show, status: :created, location: @inscription } else format.html { render :new, status: :unprocessable_entity } format.json { render json: @inscription.errors, status: :unprocessable_entity } end end end # PATCH/PUT /inscriptions/1 or /inscriptions/1.json def update respond_to do |format| if @inscription.update(inscription_params) format.html { redirect_to inscription_url(@inscription), notice: "Inscription was successfully updated." } format.json { render :show, status: :ok, location: @inscription } else format.html { render :edit, status: :unprocessable_entity } format.json { render json: @inscription.errors, status: :unprocessable_entity } end end end # DELETE /inscriptions/1 or /inscriptions/1.json def destroy @inscription.destroy respond_to do |format| format.html { redirect_to inscriptions_url, notice: "Inscription was successfully destroyed." } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_inscription @inscription = Inscription.find(params[:id]) end # Only allow a list of trusted parameters through. def inscription_params params.require(:inscription).permit(:club_name, :age, :vorname, :name, :adress, :email, :phone, :gender) end end
Inscription模型
class Inscription < ApplicationRecord belongs_to :club, :optional => true #validates :club_name, presence: true validates :name, presence: true validates :age, presence: true, numericality: { only_integer: true, greater_than: 0 } validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP } validates :phone, presence: true , format: { with: /\A\d{10}\z/ } end
Club模型
class Club < ApplicationRecord belongs_to :ClubAdulte belongs_to :ClubTeevo belongs_to :ClubJeuneLecteur end
错误原因分析
- 数据库约束优先级高于模型设置:虽然
Inscription模型中设置了belongs_to :club, optional: true,但数据库层面inscriptions表的club_id列被设置为非空(NOT NULL),导致提交null值时触发数据库约束错误。 - 表单未提交club_id参数:从请求参数可以看到,提交的
inscription数据中没有club_id字段,且控制器的inscription_params也未允许该参数通过。 - Club模型关联命名错误:
Club模型中的belongs_to关联使用了大写开头的模型名(如:ClubAdulte),违反Rails约定(应为单数小写,如:club_adulte),会导致关联查找失败。
解决方案
根据业务需求选择以下方案:
方案1:要求每个Inscription必须关联Club
如果业务规则要求注册必须选择俱乐部,按以下步骤修改:
- 在表单中添加club_id字段:比如添加下拉选择框或隐藏字段,确保提交时包含
club_id参数。 - 更新控制器的强参数:在
inscription_params中添加:club_id到允许列表:def inscription_params params.require(:inscription).permit(:club_id, :club_name, :age, :vorname, :name, :adress, :email, :phone, :gender) end - 修正模型关联验证:移除
optional: true,添加俱乐部存在验证:class Inscription < ApplicationRecord belongs_to :club validates :club, presence: true # 其他验证规则... end
方案2:允许Inscription不关联Club
如果注册可以不选择俱乐部,需要修改数据库约束:
- 生成迁移文件:
rails generate migration ChangeClubIdToNullableInInscriptions - 编辑迁移文件,修改
club_id允许为空:class ChangeClubIdToNullableInInscriptions < ActiveRecord::Migration[7.0] def change change_column_null :inscriptions, :club_id, true end end - 运行迁移:
rails db:migrate - 保留模型的optional设置:确保
Inscription模型中的belongs_to :club, optional: true保留,维持模型层面的允许空规则。
额外修正:Club模型关联命名
修正Club模型的关联命名为Rails约定的单数小写形式:
class Club < ApplicationRecord belongs_to :club_adulte belongs_to :club_teevo belongs_to :club_jeune_lecteur end
内容的提问来源于stack exchange,提问作者Piercel Moussa
相关产品推荐
相关产品推荐

