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

Rails 7中ActionText/Trix图片上传失败问题求助

Rails 7中ActionText/Trix上传图片失败:Service name can't be blank

问题描述

在Rails 7项目中使用ActionText/Trix为博客上传图片时,无论配置本地磁盘存储还是AWS S3,都会出现相同问题:纯文本编辑功能正常,但点击回形针图标上传图片时,选择文件后前端控制台立即报错422,提交表单后图片未保存,仅收到博客更新成功的提示。

前端控制台错误

POST http://localhost:3000/rails/active_storage/direct_uploads 422 (Unprocessable Entity) actiontext.js:543
Uncaught Error: Direct upload failed: Error creating Blob for "Screenshot from 2022-10-13 12-38-57.png". Status: 422
at AttachmentUpload.directUploadDidComplete (actiontext.js:849:13)
at BlobRecord2.callback (actiontext.js:618:13)
at BlobRecord2.requestDidError (actiontext.js:560:12)
at BlobRecord2.requestDidLoad (actiontext.js:556:14)
at XMLHttpRequest. (actiontext.js:527:56)

Rails服务器端错误

Started POST "/rails/active_storage/direct_uploads" for ::1 at 2022-11-03 14:20:10 -0500
Processing by ActiveStorage::DirectUploadsController#create as JSON
Parameters: {"blob"=>{"filename"=>"Screenshot from 2022-10-26 15-49-39.png", "content_type"=>"image/png", "byte_size"=>336871, "checksum"=>"TZneH7Z7DdCSftEvona6zg=="}, "direct_upload"=>{"blob"=>{"filename"=>"Screenshot from 2022-10-26 15-49-39.png", "content_type"=>"image/png", "byte_size"=>336871, "checksum"=>"TZneH7Z7DdCSftEvona6zg=="}}}
Completed 422 Unprocessable Entity in 33ms (ActiveRecord: 8.4ms | Allocations: 14851)
ActiveRecord::RecordInvalid (Validation failed: Service name can't be blank):

已尝试的排查步骤

  • 禁用博客页面的Turbo功能,无效
  • 多次查阅Active Storage和ActionText官方文档,未找到对应解决方案
  • 确认storage.yml中已配置本地存储服务

相关配置与代码

storage.yml

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

表单代码(app/views/blogs/_form.html.erb)

<div class="form-group">
  <%= form.rich_text_area :body, class: 'form-control', rows: 15, placeholder: 'Content' %>
</div>

控制器代码(app/controllers/blogs_controller.rb)

class BlogsController < CommentsController
  before_action :set_blog, only: %i[ show edit update destroy toggle_status ]
  before_action :set_sidebar_topics, except: [:update, :create, :destroy, :toggle_status]
  layout "blog"
  access all: [:show, :index], user: { except: [:destroy, :new, :create, :update, :edit, :toggle_status] }, admin: :all, testing: { except: [:destroy, :create, :update]}

  def index
    if logged_in?(:admin) || logged_in?(:testing)
      @blogs = Blog.recent.with_rich_text_body_and_embeds.page(params[:page]).per(5)
    else
      @blogs = Blog.published.recent.with_rich_text_body_and_embeds.page(params[:page]).per(5)
    end
    @page_title = "My Portfolio Blog"
  end

  def show
    if logged_in?(:admin) || logged_in?(:testing) || @blog.published?
      @blog = Blog.includes(:comments).friendly.find(params[:id])
      @comment = Comment.new
      @page_title = @blog.title
      @seo_keywords = @blog.body
    else redirect_to blogs_path, notice: 'You are not authorized to access this page.'
    end
  end

  def new
    @blog = Blog.new
  end

  def edit
  end

  def create
    @blog = Blog.new(blog_params)

    respond_to do |format|
      if @blog.save
        format.html { redirect_to blog_url(@blog), success: "Blog was successfully created." }
      else
        flash[:danger] = "Blog must have title, body and topic."
        format.html { render :new, status: :unprocessable_entity }
      end
    end
  end

  def update
    respond_to do |format|
      if @blog.update(blog_params)
        format.html { redirect_to blog_url(@blog), success: "Blog was successfully updated." }
      else
        format.html { render :edit, status: :unprocessable_entity }
      end
    end
  end

  def destroy
    @blog.destroy

    respond_to do |format|
      format.html { redirect_to blogs_url, status: :see_other }
    end
  end

  def toggle_status
    if @blog.draft? && logged_in?(:admin)
      @blog.published!
    elsif @blog.published? && logged_in?(:admin)
      @blog.draft!
    end
    redirect_to blogs_url, success: 'Post status has been updated.'
  end

  private

  def set_blog
    @blog = Blog.friendly.find(params[:id])
  end

  def blog_params
    params.require(:blog).permit(:title, :body, :topic_id, :status, images: [])
  end

  def set_sidebar_topics
    @side_bar_topics = Topic.with_blogs
  end
end

模型代码(app/models/blog.rb)

class Blog < ApplicationRecord
  enum status: { draft: 0, published: 1 }
  extend FriendlyId
  friendly_id :title, use: :slugged

  validates_presence_of :title, :body, :topic_id

  has_rich_text :body
  has_many_attached :images, dependent: :destroy

  has_many :comments, as: :commentable, dependent: :destroy, counter_cache: :commentable_count

  belongs_to :topic, optional: true

  def self.special_blogs
    all
  end

  def self.featured_blogs
    limit(2)
  end

  def self.recent
    order("updated_at DESC")
  end
end

数据库Schema

ActiveRecord::Schema[7.0].define(version: 2022_10_14_225319) do
  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"

  create_table "action_text_rich_texts", force: :cascade do |t|
    t.string "name", null: false
    t.text "body"
    t.string "record_type", null: false
    t.bigint "record_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true
  end

  create_table "active_storage_attachments", force: :cascade do |t|
    t.string "name", null: false
    t.string "record_type", null: false
    t.bigint "record_id", null: false
    t.bigint "blob_id", null: false
    t.datetime "created_at", null: false
    t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
    t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
  end

  create_table "active_storage_blobs", force: :cascade do |t|
    t.string "key", null: false
    t.string "filename", null: false
    t.string "content_type"
    t.text "metadata"
    t.string "service_name", null: false
    t.bigint "byte_size", null: false
    t.string "checksum"
    t.datetime "created_at", null: false
    t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
  end

  create_table "active_storage_variant_records", force: :cascade do |t|
    t.bigint "blob_id", null: false
    t.string "variation_digest", null: false
    t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
  end

  create_table "blogs", force: :cascade do |t|
    t.string "title"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "slug"
    t.integer "status", default: 0
    t.bigint "topic_id"
    t.integer "commentable_count"
    t.text "body"
    t.index ["slug"], name: "index_blogs_on_slug", unique: true
    t.index ["topic_id"], name: "index_blogs_on_topic_id"
  end

  create_table "comments", force: :cascade do |t|
    t.bigint "user_id", null: false
    t.string "commentable_type", null: false
    t.bigint "commentable_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.text "body"
    t.index ["commentable_type", "commentable_id"], name: "index_comments_on_commentable"
    t.index ["user_id"], name: "index_comments_on_user_id"
  end

  create_table "friendly_id_slugs", force: :cascade do |t|
    t.string "slug", null: false
    t.integer "sluggable_id", null: false
    t.string "sluggable_type", limit: 50
    t.string "scope"
    t.datetime "created_at"
    t.index ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true
    t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type"
    t.index ["sluggable_type", "sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_type_and_sluggable_id"
  end

  create_table "portfolios", force: :cascade do |t|
    t.string "title"
    t.string "subtitle"
    t.text "body"
    t.text "main_image"
    t.text "thumb_image"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.integer "position"
  end

  create_table "skills", force: :cascade do |t|
    t.string "title"
    t.integer "percent_utilized"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.text "badge"
  end

  create_table "technologies", force: :cascade do |t|
    t.string "name"
    t.bigint "portfolio_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["portfolio_id"], name: "index_technologies_on_portfolio_id"
  end

  create_table "topics", force: :cascade do |t|
    t.string "title"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "name"
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer "sign_in_count", default: 0, null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.string "current_sign_in_ip"
    t.string "last_sign_in_ip"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "roles"
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

  add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
  add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
  add_foreign_key "blogs", "topics"
  add_foreign_key "comments", "users"
  add_foreign_key "technologies", "portfolios"
end

解决建议

1. 配置Active Storage默认服务

打开config/environments/development.rb,添加或确认以下配置:

config.active_storage.service = :local

缺少该配置时,Active Storage无法识别当前环境使用的存储服务,导致active_storage_blobs表的service_name字段为空,触发验证错误。

2. 重启Rails服务器

添加配置后,重启服务器使配置生效。

3. 验证修复效果

再次尝试上传图片,检查服务器日志是否仍有Service name can't be blank错误。若配置正确,图片将正常上传并保存到指定存储位置。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 13:05:30