Rectangle 27 0

Rails 4 multiple image or file upload using carrierwave?


:_destroy
<%= 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 %>
<%= 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 %>
<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 %>
class Post < ActiveRecord::Base
   has_many :post_attachments
   accepts_nested_attributes_for :post_attachments
end
class PostAttachment < ActiveRecord::Base
   mount_uploader :avatar, AvatarUploader
   belongs_to :post
end
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
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
gem 'carrierwave'
bundle install
rails generate uploader Avatar
rails generate scaffold post title:string
rails generate scaffold post_attachment post_id:integer avatar:string

rake db:migrate
rails new multiple_image_upload_carrierwave

This is solution to upload multiple images using carrierwave in rails 4 from scratch

@SSR Why you looping through each post attachments in create action? Rails and carrierwave are smart enough to save collections automatically.

For edit an attachment we cant modify all the attachments at a time. so we will replace attachment one by one, or you can modify as per your rule, Here I just show you how to update any attachment.

In rails 3 no need to define strong parameters and as you can define attribute_accessible in both the model and accept_nested_attribute to post model because attribute accessible is deprecated in rails 4.

To edit an attachment and list of attachment for any post. In views/posts/show.html.erb

When I add validations to the post_attachment model, they do not prevent the post model from saving. Instead the post is saved, and then the ActiveRecord invalid error is thrown for the attachment model only. I think this is because of the create! method. but using create instead just fails silently. Any idea how to have the validation happen on the post reach into the attachments?

in the show action of the post controller i think you've forgot @post =Post.find(params[:id])

Note
Rectangle 27 0

Rails 4 multiple image or file upload using carrierwave?


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

Also I figured out how to update the multiple file upload and I also refactored it a bit. This code is mine but you get the drift.

Have you had a chance to give me access?

Thanks for sharing your code. when you get time please update code at my gihub repo and do not forget to comment for each method so everyone can easily understand code.

Note
Rectangle 27 0

Rails 4 multiple image or file upload using carrierwave?


@product.pictures[1].url
app/controllers/products_controller.rb

def product_params
  params.require(:product).permit(:name, pictures: [])
end
app/models/product.rb

class Product < ActiveRecord::Base
  validates :name, presence: true
  mount_uploaders :pictures, PictureUploader
end
gem 'carrierwave', github:'carrierwaveuploader/carrierwave'
rails generate migration AddPicturesToProducts pictures:json
  • Allow your form to accept multiple pictures 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 %>
  • Run the migration bundle exec rake db:migrate

Add pictures to strong params in ProductsController

CarrierWave's solution to this problem makes me cringe. It involves putting all the references to the files into one field in an array! It certainly wouldn't be considered the "rails way". What if you then want to remove some, or add extra files to the post? I'm not saying it wouldn't be possible, I'm just saying it would be ugly. A join table is a much better idea.

Create a column in the intended model to host an array of images:

Get the master branch Carrierwave and add it to your Gemfile:

I will use Product as the model I want to add the pictures, as an example.

If we take a look at CarrierWave's documentation, this is actually very easy now.

If you choose several images from a folder, the order will be the exact order you are taking them from top to bottom.

In your views, you can reference the images parsing the pictures array:

Thanks @Toby1Kenobi, I was wondering how the column array method would account for image versions (I don't see how it can). Your strategy is doable.

That solutions is already provided by SSR. Another model is put in place to hold the uploaded file, then the thing that needs many files uploaded relates in a one-to-many or many-to-many relationship with that other model. (the join table I mentioned in my earlier comment would be in the case of a many-to-many relationship)

Note
Rectangle 27 0

Rails 4 multiple image or file upload using carrierwave?


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
  • Replace @motherboard with self.

Here is my second refactor into the model:

Note
Rectangle 27 0

Rails 4 multiple image or file upload using carrierwave?


<%= 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 %>
<% 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 %>
@post_attachment = @post.post_attachments.build
PostAttachment.new
Rails.application.config.active_record.belongs_to_required_by_default
accepts_nested_attributes_for
class Post < ApplicationRecord
    ...
    accepts_nested_attributes_for :post_attachments, allow_destroy: true
end
def post_params
  params.require(:post).permit(:title, :text, 
    post_attachments_attributes: ["avatar", "@original_filename", "@content_type", "@headers", "_destroy", "id"])
end
false
name: "post[post_attachments_attributes][][avatar]"
name: "post_attachments[avatar][]"
params[:post_attachments]['avatar'].each do |a|
  @post_attachment = @post.post_attachments.create!(:avatar => a)
end
true

This way you simply do not need to have a child object's controller at all! I mean no any PostAttachmentsController is needed anymore. As for parent object's controller (PostController), you also almost don't change it - the only thing you change in there is the list of the whitelisted params (to include the child object-related params) like this:

This would make redundant this change in the parent's controller:

Those are actually major additions to @SSR answer, not minor :) accept_nested_attributes_for is quite something. Indeed there's no need for a child controller at all. By following your approach, the only thing I'm unable to do is to display form error messages for the child when something goes wrong with the upload.

accepts_nested_attributes_for does not require you to change the parent object's controller. So if to correct

Note
Rectangle 27 0

Rails 4 multiple image or file upload using carrierwave?


When using the association @post.post_attachments you do not need to set the post_id.

Note