Rails 4 téléchargement d'images ou de fichiers multiples à l'aide de Carrierwave

86

Comment puis-je télécharger plusieurs images à partir d'une fenêtre de sélection de fichiers à l'aide de Rails 4 et CarrierWave? J'ai un modèle post_controlleret post_attachments. Comment puis-je faire ceci?

Quelqu'un peut-il donner un exemple? Y a-t-il une approche simple à cela?

SSR
la source

Réponses:

195

C'est une solution pour télécharger plusieurs images à l'aide de carrierwave dans les rails 4 à partir de zéro

Ou vous pouvez trouver une démo de travail: Rails de fixation multiples 4

Pour ce faire, suivez simplement ces étapes.

rails new multiple_image_upload_carrierwave

Dans le fichier gem

gem 'carrierwave'
bundle install
rails generate uploader Avatar 

Créer un échafaudage de poste

rails generate scaffold post title:string

Créer un échafaudage post_attachment

rails generate scaffold post_attachment post_id:integer avatar:string

rake db:migrate

Dans post.rb

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

Dans post_attachment.rb

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

Dans 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

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

Pour modifier une pièce jointe et une liste de pièces jointes pour n'importe quel message. Dans 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 %>

Mettre à jour le formulaire pour modifier une pièce jointe 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 %>

Modifier la méthode de mise à jour dans 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

Dans les rails 3, il n'est pas nécessaire de définir des paramètres forts et comme vous pouvez définir attribute_accessible à la fois dans le modèle et accept_nested_attribute pour poster le modèle car l'attribut accessible est déconseillé dans les rails 4.

Pour modifier une pièce jointe, nous ne pouvons pas modifier toutes les pièces jointes à la fois. nous allons donc remplacer la pièce jointe une par une, ou vous pouvez la modifier selon votre règle, ici je vous montre simplement comment mettre à jour une pièce jointe.

SSR
la source
2
dans l'action de spectacle du contrôleur de poste, je pense que vous avez oublié @post = Post.find (params [: id])
wael
1
@SSR Pourquoi parcourez-vous chaque pièce jointe en createaction? Les rails et carrierwave sont suffisamment intelligents pour enregistrer automatiquement les collections.
hawk le
3
:_destroy
Tun
5
@SSR - Votre réponse est très utile. Pourriez-vous également mettre à jour votre réponse avec une action de modification.
raj_on_rails
2
Lorsque j'ajoute des validations au modèle post_attachment, elles n'empêchent pas le modèle de publication de s'enregistrer. Au lieu de cela, la publication est enregistrée, puis l'erreur non valide ActiveRecord est générée pour le modèle de pièce jointe uniquement. Je pense que c'est à cause de la création! méthode. mais l'utilisation de create à la place échoue silencieusement. Avez-vous une idée de la façon dont la validation se produira sur la publication dans les pièces jointes?
dchess le
32

Si nous jetons un œil à la documentation de CarrierWave, c'est en fait très facile maintenant.

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

J'utiliserai Product comme modèle que je souhaite ajouter les images, à titre d'exemple.

  1. Obtenez la branche principale Carrierwave et ajoutez-la à votre Gemfile:

    gem 'carrierwave', github:'carrierwaveuploader/carrierwave'
    
  2. Créez une colonne dans le modèle prévu pour héberger un tableau d'images:

    rails generate migration AddPicturesToProducts pictures:json
    
  3. Exécutez la migration

    bundle exec rake db:migrate
    
  4. Ajouter des images au modèle de produit

    app/models/product.rb
    
    class Product < ActiveRecord::Base
      validates :name, presence: true
      mount_uploaders :pictures, PictureUploader
    end
    
  5. Ajouter des images aux paramètres forts dans ProductsController

    app/controllers/products_controller.rb
    
    def product_params
      params.require(:product).permit(:name, pictures: [])
    end
    
  6. Autorisez votre formulaire à accepter plusieurs photos

    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. Dans vos vues, vous pouvez référencer les images analysant le tableau d'images:

    @product.pictures[1].url
    

Si vous choisissez plusieurs images dans un dossier, l'ordre sera l'ordre exact dans lequel vous les prenez de haut en bas.

