Carga de múltiples imágenes o archivos de Rails 4 usando carrierwave

86

¿Cómo puedo cargar varias imágenes desde una ventana de selección de archivos usando Rails 4 y CarrierWave? tengo unpost_controllerpost_attachments modelo y . ¿Cómo puedo hacer esto?

¿Alguien puede dar un ejemplo? ¿Existe un enfoque simple para esto?

SSR
fuente

Respuestas:

195

Esta es una solución para cargar múltiples imágenes usando carrierwave en rieles 4 desde cero

O puede encontrar una demostración funcional: Multiple Attachment Rails 4

Para hacerlo, siga estos pasos.

rails new multiple_image_upload_carrierwave

En archivo de gemas

gem 'carrierwave'
bundle install
rails generate uploader Avatar 

Crear andamio de publicaciones

rails generate scaffold post title:string

Crear andamio post_attachment

rails generate scaffold post_attachment post_id:integer avatar:string

rake db:migrate

En post.rb

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

En post_attachment.rb

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

En 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

En vistas / publicaciones / _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 %>

Para editar un archivo adjunto y una lista de archivos adjuntos para cualquier publicación. En vistas / 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 %>

Actualizar formulario para editar un archivo adjunto 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 %>

Modificar el método de actualización en 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

En rieles 3 no es necesario definir parámetros sólidos y, como puede definir atributo_accessible tanto en el modelo como accept_nested_attribute para publicar modelo, porque el atributo accesible está obsoleto en rieles 4.

Para editar un adjunto, no podemos modificar todos los adjuntos a la vez. así que reemplazaremos el archivo adjunto uno por uno, o puede modificarlo según su regla. Aquí solo le muestro cómo actualizar cualquier archivo adjunto.

SSR
fuente
2
en la acción de mostrar del controlador de la publicación, creo que te has olvidado @post = Post.find (params [: id])
wael
1
@SSR ¿Por qué recorre los archivos adjuntos de cada publicación en createacción? Rails y carrierwave son lo suficientemente inteligentes como para guardar colecciones automáticamente.
halcón
3
Me encantaría ver la edición (especialmente la :_destroyparte de manejo )
Tun
5
@SSR: su respuesta es muy útil. ¿Podría actualizar su respuesta con la acción de edición también?
raj_on_rails
2
Cuando agrego validaciones al modelo post_attachment, no impiden que se guarde el modelo de publicación. En su lugar, la publicación se guarda y, a continuación, se lanza el error ActiveRecord no válido solo para el modelo de adjunto. ¡Creo que esto se debe a la creación! método. pero usar create en su lugar simplemente falla silenciosamente. ¿Alguna idea de cómo hacer que la validación ocurra en la publicación en los archivos adjuntos?
dchess
32

Si echamos un vistazo a la documentación de CarrierWave, esto es realmente muy fácil ahora.

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

Usaré Producto como modelo. Quiero agregar las imágenes, como ejemplo.

  1. Obtenga la rama principal Carrierwave y agréguela a su Gemfile:

    gem 'carrierwave', github:'carrierwaveuploader/carrierwave'
    
  2. Cree una columna en el modelo previsto para alojar una matriz de imágenes:

    rails generate migration AddPicturesToProducts pictures:json
    
  3. Ejecuta la migración

    bundle exec rake db:migrate
    
  4. Agregar imágenes al producto modelo

    app/models/product.rb
    
    class Product < ActiveRecord::Base
      validates :name, presence: true
      mount_uploaders :pictures, PictureUploader
    end
    
  5. Agregue imágenes a parámetros fuertes en ProductsController

    app/controllers/products_controller.rb
    
    def product_params
      params.require(:product).permit(:name, pictures: [])
    end
    
  6. Permita que su formulario acepte varias imágenes

    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. En sus vistas, puede hacer referencia a las imágenes analizando la matriz de imágenes:

    @product.pictures[1].url
    

Si elige varias imágenes de una carpeta, el orden será el orden exacto en el que las está tomando de arriba a abajo.

