Come posso caricare più immagini da una finestra di selezione file usando Rails 4 e CarrierWave? Ho un modello post_controller
e post_attachments
. Come posso fare questo?
Qualcuno può fornire un esempio? C'è un approccio semplice a questo?
Risposte:
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.
create
azione? Rails e carrierwave sono abbastanza intelligenti da salvare automaticamente le raccolte.
:_destroy
parte)
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.
Ottieni il ramo principale Carrierwave e aggiungilo al tuo Gemfile:
gem 'carrierwave', github:'carrierwaveuploader/carrierwave'
Crea una colonna nel modello previsto per ospitare un array di immagini:
rails generate migration AddPicturesToProducts pictures:json
Esegui la migrazione
bundle exec rake db:migrate
Aggiungi immagini al modello di prodotto
app/models/product.rb
class Product < ActiveRecord::Base
validates :name, presence: true
mount_uploaders :pictures, PictureUploader
end
Aggiungi immagini a parametri forti in ProductsController
app/controllers/products_controller.rb
def product_params
params.require(:product).permit(:name, pictures: [])
end
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 %>
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.
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::UploadedFile
nel database invece del nome del file. Inoltre, non memorizza i file nelle cartelle pubbliche per un determinato percorso nell'uploader.
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.new
al 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_default
valore da true
a 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 PostAttachmentsController
n'è 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.
<%= d.text_field :copyright, name: "album[diapos_attributes][][copyright]", class: 'form-field' %>
scrive il copyright solo sull'ultimo record e non su tutti.
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
Ecco il mio secondo refactoring nel modello:
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
Quando si utilizza l'associazione @post.post_attachments
non è necessario impostare l'estensione post_id
.