Rails 4 ภาพหรือไฟล์อัพโหลดโดยใช้ carrierwave


86

ฉันจะอัปโหลดภาพหลายภาพจากหน้าต่างการเลือกไฟล์โดยใช้ Rails 4 และ CarrierWave ได้อย่างไร ฉันมีแบบจำลองpost_controllerและ post_attachmentsฉันจะทำเช่นนี้ได้อย่างไร?

ใครช่วยยกตัวอย่างได้ไหม มีวิธีง่ายๆในการนี้หรือไม่?

คำตอบ:


195

นี่คือวิธีการอัปโหลดหลายภาพโดยใช้คลื่นพาหะในราง 4 ตั้งแต่เริ่มต้น

หรือคุณสามารถค้นหาการสาธิตการใช้งานได้: รางแนบหลายตัว 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

ในมุมมอง / โพสต์ / _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 %>

เพื่อแก้ไขไฟล์แนบและรายการไฟล์แนบสำหรับโพสต์ใด ๆ ในการดู / โพสต์ / 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 %>

อัปเดตแบบฟอร์มเพื่อแก้ไขมุมมองไฟล์แนบ/ 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

ในราง 3 ไม่จำเป็นต้องกำหนดพารามิเตอร์ที่รัดกุมและในขณะที่คุณสามารถกำหนด attribute_accessible ได้ทั้งในโมเดลและ accept_nested_attribute เพื่อโพสต์โมเดลเนื่องจากแอตทริบิวต์สามารถเข้าถึงได้ถูกเลิกใช้ในราง 4

สำหรับการแก้ไขไฟล์แนบเราไม่สามารถแก้ไขไฟล์แนบทั้งหมดได้ในคราวเดียว ดังนั้นเราจะแทนที่ไฟล์แนบทีละไฟล์หรือคุณสามารถแก้ไขได้ตามกฎของคุณที่นี่ฉันจะแสดงวิธีอัปเดตไฟล์แนบ


2
ในการแสดงของตัวควบคุมโพสต์ฉันคิดว่าคุณลืมไปแล้ว @post = Post.find (params [: id])
wael

2
@SSR ทำไมคุณถึงวนซ้ำไฟล์แนบแต่ละโพสต์ในcreateการดำเนินการ? Rails และ carrierwave ฉลาดพอที่จะบันทึกคอลเลคชันโดยอัตโนมัติ
เหยี่ยว

3
ชอบที่จะเห็นการแก้ไข (โดยเฉพาะ:_destroyส่วนการจัดการ)
Tun

5
@SSR - คำตอบของคุณมีประโยชน์มาก โปรดอัปเดตคำตอบพร้อมการแก้ไขด้วย
raj_on_rails

2
เมื่อฉันเพิ่มการตรวจสอบความถูกต้องให้กับโมเดล post_attachment จะไม่ป้องกันไม่ให้บันทึกโมเดลโพสต์ แต่โพสต์จะถูกบันทึกแล้วข้อผิดพลาดที่ไม่ถูกต้องของ ActiveRecord จะถูกส่งไปสำหรับโมเดลไฟล์แนบเท่านั้น ฉันคิดว่านี่เป็นเพราะการสร้าง! วิธี. แต่การใช้การสร้างแทนจะล้มเหลวอย่างเงียบ ๆ มีความคิดอย่างไรที่จะให้การตรวจสอบความถูกต้องเกิดขึ้นในโพสต์ถึงไฟล์แนบ
dchess

32

ถ้าเราดูเอกสารของ CarrierWave ตอนนี้มันง่ายมาก

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

ฉันจะใช้ผลิตภัณฑ์เป็นรูปแบบที่ฉันต้องการเพิ่มรูปภาพดังตัวอย่าง

  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
    

หากคุณเลือกหลายภาพจากโฟลเดอร์ลำดับจะเป็นลำดับที่แน่นอนที่คุณถ่ายจากบนลงล่าง


10
วิธีแก้ปัญหานี้ของ CarrierWave ทำให้ฉันประจบประแจง มันเกี่ยวข้องกับการใส่การอ้างอิงทั้งหมดไปยังไฟล์ลงในฟิลด์เดียวในอาร์เรย์! มันคงไม่ถือเป็น "ทางรถไฟ" อย่างแน่นอน แล้วถ้าคุณต้องการลบบางส่วนหรือเพิ่มไฟล์พิเศษในโพสต์ล่ะ? ฉันไม่ได้บอกว่ามันจะเป็นไปไม่ได้ฉันแค่บอกว่ามันจะน่าเกลียด โต๊ะรวมเป็นความคิดที่ดีกว่ามาก
Toby 1 Kenobi

3
ฉันไม่เห็นด้วยกับโทบี้มากกว่านี้ คุณจะใจดีให้วิธีแก้ปัญหานั้นหรือไม่?
drjorgepolanco

