Rails 4 tải lên nhiều hình ảnh hoặc tệp bằng sóng mang


86

Làm cách nào để tải lên nhiều hình ảnh từ một cửa sổ chọn tệp bằng Rails 4 và CarrierWave? Tôi có một post_controllerpost_attachmentsmô hình. Tôi có thể làm cái này như thế nào?

Ai đó có thể cung cấp một ví dụ? Có một cách tiếp cận đơn giản cho điều này?

Câu trả lời:


195

Đây là giải pháp tải lên nhiều hình ảnh bằng sóng mang trong rails 4 từ đầu

Hoặc bạn có thể tìm thấy bản demo đang hoạt động: Multiple Attachment Rails 4

Để làm chỉ cần làm theo các bước sau.

rails new multiple_image_upload_carrierwave

Trong tệp đá quý

gem 'carrierwave'
bundle install
rails generate uploader Avatar 

Tạo đoạn đầu bài đăng

rails generate scaffold post title:string

Tạo giàn giáo post_attachment

rails generate scaffold post_attachment post_id:integer avatar:string

rake db:migrate

Trong post.rb

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

Trong post_attachment.rb

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

Trong 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

Trong 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 %>

Để chỉnh sửa tệp đính kèm và danh sách tệp đính kèm cho bất kỳ bài đăng nào. Trong 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 %>

Cập nhật biểu mẫu để chỉnh sửa dạng xem tệp đính kèm / 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 %>

Sửa đổi phương pháp cập nhật trong 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

Trong rails 3 không cần xác định các tham số mạnh và bạn có thể xác định thuộc tính_accessible trong cả mô hình và accept_nested_attribute để đăng mô hình vì thuộc tính có thể truy cập không được dùng trong rails 4.

Để chỉnh sửa tệp đính kèm, chúng tôi không thể sửa đổi tất cả các tệp đính kèm cùng một lúc. vì vậy chúng tôi sẽ thay thế từng tệp đính kèm một hoặc bạn có thể sửa đổi theo quy tắc của mình, Ở đây tôi chỉ hướng dẫn bạn cách cập nhật bất kỳ tệp đính kèm nào.


2
trong hành động hiển thị của bộ điều khiển bài đăng, tôi nghĩ bạn đã quên @post = Post.find (params [: id])
wael

1
@SSR Tại sao bạn lặp lại từng tệp đính kèm của bài đăng khi createthực hiện? Rails và sóng mang đủ thông minh để lưu các bộ sưu tập tự động.
hawk

3
Rất thích xem bản chỉnh sửa (đặc biệt là :_destroyphần xử lý )
Tun

5
@SSR - Câu trả lời của bạn rất hữu ích. Bạn có thể vui lòng cập nhật câu trả lời của mình bằng thao tác chỉnh sửa không.
raj_on_rails

2
Khi tôi thêm xác thực vào mô hình post_attachment, chúng không ngăn mô hình bài đăng lưu. Thay vào đó, bài đăng được lưu và sau đó lỗi không hợp lệ ActiveRecord chỉ được đưa ra cho mô hình tệp đính kèm. Tôi nghĩ rằng điều này là do tạo ra! phương pháp. nhưng sử dụng tạo thay thế chỉ thất bại âm thầm. Bất kỳ ý tưởng làm thế nào để xác thực xảy ra trên bài đăng đến được với tệp đính kèm?
dchess,

32

Nếu chúng ta xem qua tài liệu của CarrierWave, điều này thực sự rất dễ dàng.

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

Tôi sẽ sử dụng Sản phẩm làm mô hình mà tôi muốn thêm hình ảnh, làm ví dụ.

  1. Lấy Carrierwave nhánh chính và thêm nó vào Gemfile của bạn:

    gem 'carrierwave', github:'carrierwaveuploader/carrierwave'
    
  2. Tạo một cột trong mô hình dự định để lưu trữ một mảng hình ảnh:

    rails generate migration AddPicturesToProducts pictures:json
    
  3. Chạy quá trình di chuyển

    bundle exec rake db:migrate
    
  4. Thêm hình ảnh vào mô hình Sản phẩm

    app/models/product.rb
    
    class Product < ActiveRecord::Base
      validates :name, presence: true
      mount_uploaders :pictures, PictureUploader
    end
    
  5. Thêm hình ảnh vào các thông số mạnh trong ProductsController

    app/controllers/products_controller.rb
    
    def product_params
      params.require(:product).permit(:name, pictures: [])
    end
    
  6. Cho phép biểu mẫu của bạn chấp nhận nhiều hình ảnh

    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. Trong chế độ xem của mình, bạn có thể tham khảo các hình ảnh phân tích cú pháp mảng ảnh:

    @product.pictures[1].url
    

Nếu bạn chọn một số hình ảnh từ một thư mục, thứ tự sẽ là thứ tự chính xác mà bạn đang lấy chúng từ trên xuống dưới.


9
Giải pháp của CarrierWave cho vấn đề này khiến tôi quặn lòng. Nó liên quan đến việc đặt tất cả các tham chiếu đến các tệp vào một trường trong một mảng! Nó chắc chắn sẽ không được coi là "đường ray". Điều gì sẽ xảy ra nếu bạn muốn xóa một số hoặc thêm các tệp bổ sung vào bài đăng? Tôi không nói nó sẽ không thể, tôi chỉ nói rằng nó sẽ rất xấu. Một bảng tham gia là một ý tưởng tốt hơn nhiều.
Toby 1 Kenobi