drjorgepolanco
la source
9
La solution de CarrierWave à ce problème me fait grincer des dents. Il s'agit de mettre toutes les références aux fichiers dans un seul champ dans un tableau! Cela ne serait certainement pas considéré comme la «voie des rails». Que faire si vous souhaitez ensuite en supprimer ou ajouter des fichiers supplémentaires à la publication? Je ne dis pas que ce ne serait pas possible, je dis juste que ce serait moche. Une table de jointure est une bien meilleure idée.
Toby 1 Kenobi
3
Je ne pourrais pas être plus d'accord avec Toby. Seriez-vous si gentil de fournir cette solution?
drjorgepolanco
2
Cette solution est déjà fournie par SSR. Un autre modèle est mis en place pour contenir le fichier téléchargé, puis la chose qui a besoin de nombreux fichiers téléchargés se rapporte dans une relation un-à-plusieurs ou plusieurs-à-plusieurs avec cet autre modèle. (la table de jointure que j'ai mentionnée dans mon commentaire précédent serait dans le cas d'une relation plusieurs-à-plusieurs)
Toby 1 Kenobi
Merci @ Toby1Kenobi, je me demandais comment la méthode de tableau de colonnes tiendrait compte des versions d'image (je ne vois pas comment elle le peut). Votre stratégie est faisable.
chaostheory
J'ai implémenté cette fonctionnalité de Carrierwave avec Rails 5.xx, github.com/carrierwaveuploader/carrierwave/blob/master/... Mais je ne suis pas en mesure de l'exécuter avec succès, et cela génère une erreur, UndefinedConversionError ("\x89" from ASCII-8BIT to UTF-8) pour la solution SSR, cela fonctionne bien avec Rails 4.xx, mais je suis confronté à des défis (avec Rails 5.xx) c'est à dire son stockage ActionDispatch::Http::UploadedFiledans la base de données au lieu du nom de fichier. Il ne stocke pas non plus les fichiers dans les dossiers publics pour un chemin donné dans le programme de téléchargement.
Mansi Shah
8

Quelques ajouts mineurs à la réponse SSR :

accepte_nested_attributes_for ne vous oblige pas à changer le contrôleur de l'objet parent. Alors si pour corriger

name: "post_attachments[avatar][]"

à

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

alors tous ces changements de contrôleur comme ceux-ci deviennent redondants:

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

Vous devez également ajouter PostAttachment.newau formulaire d'objet parent:

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

Cela rendrait redondant ce changement dans le contrôleur du parent:

@post_attachment = @post.post_attachments.build

Pour plus d'informations, consultez les champs Rails_for formulaire non affiché, formulaire imbriqué

Si vous utilisez Rails 5, changez la Rails.application.config.active_record.belongs_to_required_by_defaultvaleur detrue à false(dans config / initializers / new_framework_defaults.rb) en raison d'un bogue dans accepte_nested_attributes_for (sinon, accepte_nested_attributes_for ne fonctionnera généralement pas sous Rails 5).

MODIFIER 1:

Pour ajouter à propos de détruire :

Dans models / post.rb

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

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

De cette façon, vous n'avez tout simplement pas besoin d'avoir le contrôleur d'un objet enfant! Je veux dire qu'aucun PostAttachmentsControllern'est plus nécessaire. En ce qui concerne le contrôleur de l' objet parent ( PostController), vous aussi presque ne le changez pas non plus - la seule chose que vous changez ici est la liste des paramètres de la liste blanche (pour inclure les paramètres liés à l'objet enfant) comme ceci:

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

C'est pourquoi le accepts_nested_attributes_forest si incroyable.

prograils
la source
Ce sont en fait des ajouts majeurs à la réponse @SSR, pas mineurs :) accept_nested_attributes_for est quelque chose. En effet, il n'y a pas du tout besoin d'un contrôleur enfant. En suivant votre approche, la seule chose que je ne peux pas faire est d'afficher des messages d'erreur de formulaire pour l'enfant lorsque quelque chose ne va pas avec le téléchargement.
Luis Fernando Alen
Merci pour votre contribution. J'ai fait fonctionner le téléchargement, mais je me demande comment je pourrais ajouter des attributs supplémentaires au champ de formulaire post_attachments dans views / posts / _form.html.erb? <%= d.text_field :copyright, name: "album[diapos_attributes][][copyright]", class: 'form-field' %>écrit les droits d'auteur uniquement sur le dernier enregistrement et non sur tous.
SEJU
6

J'ai également compris comment mettre à jour le téléchargement de plusieurs fichiers et je l'ai également refactoré un peu. Ce code est le mien mais vous obtenez la dérive.

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
la source
1
Merci d'avoir partagé votre code. lorsque vous avez le temps, veuillez mettre à jour le code de mon dépôt gihub et n'oubliez pas de commenter chaque méthode afin que tout le monde puisse facilement comprendre le code.
SSR
1
J'ai cloné les dépôts, me donnerez-vous la permission de faire un PR?
Chris Habgood
Oui bien sûr. Quel est votre nom d'utilisateur github
SSR
Avez-vous eu l'occasion de me donner accès?
Chris Habgood du
2

Voici mon deuxième refactor dans le modèle:

  1. Déplacez les méthodes privées vers le modèle.
  2. Remplacez @motherboard par self.

Manette:

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

Dans le modèle de carte mère:

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
la source
2

Lorsque vous utilisez l'association, @post.post_attachmentsvous n'avez pas besoin de définir le post_id.

Chris Habgood
la source