2
SSR มีโซลูชันดังกล่าวอยู่แล้ว โมเดลอื่นถูกวางไว้เพื่อเก็บไฟล์ที่อัปโหลดจากนั้นสิ่งที่ต้องการไฟล์จำนวนมากที่อัปโหลดจะเกี่ยวข้องกับความสัมพันธ์แบบหนึ่งต่อกลุ่มหรือหลายต่อหลายกับโมเดลอื่นนั้น (ตารางเข้าร่วมที่ฉันพูดถึงในความคิดเห็นก่อนหน้าของฉันจะเป็นในกรณีของความสัมพันธ์แบบกลุ่มต่อกลุ่ม)
Toby 1 Kenobi

ขอบคุณ @ Toby1Kenobi ฉันสงสัยว่าวิธีการอาร์เรย์ของคอลัมน์จะอธิบายถึงเวอร์ชันของรูปภาพได้อย่างไร (ฉันไม่เห็นว่าทำได้อย่างไร) กลยุทธ์ของคุณทำได้
chaostheory

ฉันได้ใช้คุณสมบัตินี้ของ Carrierwave กับ Rails 5.xx, github.com/carrierwaveuploader/carrierwave/blob/master/… แต่ฉันไม่สามารถเรียกใช้งานได้สำเร็จและเกิดข้อผิดพลาดUndefinedConversionError ("\x89" from ASCII-8BIT to UTF-8) สำหรับโซลูชัน SSR มันทำงานได้ดีกับ Rails 4.xx แต่ฉันกำลังเผชิญกับความท้าทาย (ด้วย Rails 5.xx) เช่นการจัดเก็บActionDispatch::Http::UploadedFileในฐานข้อมูลแทนชื่อไฟล์ ยังไม่จัดเก็บไฟล์ในโฟลเดอร์สาธารณะสำหรับเส้นทางที่กำหนดในโปรแกรมอัปโหลด
Mansi Shah

8

การเพิ่มเติมเล็กน้อยสำหรับคำตอบSSR :

Accept_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ในฟอร์มออบเจ็กต์หลัก:

ในมุมมอง / โพสต์ / _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 ไม่แสดงขึ้นรูปแบบซ้อนกัน

หากคุณใช้ Rails 5 ให้เปลี่ยนRails.application.config.active_record.belongs_to_required_by_defaultค่าจากtrueเป็นfalse(ใน config / initializers / new_framework_defaults.rb) เนื่องจากบั๊กภายในaccept_nested_attributes_for ( โดยทั่วไปแล้วaccept_nested_attributes_forจะไม่ทำงานภายใต้ Rails 5)

แก้ไข 1:

หากต้องการเพิ่มเกี่ยวกับการทำลาย :

ในรุ่น / post.rb

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

ในมุมมอง / โพสต์ / _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 เป็นสิ่งที่ค่อนข้าง อันที่จริงไม่จำเป็นต้องมีตัวควบคุมเด็กเลย การทำตามแนวทางของคุณสิ่งเดียวที่ฉันทำไม่ได้คือการแสดงข้อความแสดงข้อผิดพลาดของแบบฟอร์มสำหรับเด็กเมื่อมีสิ่งผิดปกติเกิดขึ้นกับการอัปโหลด
Luis Fernando Alen

ขอบคุณสำหรับข้อมูลของคุณ ฉันทำให้การอัปโหลดใช้งานได้ แต่ฉันสงสัยว่าจะเพิ่มแอตทริบิวต์เพิ่มเติมในช่องแบบฟอร์ม post_attachments ใน views / posts / _form.html.erb ได้อย่างไร? <%= d.text_field :copyright, name: "album[diapos_attributes][][copyright]", class: 'form-field' %>เขียนลิขสิทธิ์ลงในบันทึกสุดท้ายเท่านั้นไม่ใช่บันทึกทั้งหมด
SEJU

6

นอกจากนี้ฉันยังหาวิธีอัปเดตการอัปโหลดไฟล์หลายไฟล์และฉันก็ปรับโครงสร้างใหม่อีกเล็กน้อย รหัสนี้เป็นของฉัน แต่คุณได้รับการล่องลอย

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
ขอบคุณที่แบ่งปันรหัสของคุณ เมื่อคุณมีเวลาโปรดอัปเดตโค้ดที่ gihub repo ของฉันและอย่าลืมแสดงความคิดเห็นสำหรับแต่ละวิธีเพื่อให้ทุกคนเข้าใจโค้ดได้ง่าย
SSR

1
ฉันโคลน repos คุณจะอนุญาตให้ฉันทำ PR หรือไม่?
Chris Habgood

แน่นอน. ชื่อผู้ใช้ github ของคุณคืออะไร
SSR

คุณมีโอกาสให้ฉันเข้าถึงไหม
Chris Habgood

2

นี่คือ refactor ตัวที่สองของฉันในโมเดล:

  1. ย้ายเมธอดส่วนตัวไปยังโมเดล
  2. แทนที่ @ เมนบอร์ดด้วยตัวเอง

ตัวควบคุม:

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.