Rails 4 Hochladen mehrerer Bilder oder Dateien mit Carrierwave

86

Wie kann ich mit Rails 4 und CarrierWave mehrere Bilder aus einem Dateiauswahlfenster hochladen? Ich habe einpost_controller undpost_attachments Modell. Wie kann ich das machen?

Kann jemand ein Beispiel geben? Gibt es einen einfachen Ansatz dafür?

SSR
quelle

Antworten:

195

Dies ist eine Lösung, um mehrere Bilder mit Carrierwave in Rails 4 von Grund auf hochzuladen

Oder Sie finden eine funktionierende Demo: Multiple Attachment Rails 4

Befolgen Sie dazu einfach diese Schritte.

rails new multiple_image_upload_carrierwave

In Edelsteindatei

gem 'carrierwave'
bundle install
rails generate uploader Avatar 

Postgerüst erstellen

rails generate scaffold post title:string

Erstellen Sie ein post_attachment-Gerüst

rails generate scaffold post_attachment post_id:integer avatar:string

rake db:migrate

In post.rb.

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

In post_attachment.rb

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

In 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

In Ansichten / Beiträge / _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 %>

So bearbeiten Sie einen Anhang und eine Liste der Anhänge für einen Beitrag. In Ansichten / Beiträge / 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 %>

Aktualisieren Sie das Formular, um die Ansichten eines Anhangs / post_attachments / _form.html.erb zu bearbeiten

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

Ändern Sie die Aktualisierungsmethode in 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

In Rails 3 müssen keine starken Parameter definiert werden, und da Sie attribute_accessible sowohl im Modell als auch accept_nested_attribute für das Post-Modell definieren können, ist das Attribut access in Rails 4 veraltet.

Zum Bearbeiten eines Anhangs können nicht alle Anhänge gleichzeitig geändert werden. Daher ersetzen wir den Anhang nacheinander, oder Sie können ihn gemäß Ihrer Regel ändern. Hier zeige ich Ihnen nur, wie Sie einen Anhang aktualisieren.

SSR
quelle
2
in der Show-Aktion des Post-Controllers denke ich, dass Sie @post = Post.find (params [: id])
vergessen haben
1
@SSR Warum durchlaufen Sie die einzelnen Post-Anhänge in createAktion? Schienen und Trägerwellen sind intelligent genug, um Sammlungen automatisch zu speichern.
Falke
3
Würde gerne sehen, wie bearbeiten (insbesondere Handling :_destroyTeil)
Tun
5
@SSR - Ihre Antwort ist sehr hilfreich. Könnten Sie bitte Ihre Antwort auch mit einer Bearbeitungsaktion aktualisieren?
Raj_on_rails
2
Wenn ich dem post_attachment-Modell Validierungen hinzufüge, verhindern sie nicht, dass das post-Modell gespeichert wird. Stattdessen wird der Beitrag gespeichert und der ungültige ActiveRecord-Fehler wird nur für das Anhangsmodell ausgegeben. Ich denke das liegt an der Schaffung! Methode. Die Verwendung von create schlägt jedoch nur im Hintergrund fehl. Irgendeine Idee, wie die Validierung auf der Post in die Anhänge gelangen soll?
Dschess
32

Wenn wir uns die Dokumentation von CarrierWave ansehen, ist dies jetzt tatsächlich sehr einfach.

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

Ich werde Product als Modell verwenden, um die Bilder als Beispiel hinzuzufügen.

  1. Holen Sie sich die Hauptniederlassung Carrierwave und fügen Sie sie Ihrer Gemfile hinzu:

    gem 'carrierwave', github:'carrierwaveuploader/carrierwave'
  2. Erstellen Sie eine Spalte im beabsichtigten Modell, um ein Array von Bildern zu hosten:

    rails generate migration AddPicturesToProducts pictures:json
  3. Führen Sie die Migration aus

    bundle exec rake db:migrate
  4. Fügen Sie Bilder zum Modellprodukt hinzu

    app/models/product.rb
    
    class Product < ActiveRecord::Base
      validates :name, presence: true
      mount_uploaders :pictures, PictureUploader
    end
  5. Fügen Sie Bilder zu starken Parametern in ProductsController hinzu

    app/controllers/products_controller.rb
    
    def product_params
      params.require(:product).permit(:name, pictures: [])
    end
  6. Lassen Sie Ihr Formular mehrere Bilder akzeptieren

    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. In Ihren Ansichten können Sie auf die Bilder verweisen, die das Bildarray analysieren:

    @product.pictures[1].url

Wenn Sie mehrere Bilder aus einem Ordner auswählen, entspricht die Reihenfolge genau der Reihenfolge, in der Sie sie von oben nach unten aufnehmen.

