【问题标题】:Rails 4 multiple image or file upload using carrierwaveRails 4使用carrierwave上传多个图像或文件
【发布时间】:2014-02-20 03:05:47
【问题描述】:

如何使用 Rails 4 和 CarrierWave 从文件选择窗口上传多个图像?我有一个 post_controllerpost_attachments 模型。我该怎么做?

有人可以举个例子吗?有没有简单的方法?

【问题讨论】:

    标签: ruby-on-rails-4 carrierwave


    【解决方案1】:

    这是使用 rails 4 中的载波从头开始上传多个图像的解决方案

    或者你可以找到工作演示: Multiple Attachment Rails 4

    只需按照以下步骤操作即可。

    rails new multiple_image_upload_carrierwave
    

    在 gem 文件中

    gem 'carrierwave'
    bundle install
    rails generate uploader Avatar 
    

    创建脚手架

    rails generate scaffold post title:string
    

    创建 post_attachment 脚手架

    rails generate scaffold post_attachment post_id:integer avatar:string
    
    rake db:migrate
    

    在 post.rb 中

    class Post < ActiveRecord::Base
       has_many :post_attachments
       accepts_nested_attributes_for :post_attachments
    end
    

    在 post_attachment.rb 中

    class PostAttachment < ActiveRecord::Base
       mount_uploader :avatar, AvatarUploader
       belongs_to :post
    end
    

    在 post_controller.rb 中

    def show
       @post_attachments = @post.post_attachments.all
    end
    
    def new
       @post = Post.new
       @post_attachment = @post.post_attachments.build
    end
    
    def create
       @post = Post.new(post_params)
    
       respond_to do |format|
         if @post.save
           params[:post_attachments]['avatar'].each do |a|
              @post_attachment = @post.post_attachments.create!(:avatar => a)
           end
           format.html { redirect_to @post, notice: 'Post was successfully created.' }
         else
           format.html { render action: 'new' }
         end
       end
     end
    
     private
       def post_params
          params.require(:post).permit(:title, post_attachments_attributes: [:id, :post_id, :avatar])
       end
    

    在views/posts/_form.html.erb中

    <%= form_for(@post, :html => { :multipart => true }) do |f| %>
       <div class="field">
         <%= f.label :title %><br>
         <%= f.text_field :title %>
       </div>
    
       <%= f.fields_for :post_attachments do |p| %>
         <div class="field">
           <%= p.label :avatar %><br>
           <%= p.file_field :avatar, :multiple => true, name: "post_attachments[avatar][]" %>
         </div>
       <% end %>
    
       <div class="actions">
         <%= f.submit %>
       </div>
    <% end %>
    

    编辑任何帖子的附件和附件列表。 在views/posts/show.html.erb中

    <p id="notice"><%= notice %></p>
    
    <p>
      <strong>Title:</strong>
      <%= @post.title %>
    </p>
    
    <% @post_attachments.each do |p| %>
      <%= image_tag p.avatar_url %>
      <%= link_to "Edit Attachment", edit_post_attachment_path(p) %>
    <% end %>
    
    <%= link_to 'Edit', edit_post_path(@post) %> |
    <%= link_to 'Back', posts_path %>
    

    更新表单以编辑附件views/post_attachments/_form.html.erb

    <%= image_tag @post_attachment.avatar %>
    <%= form_for(@post_attachment) do |f| %>
      <div class="field">
        <%= f.label :avatar %><br>
        <%= f.file_field :avatar %>
      </div>
      <div class="actions">
        <%= f.submit %>
      </div>
    <% end %>
    

    修改post_attachment_controller.rb中的更新方法

    def update
      respond_to do |format|
        if @post_attachment.update(post_attachment_params)
          format.html { redirect_to @post_attachment.post, notice: 'Post attachment was successfully updated.' }
        end 
      end
    end
    

    在 rails 3 中无需定义强参数,您可以在模型中定义 attribute_accessible 并在模型中定义 accept_nested_attribute 来发布模型,因为在 rails 4 中不推荐使用属性可访问性。

    对于编辑附件,我们不能一次修改所有附件。所以我们会一一替换附件,或者你可以根据你的规则修改,这里我只是告诉你如何更新任何附件。

    【讨论】:

    • 在后控制器的显示动作中,我想你忘记了@post =Post.find(params[:id])
    • @SSR 为什么在create 操作中循环浏览每个帖子附件? Rails 和carrierwave 足够智能,可以自动保存收藏。
    • 希望看到编辑(尤其是处理:_destroy 部分)
    • @SSR - 你的回答很有帮助。您能否也通过编辑操作更新您的答案。
    • 当我向 post_attachment 模型添加验证时,它们不会阻止 post 模型保存。而是保存帖子,然后仅为附件模型引发 ActiveRecord 无效错误。我想这是因为创造!方法。但是使用 create 只是默默地失败。知道如何在帖子到达附件时进行验证吗?
    【解决方案2】:

    如果我们看一下 CarrierWave 的文档,现在这实际上非常简单。

    https://github.com/carrierwaveuploader/carrierwave/blob/master/README.md#multiple-file-uploads

    我将使用Product作为我要添加图片的模型,作为示例。

    1. 获取主分支 Carrierwave 并将其添加到您的 Gemfile:

      gem 'carrierwave', github:'carrierwaveuploader/carrierwave'
      
    2. 在预期模型中创建一个列来托管图像数组:

      rails generate migration AddPicturesToProducts pictures:json
      
    3. 运行迁移

      bundle exec rake db:migrate
      
    4. 为模型产品添加图片

      app/models/product.rb
      
      class Product < ActiveRecord::Base
        validates :name, presence: true
        mount_uploaders :pictures, PictureUploader
      end
      
    5. 在 ProductsController 中为强参数添加图片

      app/controllers/products_controller.rb
      
      def product_params
        params.require(:product).permit(:name, pictures: [])
      end
      
    6. 允许您的表单接受多张图片

      app/views/products/new.html.erb
      
      # notice 'html: { multipart: true }'
      <%= form_for @product, html: { multipart: true } do |f| %>
        <%= f.label :name %>
        <%= f.text_field :name %>
      
        # notice 'multiple: true'
        <%= f.label :pictures %>
        <%= f.file_field :pictures, multiple: true, accept: "image/jpeg, image/jpg, image/gif, image/png" %>
      
        <%= f.submit "Submit" %>
      <% end %>
      
    7. 在您的视图中,您可以引用解析图片数组的图像:

      @product.pictures[1].url
      

    如果您从一个文件夹中选择多个图像,则顺序将与您从上到下的确切顺序相同。

    【讨论】:

    • CarrierWave 对这个问题的解决方案让我感到畏缩。它涉及将所有对文件的引用放入数组中的一个字段中!它当然不会被认为是“铁路方式”。如果您想删除一些文件或在帖子中添加额外文件怎么办?我不是说这不可能,我只是说这会很丑。连接表是一个更好的主意。
    • 托比我完全同意。您能提供这样的解决方案吗?
    • SSR 已经提供了该解决方案。放置另一个模型来保存上传的文件,然后需要上传许多文件的东西与另一个模型建立一对多或多对多的关系。 (我在之前的评论中提到的连接表将是多对多关系的情况)
    • 谢谢@Toby1Kenobi,我想知道列数组方法如何解释图像版本(我不明白它是怎么做到的)。你的策略是可行的。
    • 我已经用 Rails 5.xx 实现了 Carrierwave 的这个功能,github.com/carrierwaveuploader/carrierwave/blob/master/… 但是我无法成功运行它,并且它正在产生错误,UndefinedConversionError ("\x89" from ASCII-8BIT to UTF-8) 对于 SSR 解决方案,它工作正常使用 Rails 4.xx,但我面临挑战(使用 Rails 5.xx),即它在数据库中存储 ActionDispatch::Http::UploadedFile 而不是文件名。它也不会将文件存储在上传器中给定路径的公共文件夹中。
    【解决方案3】:

    SSR 答案的一些小补充:

    accepts_nested_attributes_for 不需要您更改父对象的控制器。所以如果要纠正

    name: "post_attachments[avatar][]"
    

    name: "post[post_attachments_attributes][][avatar]"
    

    那么像这样的所有这些控制器更改都变得多余:

    params[:post_attachments]['avatar'].each do |a|
      @post_attachment = @post.post_attachments.create!(:avatar => a)
    end
    

    您还应该在父对象表单中添加PostAttachment.new

    在views/posts/_form.html.erb中

      <%= f.fields_for :post_attachments, PostAttachment.new do |ff| %>
        <div class="field">
          <%= ff.label :avatar %><br>
          <%= ff.file_field :avatar, :multiple => true, name: "post[post_attachments_attributes][][avatar]" %>
        </div>
      <% end %>
    

    这会使父控制器中的这种更改变得多余:

    @post_attachment = @post.post_attachments.build
    

    欲了解更多信息,请参阅Rails fields_for form not showing up, nested form

    如果你使用 Rails 5,那么由于 accepts_nested_attributes_for 中的错误(否则 accepts_nested_attributes_for 一般不能在 Rails 5 下工作)。

    编辑 1:

    添加关于destroy

    在models/post.rb中

    class Post < ApplicationRecord
        ...
        accepts_nested_attributes_for :post_attachments, allow_destroy: true
    end
    

    在views/posts/_form.html.erb中

     <% f.object.post_attachments.each do |post_attachment| %>
        <% if post_attachment.id %>
    
          <%
    
          post_attachments_delete_params =
          {
          post:
            {              
              post_attachments_attributes: { id: post_attachment.id, _destroy: true }
            }
          }
    
          %>
    
          <%= link_to "Delete", post_path(f.object.id, post_attachments_delete_params), method: :patch, data: { confirm: 'Are you sure?' } %>
    
          <br><br>
        <% end %>
      <% end %>
    

    这样,您根本就不需要拥有子对象的控制器!我的意思是不再需要任何PostAttachmentsController。至于父对象的控制器 (PostController),您也几乎不会更改它 - 您唯一更改的是白名单参数列表(包括与子对象相关的参数) 像这样:

    def post_params
      params.require(:post).permit(:title, :text, 
        post_attachments_attributes: ["avatar", "@original_filename", "@content_type", "@headers", "_destroy", "id"])
    end
    

    这就是accepts_nested_attributes_for 如此神奇的原因。

    【讨论】:

    • 这些实际上是对@SSR 答案的主要补充,而不是次要:) accept_nested_attributes_for 相当重要。事实上,根本不需要子控制器。按照您的方法,我唯一无法做的就是在上传出现问题时为孩子显示表单错误消息。
    • 感谢您的意见。我得到了上传工作,但我想知道如何在 view/posts/_form.html.erb 中的 post_attachments 表单字段中添加其他属性? &lt;%= d.text_field :copyright, name: "album[diapos_attributes][][copyright]", class: 'form-field' %&gt; 仅将版权写入最后一条记录,而不是所有记录。
    【解决方案4】:

    我还想出了如何更新多文件上传,我还对其进行了一些重构。这段代码是我的,但你明白了。

    def create
      @motherboard = Motherboard.new(motherboard_params)
      if @motherboard.save
        save_attachments if params[:motherboard_attachments]
        redirect_to @motherboard, notice: 'Motherboard was successfully created.'
      else
        render :new
      end
    end
    
    
    def update
      update_attachments if params[:motherboard_attachments]
      if @motherboard.update(motherboard_params)
        redirect_to @motherboard, notice: 'Motherboard was successfully updated.'
      else
       render :edit
      end
    end
    
    private
    def save_attachments
      params[:motherboard_attachments]['photo'].each do |photo|
        @motherboard_attachment = @motherboard.motherboard_attachments.create!(:photo => photo)
      end
    end
    
     def update_attachments
       @motherboard.motherboard_attachments.each(&:destroy) if @motherboard.motherboard_attachments.present?
       params[:motherboard_attachments]['photo'].each do |photo|
         @motherboard_attachment = @motherboard.motherboard_attachments.create!(:photo => photo)
       end
     end
    

    【讨论】:

    • 感谢您分享您的代码。有时间请在我的 gihub repo 更新代码,不要忘记对每个方法进行评论,以便每个人都可以轻松理解代码。
    • 我克隆了repos,你能允许我做PR吗?
    • 是的,当然。你的github用户名是什么
    • 你有机会给我访问权限吗?
    【解决方案5】:

    这是我对模型的第二次重构:

    1. 将私有方法移至模型。
    2. 将@motherboard 替换为self。

    控制器:

    def create
      @motherboard = Motherboard.new(motherboard_params)
    
      if @motherboard.save
        @motherboard.save_attachments(params) if params[:motherboard_attachments]
      redirect_to @motherboard, notice: 'Motherboard was successfully created.'
      else
        render :new
      end
    end
    
    def update
      @motherboard.update_attachments(params) if params[:motherboard_attachments]
      if @motherboard.update(motherboard_params)
        redirect_to @motherboard, notice: 'Motherboard was successfully updated.'
      else
        render :edit
      end
    end
    

    在主板型号中:

    def save_attachments(params)
      params[:motherboard_attachments]['photo'].each do |photo|
        self.motherboard_attachments.create!(:photo => photo)
      end
    end
    
    def update_attachments(params)
      self.motherboard_attachments.each(&:destroy) if self.motherboard_attachments.present?
      params[:motherboard_attachments]['photo'].each do |photo|
        self.motherboard_attachments.create!(:photo => photo)
      end
    end
    

    【讨论】:

      【解决方案6】:

      使用@post.post_attachments关联时,不需要设置post_id

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-04-05
        • 2017-03-02
        • 2016-01-06
        • 2013-11-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多