求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
相关产品推荐
相关产品推荐