drjorgepolanco
quelle
9
Die Lösung von CarrierWave für dieses Problem lässt mich zusammenzucken. Dabei werden alle Verweise auf die Dateien in einem Feld eines Arrays abgelegt! Es wäre sicherlich nicht der "Schienenweg". Was ist, wenn Sie dann einige entfernen oder zusätzliche Dateien zum Beitrag hinzufügen möchten? Ich sage nicht, dass es nicht möglich wäre, ich sage nur, dass es hässlich wäre. Eine Join-Tabelle ist eine viel bessere Idee.
Toby 1 Kenobi
3
Ich könnte nicht mehr Toby zustimmen. Wären Sie so freundlich, diese Lösung anzubieten?
Drjorgepolanco
2
Diese Lösung wird bereits von SSR bereitgestellt. Ein anderes Modell wird eingerichtet, um die hochgeladene Datei zu speichern. Dann hängt das, was viele hochgeladene Dateien benötigt, in einer Eins-zu-Viele- oder Viele-zu-Viele-Beziehung zu diesem anderen Modell. (Die Join-Tabelle, die ich in meinem früheren Kommentar erwähnt habe, wäre im Fall einer Viele-zu-Viele-Beziehung)
Toby 1 Kenobi
Danke @ Toby1Kenobi, ich habe mich gefragt, wie die Spaltenarray-Methode Bildversionen berücksichtigen würde (ich sehe nicht, wie es geht). Ihre Strategie ist machbar.
Chaostheorie
Ich habe diese Funktion von Carrierwave mit Rails 5.xx implementiert, github.com/carrierwaveuploader/carrierwave/blob/master/…. Ich kann sie jedoch nicht erfolgreich ausführen und es wird ein Fehler generiert. UndefinedConversionError ("\x89" from ASCII-8BIT to UTF-8) Bei SSR-Lösungen funktioniert sie einwandfrei Rails 4.xx, aber ich stehe vor Herausforderungen (mit Rails 5.xx), dh das Speichern ActionDispatch::Http::UploadedFilein der Datenbank anstelle des Dateinamens. Es werden auch keine Dateien in öffentlichen Ordnern für einen bestimmten Pfad im Uploader gespeichert.
Mansi Shah
8

Einige kleinere Ergänzungen zur SSR- Antwort:

accepts_nested_attributes_for erfordert nicht, dass Sie den Controller des übergeordneten Objekts ändern. Also wenn zu korrigieren

name: "post_attachments[avatar][]"

zu

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

dann werden alle diese Controller-Änderungen wie diese überflüssig:

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

Außerdem sollten Sie PostAttachment.newdem übergeordneten Objektformular Folgendes hinzufügen :

In Ansichten / Beiträge / _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 %>

Dies würde diese Änderung im Controller des übergeordneten Controllers überflüssig machen:

@post_attachment = @post.post_attachments.build

Weitere Informationen finden Sie unter Rails fields_for-Formular nicht angezeigt, verschachteltes Formular

Wenn Sie Rails 5 verwenden, ändern Sie den Rails.application.config.active_record.belongs_to_required_by_defaultWert von truein false(in config / initializers / new_framework_defaults.rb) aufgrund eines Fehlers in accepts_nested_attributes_for (andernfalls funktioniert accept_nested_attributes_for unter Rails 5 im Allgemeinen nicht).

EDIT 1:

Um über zerstören hinzuzufügen :

In models / post.rb.

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

In Ansichten / Beiträge / _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 %>

Auf diese Weise benötigen Sie einfach überhaupt keinen Controller für ein untergeordnetes Objekt! Ich meine, es wird nichts mehr PostAttachmentsControllerbenötigt. Der Controller ( PostController) des übergeordneten Objekts wird ebenfalls fast nicht geändert. Das einzige, was Sie dort ändern, ist die Liste der Parameter auf der Whitelist (einschließlich der untergeordneten objektbezogenen Parameter) wie folgt:

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

Deshalb accepts_nested_attributes_forist das so erstaunlich.

Prograils
quelle
Das sind eigentlich wichtige Ergänzungen zur @ SSR-Antwort, keine kleinen :) accept_nested_attributes_for ist schon etwas. In der Tat ist überhaupt kein untergeordneter Controller erforderlich. Wenn ich Ihrem Ansatz folge, kann ich nur Formularfehlermeldungen für das Kind anzeigen, wenn beim Hochladen ein Fehler auftritt.
Luis Fernando Alen
Danke für deinen Beitrag. Ich habe den Upload zum Laufen gebracht, frage mich aber, wie ich dem Formularfeld post_attachments in views / posts / _form.html.erb zusätzliche Attribute hinzufügen kann. <%= d.text_field :copyright, name: "album[diapos_attributes][][copyright]", class: 'form-field' %>schreibt das Copyright nur auf den letzten Datensatz und nicht auf alle.
SEJU
6

Außerdem habe ich herausgefunden, wie der Upload mehrerer Dateien aktualisiert werden kann, und ich habe ihn auch ein wenig überarbeitet. Dieser Code gehört mir, aber Sie bekommen die Drift.

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
quelle
1
Vielen Dank, dass Sie Ihren Code geteilt haben. Wenn Sie Zeit haben, aktualisieren Sie bitte den Code in meinem Gihub-Repo und vergessen Sie nicht, für jede Methode einen Kommentar abzugeben, damit jeder den Code leicht verstehen kann.
SSR
1
Ich habe die Repos geklont. Wirst du mir die Erlaubnis geben, PR zu machen?
Chris Habgood
Ja sicher. Was ist Ihr Github-Benutzername
SSR
Haben Sie die Gelegenheit gehabt, mir Zugang zu gewähren?
Chris Habgood
2

Hier ist mein zweiter Refaktor im Modell:

  1. Verschieben Sie private Methoden zum Modellieren.
  2. Ersetzen Sie @motherboard durch self.

Regler:

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

Im Motherboard-Modell:

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
quelle
2

Wenn Sie die Zuordnung verwenden, müssen @post.post_attachmentsSie die nicht festlegen post_id.

Chris Habgood
quelle