You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

添加Stimulus后Rails表单失效:提交报错与验证提示异常

Rails表单结合Stimulus图片预览后功能异常问题

我在Rails表单里用Stimulus实现了图片上传预览,预览功能正常,但表单本身出了问题:

  • 点击提交时出现错误:Error: Form responses must redirect to another location,没有其他反应;
  • 空字段的提示弹窗不再工作。

试过用form_tag包裹整个表单,但提交时直接跳转,就算表单不完整也不会有验证提示。唯一能恢复表单功能的方法是移除<%= javascript_importmap_tags %>,但这样Stimulus的预览功能就失效了。

相关代码

_form.html.erb

<%= form_with model: @movie do |f|%>
    <%= f.hidden_field :user_id, value: current_user.id %>
    <%= f.hidden_field :rating, value: -1%>
    <%= render "movies/error_messages" %>
    <div class="field form-group" style = "color: white">
        <%= f.text_field :title, placeholder: "Title", class:"form-control"%>
    </div>
    <br>
    <div class="field form-group" style = "color: white">
        <%= f.text_area :description, placeholder: "Description", class:"form-control" %>
    </div>
    <br>
    <div class="field form-group" style = "color: white">
        <%= f.text_field :director, placeholder: "Director", class:"form-control" %>
    </div>
    <br>
    <%= f.select :genre, options_for_select([["Movie Genre", ""], "Action", "Adventure", "Drama", "Romance", "Comedy", "Horror", "Suspense"]), 
    {}, { class: "form form-group form-control" } %>
    <br>
    <div class="field form-group" style = "color: white">
        <label class="form-label" for="typeNumber">Movie Duration</label>
        <%= f.text_field :movie_length, class:"form-control", step:"0.01", value:"01.00", type:"number", id:"typeNumber" %>
    </div>
    <br>
    <div data-controller = "previews">
        <label class="form-label" for="typeNumber" style = "color: white">Movie Cover</label>
        <%= f.file_field :image, direct_upload: true, data: { previews_target: "input", action: "change->previews#preview"}, class:"form-control" %>
        <div class= "image-cover-preview">
        <% if @movie.image.attached? && @movie.image.persisted?%>
            <%= image_tag @movie.image, data: { previews_target: "preview"}, class: "image-cover-preview" %>
        <% else %>
            <%= image_tag "", data: { previews_target: "preview"}, class: "image-cover-preview" %>
        <% end %>
        </div>
    </div>
    <br>
    <div class="actions">
        <%= f.submit class:"btn btn-primary" %>
    </div>
<% end %>

movies_controller.rb

class MoviesController < ApplicationController
  before_action :set_movie, only: [ :show, :edit, :update, :destroy ]
  before_action :authenticate_user!, except: [ :index, :show ]

  def index
    @movies = Movie.search_by_title(params[:search])

    @movies.each do |m|
      unless m.reviews.blank?
        tempRating = 0
        m.reviews.each do |r|
          tempRating += r.rating
        end
        m.rating = tempRating/m.reviews.size
      end
    end
  end
  def show
    @movie = Movie.find(params[:id])
    @reviews = Review.where(movie_id: @movie.id).order("created_at DESC")
    @users = User.all

    unless @reviews.blank?
      tempRating = 0
      @reviews.each do |r|
        tempRating += r.rating
      end
      @movie.rating = tempRating/@reviews.size
    end
  end

  def new
    @movie = Movie.new
  end

  def edit
  end

  def create
    @movie = Movie.new(movie_params)
    if movie_params[:image].blank?
      @movie.errors.add(:base, "Please upload an image")
      render :new
    else
      if @movie.save
        redirect_to @movie, notice: "Movie was successfully created"
      else
        render :new
      end
    end
  end

  def update
    if @movie.update(movie_params)
      redirect_to @movie, notice: "Movie was successfully updated"
    end
  end

  def destroy
    @movie.destroy
    redirect_to movies_path
  end

  private

  def set_movie
    @movie = Movie.find(params[:id])
  end

  def movie_params
    params.require(:movie).permit(:title, :description, :director, :genre, :movie_length, :rating, :user_id, :image, :search)
  end
end

previews_controller.js

import { Controller } from "@hotwired/stimulus"


export default class extends Controller 
{
  static targets = ["input", "preview"];

  connect() 
  {
  }

  preview()
  {
    let input = this.inputTarget;
    let preview = this.previewTarget;
    let file = input.files[0];
    let reader = new FileReader();

    reader.onloadend = function()
    {
      preview.src = reader.result;
    };

    if(file)
    {
      reader.readAsDataURL(file);
    }
    else
    {
      preview.src = "";
    }
  }
}

解决方案

问题根源

form_with 默认会以AJAX方式提交表单,而控制器在验证失败时使用 render :new 返回HTML,不符合AJAX请求的响应要求,才会抛出Error: Form responses must redirect to another location错误。同时,AJAX提交会绕过原生HTML5表单验证,导致空字段提示失效。

修复步骤

  1. 关闭表单的AJAX提交
    在form_with里添加local: true选项,强制表单以传统方式提交:

    <%= form_with model: @movie, local: true do |f|%>
    

    这样表单提交会回到正常的页面刷新模式,验证失败时render :new能正确返回表单页面,空字段的HTML5提示也会生效。

  2. 修复update方法的异常分支
    update方法只处理了更新成功的情况,失败时无响应会导致服务器返回204无内容,引发异常。补充失败分支:

    def update
      if @movie.update(movie_params)
        redirect_to @movie, notice: "Movie was successfully updated"
      else
        render :edit # 失败时返回编辑页面,显示错误信息
      end
    end
    
  3. 优化图片预览的标签ID冲突
    表单里电影时长标签的for="typeNumber"和图片封面标签重复,修改图片封面的标签关联:

    <label class="form-label" for="movie_image" style = "color: white">Movie Cover</label>
    <%= f.file_field :image, direct_upload: true, data: { previews_target: "input", action: "change->previews#preview"}, class:"form-control", id: "movie_image" %>
    

    避免标签关联错误。

做完这些修改后,表单的提交验证、错误提示都会恢复正常,同时Stimulus的图片预览功能也能继续工作。


内容的提问来源于stack exchange,提问作者JMS

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 15:15:15