Rails 4 przesyłanie wielu obrazów lub plików za pomocą carrierwave


86

Jak mogę przesłać wiele obrazów z okna wyboru plików przy użyciu Rails 4 i CarrierWave? Mam model post_controlleri post_attachments. Jak mogę to zrobić?

Czy ktoś może podać przykład? Czy jest na to proste podejście?

Odpowiedzi:


195

Jest to rozwiązanie umożliwiające przesyłanie od zera wielu obrazów za pomocą carrierwave w szynach 4

Lub możesz znaleźć działające demo: Wiele szyn mocujących 4

Aby to zrobić, wykonaj następujące kroki.

rails new multiple_image_upload_carrierwave

W pliku gem

gem 'carrierwave'
bundle install
rails generate uploader Avatar 

Utwórz rusztowanie słupkowe

rails generate scaffold post title:string

Utwórz rusztowanie post_attachment

rails generate scaffold post_attachment post_id:integer avatar:string

rake db:migrate

W post.rb

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

W post_attachment.rb

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

W 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

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

Aby edytować załącznik i listę załączników do dowolnego posta. W 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 %>

Zaktualizuj formularz, aby edytować widoki załączników / 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 %>

Zmodyfikuj metodę aktualizacji w 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

W rails 3 nie ma potrzeby definiowania silnych parametrów, a ponieważ można zdefiniować atrybut_accessible zarówno w modelu, jak i accept_nested_attribute do modelu post, ponieważ atrybut access jest przestarzały w rails 4.

W celu edycji załącznika nie możemy modyfikować wszystkich załączników naraz. więc wymienimy załącznik jeden po drugim lub możesz zmodyfikować zgodnie z regułą, tutaj pokażę ci tylko, jak zaktualizować dowolny załącznik.


2
w akcji show kontrolera pocztowego myślę, że zapomniałeś @post = Post.find (params [: id])
— wael

1
@SSR Dlaczego w createakcji przeglądasz załączniki każdego posta ? Railsy i carrierwave są wystarczająco inteligentne, aby automatycznie zapisywać kolekcje.
— jastrząb

3
Chciałbym zobaczyć edycję (zwłaszcza :_destroyczęść dotyczącą obsługi )
— Tun,

5
@SSR - Twoja odpowiedź jest bardzo pomocna. Czy mógłbyś zaktualizować swoją odpowiedź również za pomocą akcji edycji.
— raj_on_rails

2
Kiedy dodam walidacje do modelu post_attachment, nie uniemożliwiają one zapisania modelu post. Zamiast tego wpis jest zapisywany, a następnie zgłaszany jest błąd nieprawidłowego rekordu ActiveRecord tylko dla modelu załącznika. Myślę, że to z powodu tworzenia! metoda. ale użycie Create zamiast tego po prostu cicho zawodzi. Masz pomysł, jak sprawić, by walidacja nastąpiła po dotarciu do załączników?
— dchess

32

Jeśli spojrzymy na dokumentację CarrierWave, jest to teraz bardzo łatwe.

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

Będę używał produktu jako modelu, który chcę dodać zdjęcia, jako przykład.

  1. Pobierz główną gałąź Carrierwave i dodaj ją do swojego Gemfile:

    gem 'carrierwave', github:'carrierwaveuploader/carrierwave'
    
  2. Utwórz kolumnę w modelu docelowym, aby hostować tablicę obrazów:

    rails generate migration AddPicturesToProducts pictures:json
    
  3. Uruchom migrację

    bundle exec rake db:migrate
    
  4. Dodaj zdjęcia do modelu produktu

    app/models/product.rb
    
    class Product < ActiveRecord::Base
      validates :name, presence: true
      mount_uploaders :pictures, PictureUploader
    end
    
  5. Dodaj zdjęcia do silnych parametrów w ProductsController

    app/controllers/products_controller.rb
    
    def product_params
      params.require(:product).permit(:name, pictures: [])
    end
    
  6. Zezwól formularzowi na akceptowanie wielu zdjęć

    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. W swoich widokach możesz odwoływać się do obrazów analizujących tablicę pictures:

    @product.pictures[1].url
    

Jeśli wybierzesz kilka zdjęć z folderu, kolejność będzie dokładnie taka, jak robisz je od góry do dołu.


9
Wzdrygam się przy rozwiązaniu tego problemu przez firmę CarrierWave. Polega na umieszczeniu wszystkich odniesień do plików w jednym polu w tablicy! Z pewnością nie byłoby to uważane za „sposób szynowy”. A co, jeśli chcesz usunąć część lub dodać dodatkowe pliki do posta? Nie mówię, że to nie byłoby możliwe, mówię tylko, że byłoby brzydkie. Stolik do łączenia to znacznie lepszy pomysł.
— Toby 1 Kenobi

3
Nie mogłem się bardziej zgodzić, Toby. Czy byłbyś tak uprzejmy, aby zapewnić takie rozwiązanie?
— drjorgepolanco