drjorgepolanco
fuente
9
La solución de CarrierWave a este problema me hace estremecer. ¡Implica poner todas las referencias a los archivos en un campo en una matriz! Ciertamente no se consideraría el "camino de los rieles". ¿Qué sucede si luego desea eliminar algunos o agregar archivos adicionales a la publicación? No digo que no sea posible, solo digo que sería feo. Una tabla de combinación es una idea mucho mejor.
Toby 1 Kenobi
3
No podría estar más de acuerdo Toby. ¿Sería tan amable de proporcionar esa solución?
drjorgepolanco
2
Estas soluciones ya las proporciona SSR. Se coloca otro modelo para contener el archivo cargado, luego lo que necesita muchos archivos cargados se relaciona en una relación de uno a muchos o de muchos a muchos con ese otro modelo. (la tabla de unión que mencioné en mi comentario anterior sería en el caso de una relación de muchos a muchos)
Toby 1 Kenobi
Gracias @ Toby1Kenobi, me preguntaba cómo el método de matriz de columnas daría cuenta de las versiones de imagen (no veo cómo puede hacerlo). Tu estrategia es factible.
caosteoría
He implementado esta función de Carrierwave con Rails 5.xx, github.com/carrierwaveuploader/carrierwave/blob/master/… Pero no puedo ejecutarlo correctamente y está generando un error. UndefinedConversionError ("\x89" from ASCII-8BIT to UTF-8) Para la solución SSR, funciona bien con Rails 4.xx, pero estoy enfrentando desafíos (con Rails 5.xx), es decir, su almacenamiento ActionDispatch::Http::UploadedFileen la base de datos en lugar del nombre del archivo. Tampoco almacena archivos en carpetas públicas para una ruta determinada en el cargador.
Mansi Shah
8

Algunas adiciones menores a la respuesta SSR :

accept_nested_attributes_for no requiere que cambie el controlador del objeto principal. Entonces, si corregir

name: "post_attachments[avatar][]"

a

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

entonces todos estos cambios de controlador como estos se vuelven redundantes:

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

También debe agregar PostAttachment.newal formulario del objeto principal:

En vistas / publicaciones / _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 %>

Esto haría redundante este cambio en el controlador del padre:

@post_attachment = @post.post_attachments.build

Para obtener más información, consulte Campos de rieles_para que el formulario no aparezca, formulario anidado

Si usa Rails 5, cambie el Rails.application.config.active_record.belongs_to_required_by_defaultvalor de truea false(en config / initializers / new_framework_defaults.rb) debido a un error dentro de accept_nested_attributes_for (de lo contrario, accept_nested_attributes_for generalmente no funcionará en Rails 5).

EDITAR 1:

Para agregar sobre destruir :

En modelos / post.rb

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

En vistas / publicaciones / _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 esta manera, ¡simplemente no necesita tener un controlador de objeto secundario en absoluto! Quiero decir que ya no PostAttachmentsControllerse necesita ninguno . En cuanto al controlador del objeto principal ( PostController), casi no lo cambia; lo único que cambia es la lista de los parámetros de la lista blanca (para incluir los parámetros relacionados con el objeto secundario) de esta manera:

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

Por eso accepts_nested_attributes_fores tan asombroso.

prograils
fuente
Esas son en realidad adiciones importantes a la respuesta de @SSR, no menores :) accept_nested_attributes_for es bastante. De hecho, no hay necesidad de un controlador infantil. Siguiendo su enfoque, lo único que no puedo hacer es mostrar mensajes de error de formulario para el niño cuando algo sale mal con la carga.
Luis Fernando Alen
Gracias por tu contribución. Conseguí que la carga funcionara, pero me pregunto cómo podría agregar atributos adicionales al campo del formulario post_attachments en views / posts / _form.html.erb. <%= d.text_field :copyright, name: "album[diapos_attributes][][copyright]", class: 'form-field' %>escribe los derechos de autor solo para el último registro y no para todos.
SEJU
6

También descubrí cómo actualizar la carga de varios archivos y también lo refactoricé un poco. Este código es mío, pero entiendes 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
Chris Habgood
fuente
1
Gracias por compartir tu código. cuando tenga tiempo, actualice el código en mi repositorio gihub y no olvide comentar cada método para que todos puedan entender el código fácilmente.
SSR
1
Cloné los repositorios, ¿me darán permiso para hacer un PR?
Chris Habgood
Si seguro. ¿Cuál es su nombre de usuario de github
SSR
¿Ha tenido la oportunidad de darme acceso?
Chris Habgood
2

Aquí está mi segunda refactorización en el modelo:

  1. Mueva los métodos privados al modelo.
  2. Reemplace @motherboard por self.

Controlador:

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

En modelo de placa base:

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

Al utilizar la asociación @post.post_attachments, no es necesario configurar el post_id.

Chris Habgood
fuente