Rails 4 caricamento di più immagini o file utilizzando carrierwave


86

Come posso caricare più immagini da una finestra di selezione file usando Rails 4 e CarrierWave? Ho un modello post_controllere post_attachments. Come posso fare questo?

Qualcuno può fornire un esempio? C'è un approccio semplice a questo?

Risposte:


195

Questa è la soluzione per caricare più immagini utilizzando carrierwave in rails 4 da zero

Oppure puoi trovare una demo funzionante: Multiple Attachment Rails 4

Per fare basta seguire questi passaggi.

rails new multiple_image_upload_carrierwave

Nel file gem

gem 'carrierwave'
bundle install
rails generate uploader Avatar 

Crea scaffold post

rails generate scaffold post title:string

Crea scaffold post_attachment

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

Per modificare un allegato e un elenco di allegati per qualsiasi post. In 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 %>

Aggiorna modulo per modificare un allegato views / 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 %>

Modifica il metodo di aggiornamento 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 non è necessario definire parametri forti e poiché è possibile definire attribute_accessible sia nel modello che accept_nested_attribute per post model perché l'attributo accessibile è deprecato in rails 4.

Per modificare un allegato non possiamo modificare tutti gli allegati contemporaneamente. quindi sostituiremo gli allegati uno per uno, oppure puoi modificare secondo la tua regola, qui ti mostro solo come aggiornare qualsiasi allegato.


2
nell'azione di visualizzazione del controller post penso che tu abbia dimenticato @post = Post.find (params [: id])
wael

1
@SSR Perché stai scorrendo gli allegati di ogni post in createazione? Rails e carrierwave sono abbastanza intelligenti da salvare automaticamente le raccolte.
falco

3
Mi piacerebbe vedere la modifica (in particolare la gestione della :_destroyparte)
Tun

5
@SSR - La tua risposta è molto utile. Potresti aggiornare la tua risposta anche con l'azione di modifica.
raj_on_rails

2
Quando aggiungo convalide al modello post_attachment, non impediscono il salvataggio del modello post. Invece il post viene salvato e quindi l'errore non valido di ActiveRecord viene generato solo per il modello di allegato. Penso che questo sia dovuto alla creazione! metodo. ma l'utilizzo di create invece fallisce silenziosamente. Qualche idea su come fare in modo che la convalida avvenga sul post per raggiungere gli allegati?
Scacchi

32

Se diamo un'occhiata alla documentazione di CarrierWave, in realtà è molto facile ora.

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

Userò Product come modello a cui voglio aggiungere le immagini, come esempio.

  1. Ottieni il ramo principale Carrierwave e aggiungilo al tuo Gemfile:

    gem 'carrierwave', github:'carrierwaveuploader/carrierwave'
    
  2. Crea una colonna nel modello previsto per ospitare un array di immagini:

    rails generate migration AddPicturesToProducts pictures:json
    
  3. Esegui la migrazione

    bundle exec rake db:migrate
    
  4. Aggiungi immagini al modello di prodotto

    app/models/product.rb
    
    class Product < ActiveRecord::Base
      validates :name, presence: true
      mount_uploaders :pictures, PictureUploader
    end
    
  5. Aggiungi immagini a parametri forti in ProductsController

    app/controllers/products_controller.rb
    
    def product_params
      params.require(:product).permit(:name, pictures: [])
    end
    
  6. Consenti al modulo di accettare più immagini

    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. Nelle tue visualizzazioni, puoi fare riferimento alle immagini analizzando l'array di immagini:

    @product.pictures[1].url
    

Se scegli più immagini da una cartella, l'ordine sarà esattamente l'ordine in cui le stai prendendo dall'alto verso il basso.


9
La soluzione di CarrierWave a questo problema mi fa rabbrividire. Si tratta di mettere tutti i riferimenti ai file in un campo in un array! Certamente non sarebbe considerata la "via dei binari". E se volessi rimuoverne alcuni o aggiungere file extra al post? Non sto dicendo che non sarebbe possibile, sto solo dicendo che sarebbe brutto. Una tabella di join è un'idea molto migliore.
Toby 1 Kenobi

3
Non potrei essere più d'accordo Toby. Saresti così gentile da fornire quella soluzione?
drjorgepolanco