3
Tôi không thể đồng ý thêm Toby. Bạn có vui lòng cung cấp giải pháp đó không?
drjorgepolanco

2
Các giải pháp đó đã được cung cấp bởi SSR. Một mô hình khác được đặt để giữ tệp được tải lên, sau đó thứ cần nhiều tệp được tải lên sẽ liên quan đến mối quan hệ một-nhiều hoặc nhiều-nhiều với mô hình khác đó. (bảng tham gia tôi đã đề cập trong bình luận trước đây của tôi sẽ là trong trường hợp của một nhiều-nhiều mối quan hệ)
Toby 1 Kenobi

Cảm ơn @ Toby1Kenobi, tôi đã tự hỏi làm thế nào phương pháp mảng cột sẽ tính cho các phiên bản hình ảnh (tôi không thấy nó có thể như thế nào). Chiến lược của bạn là khả thi.
chaostheory

Tôi đã triển khai tính năng này của Carrierwave với Rails 5.xx, github.com/carrierwaveuploader/carrierwave/blob/master/… Nhưng tôi không thể chạy thành công và nó đang tạo ra lỗi. UndefinedConversionError ("\x89" from ASCII-8BIT to UTF-8) Đối với giải pháp SSR, nó hoạt động tốt với Rails 4.xx, nhưng tôi đang phải đối mặt với những thách thức (với Rails 5.xx) tức là nó lưu trữ ActionDispatch::Http::UploadedFiletrong cơ sở dữ liệu thay vì tên tệp. Nó cũng không lưu trữ các tệp trong các thư mục công cộng cho đường dẫn nhất định trong trình tải lên.
Mansi Shah

8

Một số bổ sung nhỏ cho câu trả lời SSR :

accept_nested_attributes_for không yêu cầu bạn thay đổi bộ điều khiển của đối tượng mẹ. Vì vậy, nếu để sửa

name: "post_attachments[avatar][]"

đến

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

sau đó tất cả những thay đổi bộ điều khiển như thế này trở nên thừa:

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

Ngoài ra, bạn nên thêm PostAttachment.newvào biểu mẫu đối tượng mẹ:

Trong 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 %>

Điều này sẽ làm dư thừa thay đổi này trong bộ điều khiển của cha mẹ:

@post_attachment = @post.post_attachments.build

Để biết thêm thông tin, hãy xem Rails fields_cho biểu mẫu không hiển thị, biểu mẫu lồng nhau

Nếu bạn sử dụng Rails 5, thì hãy thay đổi Rails.application.config.active_record.belongs_to_required_by_defaultgiá trị từ truethành false(trong config / initializers / new_framework_defaults.rb) do lỗi bên trong accept_nested_attributes_for (nếu không thì accept_nested_attributes_for thường không hoạt động trong Rails 5).

CHỈNH SỬA 1:

Để thêm về tiêu diệt :

Trong các mô hình / post.rb

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

Trong 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 %>

Bằng cách này, bạn đơn giản không cần phải có bộ điều khiển của đối tượng con! Ý tôi là không cần bất kỳ thứ PostAttachmentsControllergì nữa. Đối với controller của đối tượng cha ( PostController), bạn cũng gần như không thay đổi nó - điều duy nhất bạn thay đổi trong đó là danh sách các tham số được liệt kê trong danh sách trắng (bao gồm các tham số liên quan đến đối tượng con) như sau:

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

Đó là lý do tại sao accepts_nested_attributes_forrất tuyệt vời.


Đó thực sự là những bổ sung chính cho câu trả lời @SSR, không phải nhỏ :) accept_nested_attributes_for là một cái gì đó khá. Quả thực không cần điều khiển trẻ em gì cả. Bằng cách làm theo cách tiếp cận của bạn, điều duy nhất tôi không thể làm là hiển thị thông báo lỗi biểu mẫu cho đứa trẻ khi tải lên có sự cố.
Luis Fernando Alen

Cảm ơn vì đầu vào của bạn. Tôi đã tải lên hoạt động, nhưng tôi tự hỏi làm cách nào tôi có thể thêm các thuộc tính bổ sung vào trường biểu mẫu post_attachments trong views / posts / _form.html.erb? <%= d.text_field :copyright, name: "album[diapos_attributes][][copyright]", class: 'form-field' %>chỉ viết bản quyền cho bản ghi cuối cùng chứ không phải cho tất cả chúng.
SEJU

6

Ngoài ra, tôi đã tìm ra cách cập nhật tải lên nhiều tệp và tôi cũng đã cấu trúc lại nó một chút. Mã này là của tôi nhưng bạn sẽ bị trôi.

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

1
Cảm ơn vì đã chia sẻ mã của bạn. Khi có thời gian vui lòng cập nhật code tại repo gihub của tôi và đừng quên comment cho từng phương pháp để mọi người dễ hiểu code.
SSR

1
Tôi đã nhân bản các repo, bạn có cho phép tôi làm PR không?
Chris Habgood

Vâng, chắc chắn. Tên truy nhập github của bạn là gì
SSR

Bạn đã có cơ hội cho tôi quyền truy cập chưa?
Chris Habgood

2

Đây là bộ tái cấu trúc thứ hai của tôi vào mô hình:

  1. Di chuyển các phương thức riêng tư sang mô hình.
  2. Thay thế @ motherboard bằng self.

Bộ điều khiển:

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

Trong mô hình bo mạch chủ:

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

2

Khi sử dụng kết hợp, @post.post_attachmentsbạn không cần phải đặt post_id.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.