Rails 4 mengunggah beberapa gambar atau file menggunakan carrierwave

86

Bagaimana cara mengunggah banyak gambar dari jendela pemilihan file menggunakan Rails 4 dan CarrierWave? Saya memiliki post_controllerdan post_attachmentsmodel. Bagaimana saya bisa melakukan ini?

Bisakah seseorang memberikan contoh? Apakah ada pendekatan sederhana untuk ini?

SSR
sumber

Jawaban:

195

Ini adalah solusi untuk mengunggah banyak gambar menggunakan gelombang pembawa di rel 4 dari awal

Atau Anda dapat menemukan demo yang berfungsi: Multiple Attachment Rails 4

Untuk melakukan cukup ikuti langkah-langkah ini.

rails new multiple_image_upload_carrierwave

Dalam file permata

gem 'carrierwave'
bundle install
rails generate uploader Avatar 

Buat perancah posting

rails generate scaffold post title:string

Buat perancah post_attachment

rails generate scaffold post_attachment post_id:integer avatar:string

rake db:migrate

Di post.rb

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

Di post_attachment.rb

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

Di 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

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

Untuk mengedit lampiran dan daftar lampiran untuk setiap posting. Dalam 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 %>

Perbarui formulir untuk mengedit tampilan lampiran / 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 %>

Ubah metode pembaruan di 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

Dalam rails 3 tidak perlu untuk menentukan parameter yang kuat dan karena Anda dapat mendefinisikan atribut_accessible di kedua model dan accept_nested_attribute to post model karena atribut yang dapat diakses sudah tidak digunakan lagi di rails 4.

Untuk mengedit lampiran, kami tidak dapat mengubah semua lampiran dalam satu waktu. jadi kami akan mengganti lampiran satu per satu, atau Anda dapat memodifikasi sesuai aturan Anda, Di sini saya hanya menunjukkan kepada Anda cara memperbarui lampiran apa pun.

SSR
sumber
2
dalam aksi pertunjukan pengontrol pos saya pikir Anda lupa @post = Post.find (params [: id])
wael
2
@SSR Mengapa Anda mengulang setiap lampiran posting dalam createtindakan? Rel dan gelombang pembawa cukup pintar untuk menyimpan koleksi secara otomatis.
elang
3
Ingin sekali melihat edit (terutama :_destroybagian penanganan )
Tun
5
@SSR - Jawaban Anda sangat membantu. Bisakah Anda memperbarui jawaban Anda dengan tindakan edit juga.
raj_on_rails
2
Ketika saya menambahkan validasi ke model post_attachment, mereka tidak mencegah model posting dari penyimpanan. Sebaliknya posting disimpan, dan kemudian kesalahan tidak valid ActiveRecord dilemparkan hanya untuk model lampiran. Saya pikir ini karena ciptaan! metode. tetapi menggunakan create malah gagal secara diam-diam. Adakah ide bagaimana agar validasi terjadi pada posting yang menjangkau lampiran?
dchess
32

Jika kita melihat dokumentasi CarrierWave, ini sebenarnya sangat mudah sekarang.

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

Saya akan menggunakan Produk sebagai model yang ingin saya tambahkan gambarnya, sebagai contoh.

  1. Dapatkan Carrierwave cabang master dan tambahkan ke Gemfile Anda:

    gem 'carrierwave', github:'carrierwaveuploader/carrierwave'
    
  2. Buat kolom dalam model yang dimaksudkan untuk menghosting array gambar:

    rails generate migration AddPicturesToProducts pictures:json
    
  3. Jalankan migrasi

    bundle exec rake db:migrate
    
  4. Tambahkan gambar ke model Produk

    app/models/product.rb
    
    class Product < ActiveRecord::Base
      validates :name, presence: true
      mount_uploaders :pictures, PictureUploader
    end
    
  5. Tambahkan gambar ke parameter yang kuat di ProductsController

    app/controllers/products_controller.rb
    
    def product_params
      params.require(:product).permit(:name, pictures: [])
    end
    
  6. Izinkan formulir Anda menerima banyak gambar

    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. Dalam pandangan Anda, Anda dapat mereferensikan gambar yang mengurai larik gambar:

    @product.pictures[1].url
    

Jika Anda memilih beberapa gambar dari sebuah folder, urutannya akan sama persis dengan yang Anda ambil dari atas ke bawah.