2
Quella soluzione è già fornita da SSR. Un altro modello viene messo in atto per contenere il file caricato, quindi la cosa che richiede molti file caricati si riferisce in una relazione uno-a-molti o molti-a-molti con quell'altro modello. (la tabella di join che ho menzionato nel mio commento precedente sarebbe nel caso di una relazione molti-a-molti)
Toby 1 Kenobi

Grazie @ Toby1Kenobi, mi chiedevo come il metodo dell'array di colonne avrebbe tenuto conto delle versioni delle immagini (non vedo come può). La tua strategia è fattibile.
chaostheory

Ho implementato questa funzione di Carrierwave con Rails 5.xx, github.com/carrierwaveuploader/carrierwave/blob/master/… Ma non sono in grado di eseguirlo con successo e sta generando errori, UndefinedConversionError ("\x89" from ASCII-8BIT to UTF-8) per la soluzione SSR, funziona bene con Rails 4.xx, ma sto affrontando delle sfide (con Rails 5.xx) cioè la sua memorizzazione ActionDispatch::Http::UploadedFilenel database invece del nome del file. Inoltre, non memorizza i file nelle cartelle pubbliche per un determinato percorso nell'uploader.
Mansi Shah

8

Alcune piccole aggiunte alla risposta SSR :

accetta_nested_attributes_for non richiede di modificare il controller dell'oggetto padre. Quindi, se correggere

name: "post_attachments[avatar][]"

per

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

quindi tutte queste modifiche al controller come queste diventano ridondanti:

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

Inoltre dovresti aggiungere PostAttachment.newal modulo dell'oggetto genitore:

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

Ciò renderebbe ridondante questa modifica nel controller del genitore:

@post_attachment = @post.post_attachments.build

Per maggiori informazioni vedi Rails fields_for form non visualizzato, modulo annidato

Se usi Rails 5, cambia il Rails.application.config.active_record.belongs_to_required_by_defaultvalore da truea false(in config / initializers / new_framework_defaults.rb) a causa di un bug all'interno di accept_nested_attributes_for (altrimenti accept_nested_attributes_for generalmente non funzionerà sotto Rails 5).

MODIFICA 1:

Per aggiungere su distruggi :

In models / post.rb

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

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

In questo modo semplicemente non è necessario disporre del controller di un oggetto figlio! Voglio dire, non ce PostAttachmentsControllern'è più bisogno. Per quanto riguarda il controller ( PostController) dell'oggetto genitore , quasi non lo cambi - l'unica cosa che cambi è l'elenco dei parametri nella whitelist (per includere i parametri relativi all'oggetto figlio) in questo modo:

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

Ecco perché accepts_nested_attributes_forè così sorprendente.


Quelle sono in realtà importanti aggiunte alla risposta @SSR, non minori :) accept_nested_attributes_for è piuttosto qualcosa. In effetti non c'è affatto bisogno di un controller di bambino. Seguendo il tuo approccio, l'unica cosa che non posso fare è visualizzare messaggi di errore del modulo per il bambino quando qualcosa va storto con il caricamento.
Luis Fernando Alen

Grazie per il tuo contributo. Ho fatto funzionare il caricamento, ma mi chiedo come posso aggiungere attributi aggiuntivi al campo del modulo post_attachments in views / posts / _form.html.erb? <%= d.text_field :copyright, name: "album[diapos_attributes][][copyright]", class: 'form-field' %>scrive il copyright solo sull'ultimo record e non su tutti.
SEJU

6

Inoltre ho capito come aggiornare il caricamento di più file e l'ho anche rifattorizzato un po '. Questo codice è mio ma ottieni la deriva.

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
Grazie per aver condiviso il tuo codice. quando hai tempo, aggiorna il codice nel mio repository gihub e non dimenticare di commentare ogni metodo in modo che tutti possano facilmente capire il codice.
SSR

1
Ho clonato i repository, mi dai il permesso di fare un PR?
Chris Habgood

Si certo. Qual è il tuo nome utente GitHub
SSR

Hai avuto la possibilità di concedermi l'accesso?
Chris Habgood

2

Ecco il mio secondo refactoring nel modello:

  1. Sposta i metodi privati ​​nel modello.
  2. Sostituisci @motherboard con self.

Controller:

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

Nel modello di scheda madre:

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

Quando si utilizza l'associazione @post.post_attachmentsnon è necessario impostare l'estensione post_id.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.