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

求Shrine/Rails实现多图上传的可运行示例,包含模型与控制器代码

基于Shrine实现Listing多图上传完整可运行示例

1. 前置依赖配置

首先添加相关依赖到Gemfile:

# Gemfile
gem "shrine", "~> 3.0"
gem "image_processing" # 可选,用于图片缩略图生成、格式转换等处理

执行bundle install完成安装后,添加Shrine初始化配置:

# config/initializers/shrine.rb
require "shrine"
require "shrine/storage/file_system"

Shrine.storages = {
  cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"), # 临时缓存目录
  store: Shrine::Storage::FileSystem.new("public", prefix: "uploads/store"), # 持久存储目录
}

Shrine.plugin :activerecord # 适配ActiveRecord
Shrine.plugin :cached_attachment_data # 表单验证失败重渲染时保留已选文件
Shrine.plugin :remove_attachment # 支持删除已上传附件

创建图片上传器:

# app/uploaders/image_uploader.rb
class ImageUploader < Shrine
  # 可选配置:上传校验规则
  plugin :validation_helpers
  Attacher.validate do
    validate_max_size 5.megabyte
    validate_mime_type_inclusion %w[image/jpeg image/png image/gif]
  end

  # 可选配置:生成缩略图版本
  plugin :derivatives
  Attacher.derivatives do |original|
    magick = ImageProcessing::MiniMagick.source(original)
    {
      thumbnail: magick.resize_to_limit!(200, 200)
    }
  end
end

创建数据库迁移文件:

# db/migrate/[timestamp]_create_photos.rb
class CreatePhotos < ActiveRecord::Migration[7.0]
  def change
    create_table :photos do |t|
      t.text :image_data # Shrine默认存储附件信息的字段
      t.integer :position, default: 0 # 排序字段
      t.references :listing, null: false, foreign_key: true

      t.timestamps
    end
    add_index :photos, [:listing_id, :position] # 优化排序查询性能
  end
end

执行rails db:migrate完成表结构创建。

2. 模型代码

# app/models/photo.rb
class Photo < ApplicationRecord
  include ImageUploader::Attachment(:image) # 绑定image字段到上传器
  belongs_to :listing

  default_scope { order(position: :asc) } # 默认按position升序排列
  validates :image, presence: true
end
# app/models/listing.rb
class Listing < ApplicationRecord
  has_many :photos, dependent: :destroy
  accepts_nested_attributes_for :photos, allow_destroy: true # 支持嵌套创建、删除照片
  # 此处保留你原有Listing模型的其他业务逻辑
end

3. 控制器代码

# app/controllers/listings_controller.rb
class ListingsController < ApplicationController
  def new
    @listing = Listing.new
    3.times { @listing.photos.build } # 预生成3个空照片输入框,可按需调整数量
  end

  def create
    @listing = Listing.new(listing_params)
    if @listing.save
      # 可选:生成图片缩略图
      @listing.photos.each { |photo| photo.image_derivatives! if photo.image }
      redirect_to @listing, notice: "创建成功"
    else
      # 保存失败时补全空的输入框数量
      (3 - @listing.photos.size).times { @listing.photos.build }
      render :new
    end
  end

  def edit
    @listing = Listing.find(params[:id])
    (3 - @listing.photos.size).times { @listing.photos.build }
  end

  def update
    @listing = Listing.find(params[:id])
    if @listing.update(listing_params)
      @listing.photos.each { |photo| photo.image_derivatives! if photo.image_changed? }
      redirect_to @listing, notice: "更新成功"
    else
      (3 - @listing.photos.size).times { @listing.photos.build }
      render :edit
    end
  end

  private
  def listing_params
    params.require(:listing).permit(
      # 此处添加你Listing模型的其他业务字段,例如:
      :title, :price, :description,
      photos_attributes: [:id, :image, :position, :_destroy]
    )
  end
end

4. 表单示例(用于功能验证)

使用Rails默认ERB表单的写法如下:

# app/views/listings/new.html.erb
<%= form_with model: @listing, local: true do |form| %>
  <!-- Listing业务字段输入框 -->
  <div class="form-item">
    <%= form.label :title, "商品名称" %>
    <%= form.text_field :title %>
  </div>

  <!-- 照片嵌套字段 -->
  <%= form.fields_for :photos do |photo_fields| %>
    <div class="photo-item">
      <% if photo_fields.object.persisted? %>
        <%= image_tag photo_fields.object.image.url(:thumbnail) if photo_fields.object.image %>
        <%= photo_fields.check_box :_destroy %>
        <%= photo_fields.label :_destroy, "删除该照片" %>
      <% end %>
      <%= photo_fields.file_field :image %>
      <%= photo_fields.number_field :position, placeholder: "排序权重,数字越小越靠前" %>
    </div>
  <% end %>

  <%= form.submit "提交" %>
<% end %>

如需支持动态新增/删除照片输入框,搭配少量前端JS即可实现,上述后端代码已经兼容动态生成的嵌套字段提交。

内容的提问来源于stack exchange,提问作者Peter Van de Put

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 19:39:01