drjorgepolanco
sumber
10
Solusi CarrierWave untuk masalah ini membuat saya ngeri. Ini melibatkan meletakkan semua referensi ke file ke dalam satu bidang dalam sebuah array! Ini tentu tidak akan dianggap sebagai "jalur rel". Bagaimana jika Anda kemudian ingin menghapus beberapa, atau menambahkan file tambahan ke posting? Saya tidak mengatakan itu tidak akan mungkin, saya hanya mengatakan itu akan jelek. Tabel gabungan adalah ide yang jauh lebih baik.
Toby 1 Kenobi
3
Saya sangat setuju Toby. Apakah Anda akan berbaik hati memberikan solusi itu?
drjorgepolanco
2
Solusi itu sudah disediakan oleh SSR. Model lain ditempatkan untuk menyimpan file yang diunggah, kemudian hal yang membutuhkan banyak file yang diunggah terkait dalam hubungan satu-ke-banyak atau banyak-ke-banyak dengan model lain itu. (tabel gabung yang saya sebutkan dalam komentar saya sebelumnya adalah dalam kasus hubungan banyak-ke-banyak)
Toby 1 Kenobi
Terima kasih @ Toby1Kenobi, saya bertanya-tanya bagaimana metode array kolom akan menjelaskan versi gambar (saya tidak melihat bagaimana bisa). Strategi Anda bisa dilakukan.
chaostheory
Saya telah mengimplementasikan fitur Carrierwave dengan Rails 5.xx, github.com/carrierwaveuploader/carrierwave/blob/master/… Tapi saya tidak dapat menjalankannya dengan sukses, dan itu menghasilkan kesalahan, UndefinedConversionError ("\x89" from ASCII-8BIT to UTF-8) Untuk solusi SSR, ini berfungsi dengan baik dengan Rails 4.xx, tetapi saya menghadapi tantangan (dengan Rails 5.xx) yaitu penyimpanannya ActionDispatch::Http::UploadedFiledalam database, bukan nama file. Ini juga tidak menyimpan file di folder publik untuk jalur tertentu di pengunggah.
Mansi Shah
8

Beberapa tambahan kecil pada jawaban SSR :

accepts_nested_attributes_for tidak mengharuskan Anda mengubah pengontrol objek induk. Jadi jika benar

name: "post_attachments[avatar][]"

untuk

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

maka semua perubahan pengontrol seperti ini menjadi mubazir:

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

Anda juga harus menambahkan PostAttachment.new ke bentuk objek induk:

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

Ini akan membuat perubahan ini menjadi mubazir di pengontrol induk:

@post_attachment = @post.post_attachments.build

Untuk info lebih lanjut lihat Rails fields_for form tidak muncul, formulir bersarang

Jika Anda menggunakan Rails 5, maka ubah Rails.application.config.active_record.belongs_to_required_by_defaultnilai dari truemenjadi false(di config / initializers / new_framework_defaults.rb) karena bug di dalam accepts_nested_attributes_for (jika tidak, accepts_nested_attributes_for biasanya tidak akan berfungsi di bawah Rails 5).

EDIT 1:

Untuk menambahkan tentang menghancurkan :

Di models / post.rb

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

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

Dengan cara ini Anda sama sekali tidak perlu memiliki pengontrol objek anak! Maksud saya tidak ada PostAttachmentsControllerlagi yang dibutuhkan. Adapun controller ( PostController) objek induk , Anda juga hampir tidak mengubahnya - satu-satunya hal yang Anda ubah di sana adalah daftar whitelist params (untuk menyertakan parameter terkait objek anak) seperti ini:

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

Itulah mengapa accepts_nested_attributes_forsangat menakjubkan.

prograils
sumber
Itu sebenarnya adalah tambahan utama untuk jawaban @SSR, bukan minor :) accept_nested_attributes_for cukup berarti. Memang tidak diperlukan pengontrol anak sama sekali. Dengan mengikuti pendekatan Anda, satu-satunya hal yang tidak dapat saya lakukan adalah menampilkan pesan kesalahan formulir untuk anak ketika terjadi kesalahan dengan unggahan.
Luis Fernando Alen
Terima kasih atas masukan Anda. Saya mendapatkan unggahan berfungsi, tetapi saya bertanya-tanya bagaimana saya dapat menambahkan atribut tambahan ke bidang formulir post_attachments di views / posts / _form.html.erb? <%= d.text_field :copyright, name: "album[diapos_attributes][][copyright]", class: 'form-field' %>menulis hak cipta hanya untuk rekaman terakhir dan tidak untuk semuanya.
SEJU
6

Saya juga menemukan cara memperbarui beberapa file yang diunggah dan saya juga sedikit merefaktornya. Kode ini milik saya tetapi Anda mengerti maksudnya.

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
Chris Habgood
sumber
1
Terima kasih telah membagikan kode Anda. bila ada waktu silahkan update kode di repo gihub saya dan jangan lupa memberikan komentar untuk setiap cara agar semua orang dapat dengan mudah memahami kode.
SSR
1
Saya mengkloning repo, apakah Anda akan memberi saya izin untuk melakukan PR?
Chris Habgood
Ya tentu. Apa nama pengguna github Anda
SSR
Apakah Anda punya kesempatan untuk memberi saya akses?
Chris Habgood
2

Inilah refaktor kedua saya ke dalam model:

  1. Pindahkan metode privat ke model.
  2. Ganti @motherboard dengan diri sendiri.

Pengontrol:

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

Dalam model motherboard:

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
Chris Habgood
sumber
2

Saat menggunakan pengaitan, @post.post_attachmentsAnda tidak perlu menyetel post_id.

Chris Habgood
sumber