2
Takie rozwiązania zapewnia już SSR. Wprowadzany jest inny model do przechowywania przesłanego pliku, wtedy rzecz, która wymaga przesłania wielu plików, jest powiązana w relacji jeden do wielu lub wiele do wielu z tym innym modelem. (stół do łączenia, o którym wspomniałem w moim wcześniejszym komentarzu, byłby w przypadku relacji wiele do wielu)
— Toby 1 Kenobi

Dzięki @ Toby1Kenobi, zastanawiałem się, jak metoda tablic kolumnowych mogłaby uwzględniać wersje obrazów (nie widzę, jak to może). Twoja strategia jest wykonalna.
— chaostheory

Zaimplementowałem tę funkcję Carrierwave z Railsami 5.xx, github.com/carrierwaveuploader/carrierwave/blob/master/… Ale nie mogę jej pomyślnie uruchomić i generuje błąd, UndefinedConversionError ("\x89" from ASCII-8BIT to UTF-8) W przypadku rozwiązania SSR działa dobrze z Rails 4.xx, ale stoję przed wyzwaniami (z Railsami 5.xx), tj. Przechowywanie ActionDispatch::Http::UploadedFilew bazie danych zamiast nazwy pliku. Nie przechowuje również plików w folderach publicznych dla podanej ścieżki w programie do przesyłania.
— Mansi Shah

8

Kilka drobnych dodatków do odpowiedzi SSR :

accepts_nested_attributes_for nie wymaga zmiany kontrolera obiektu nadrzędnego. Więc jeśli poprawiać

name: "post_attachments[avatar][]"

do

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

wtedy wszystkie te zmiany kontrolera, takie jak te, stają się zbędne:

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

Należy również dodać PostAttachment.newdo formularza obiektu nadrzędnego:

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

Spowodowałoby to zbędną zmianę w kontrolerze rodzica:

@post_attachment = @post.post_attachments.build

Aby uzyskać więcej informacji, zobacz Rails fields_for formularz, który się nie wyświetla, zagnieżdżony formularz

Jeśli używasz Rails 5, zmień Rails.application.config.active_record.belongs_to_required_by_defaultwartość z truena false(w config / initializers / new_framework_defaults.rb) z powodu błędu wewnątrz accepts_nested_attributes_for (w przeciwnym razie accepts_nested_attributes_for nie będzie działać w Rails 5).

EDYCJA 1:

Aby dodać o zniszczeniu :

W modelach / post.rb

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

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

W ten sposób w ogóle nie musisz mieć kontrolera obiektu podrzędnego! To znaczy, nic PostAttachmentsControllerjuż nie jest potrzebne. Jeśli chodzi o kontroler obiektu nadrzędnego ( PostController), prawie go nie zmieniasz - jedyną rzeczą, którą tam zmieniasz, jest lista parametrów na białej liście (w celu uwzględnienia parametrów związanych z obiektem potomnym) w następujący sposób:

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

Dlatego accepts_nested_attributes_forjest tak niesamowity.


To są właściwie główne dodatki do odpowiedzi @SSR, a nie drugorzędne :) accept_nested_attributes_for to całkiem coś. W rzeczywistości w ogóle nie jest potrzebny kontroler podrzędny. Postępując zgodnie z Twoim podejściem, jedyną rzeczą, której nie jestem w stanie zrobić, jest wyświetlanie dziecku komunikatów o błędach, gdy coś pójdzie nie tak z przesyłaniem.
— Luis Fernando Alen

Dzięki za wkład. Przesyłanie działa, ale zastanawiam się, jak mogę dodać dodatkowe atrybuty do pola formularza post_attachments w views / posts / _form.html.erb? <%= d.text_field :copyright, name: "album[diapos_attributes][][copyright]", class: 'form-field' %>zapisuje prawa autorskie tylko do ostatniej płyty, a nie do wszystkich.
— SEJU

6

Dowiedziałem się również, jak zaktualizować przesyłanie wielu plików, a także trochę go zreformowałem. Ten kod jest mój, ale rozumiesz.

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
Dzięki za udostępnienie kodu. kiedy będziesz miał czas, zaktualizuj kod w moim repozytorium gihub i nie zapomnij skomentować każdej metody, aby każdy mógł łatwo zrozumieć kod.
— SSR,

1
Sklonowałem repozytoria, czy dasz mi pozwolenie na wykonanie PR?
— Chris Habgood,

Tak, oczywiście. Jaka jest twoja nazwa użytkownika na githubie
— SSR,

Czy miałeś okazję dać mi dostęp?
— Chris Habgood,

2

Oto mój drugi refaktor do modelu:

  1. Przenieś metody prywatne do modelu.
  2. Zastąp @motherboard własnym.

Kontroler:

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

W modelu z płytą główną:

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

Podczas korzystania z asocjacji @post.post_attachmentsnie musisz ustawiać pliku post_id.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.