ActiveStorage multiple upload with intermediate model - ruby-on-rails

The goal
I want to upload multiple files. So one Intervention can have multiple Uploads and each Upload has one attached file to it. This way, each Upload can have one file attached with different status, name, visibility, etc. instead of having one Upload with has_many_attached
What I have done
I have one Intervention model that can have many uploads :
class Intervention < ApplicationRecord
has_many :uploads, dependent: :destroy
accepts_nested_attributes_for :uploads, :allow_destroy => true
end
Each uploads has one attached file using ActiveStorage :
class Upload < ApplicationRecord
belongs_to :intervention
has_one_attached :file
end
In my interventions_controller I do :
def new
#intervention = Intervention.new
#intervention.uploads.build
end
def create
#intervention = Intervention.new(intervention_params)
# + default scaffolded controller [...]
end
def intervention_params
params.require(:intervention).permit(:user_id, :comment, uploads_attributes: [:status, :file])
end
In my form I have :
<%= form.fields_for :uploads, Upload.new do |uploads_attributes|%>
<%= uploads_attributes.label :file, "File:" %>
<%= uploads_attributes.file_field :file %>
<%= uploads_attributes.hidden_field :status, value: "raw" %>
<% end %>
Problem
This solution works when I want to upload only one file. But if I want to upload two files I can't figure out. I can add multiple: true to the file_field but how to created multiple Upload with one file each ?
Should I save the uploaded files into a temp variable first, extract them from the intervention_params, then create the Intervention without any Upload, then for each uploaded files saved, build a new Upload for the newly created Intervention ?

I have done what I have proposed. I don't know if there is a more elegant way to do it but anyway, this is working.
In my form I just added a simple files field that can take multiple files
<div class="field">
<%= form.label :files %>
<%= form.file_field :files, multiple: true %>
</div>
I have added this to the permitted parameters :
params.require(:intervention).permit(:user_id, :comment, files:[]])
Then I create my Intervention by ignoring this files params and I use it later to create a new Upload record for each files submitted.
# First create the intervention without the files attached
#intervention = Intervention.new(intervention_params.except(:files))
if #intervention.save
# Then for each files attached, create a separate Upload
intervention_params[:files].each do |f|
upload = #intervention.uploads.new()
upload.file.attach(f)
upload.save
end
end

Related

active storage prevent client reupload file when form error validation

I have a form with many inputs, and one is a kbis, an file input.
My model:
class Company < ApplicationRecord
validates :name, presence: true
validate :presence_kbis
has_one_attached :kbis
private
def presence_kbis
errors.add(:kbis, :blank) unless kbis.attached?
end
end
I created a classic form, and one input is:
<%= f.input :kbis %>
My problem is if an other field contains an error, I lost the file, and I ask again to the user to select again a file.
I understood that for security reasons, the navigator can not select the file.
But the file is already uploaded to the rails server.
My question:
There is a way to use the file already uploded? In CarrierWawe we could do something like this:
<%= f.file_field :avatar %>
<%= f.hidden_field :avatar_cache %>
I tried to do this:
<%= f.hidden_field :kbis, value: f.object.kbis.signed_id if f.object.kbis.attached? %>
<%= f.input :kbis %>
but when I submit I have in exception: ActiveSupport::MessageVerifier::InvalidSignature
I think I have correcty understood how data is stored when succesfully, but I am not clear with differents step. It is store before or after validation? Where is the file is validation failed? It is removed after http request? If not when it is remove?
CONTEXT:
ruby 3.0.1
rails 6.1.3.1
environement: development
disk storage: local
I use simple_form

How to edit multiple attached images using ActiveStorage `has_many_attached` in ActiveAdmin

I have a simple model that can have multiple images attached via ActiveStorage handling the file storage.
I am using ActiveAdmin to edit my model and to upload/attach the images - so far no problems.
The problem is, when I want to edit my model, and add new images, then the previous ones are deleted, and only the new ones added.
I can do a preview of already attached images, and could also delete them separately, but how do I achieve, that by uploading new images, the old ones are NOT deleted?
My model:
class Post < ActiveRecord::Base
has_many_attached :images
end
My ActiveAdmin page:
ActiveAdmin.register AdminPost do
permit_params images:[]
form do |f|
f.input :images, as: :file, input_html: { multiple: true }
if #resource.images.exists?
#resource.images.map do |m|
para image_tag m
end
end
end
end
Assuming you are using rails 6.0+;
you can solve this by adding following code in to your environments (i.e - development.rb )
https://github.com/rails/rails/issues/35817#issuecomment-628654948
config.active_storage.replace_on_assign_to_many = false
in your form,
form do |f|
f.input :images, as: :file, input_html: { multiple: true }
f.object.images.each do |image|
span image_tag(image)
end
end
So I chose the way of adding the new attachments manually, so they don't replace the existing attached images.
I added a field to handle the posted images, and a method to add them in my model.
class Post < ActiveRecord::Base
has_many_attached :images
attr_accessor :new_images
def attach_images
return if new_images.blank?
images.attach(new_images)
self.new_images = []
end
end
And the ActiveAdmin page controller handles the upload, and calls the new method:
ActiveAdmin.register AdminPost do
permit_params new_images:[]
form do |f|
f.input :new_images, as: :file, input_html: { multiple: true }
if #resource.images.exists?
#resource.images.map do |m|
para image_tag m
end
end
end
controller do
after_save :add_images
def add_images(post)
post.attach_images
end
end
end

Cloudinary::CarrierWave how to select the upload folder in Cloudinary

I try to use the module Cloudinary::CarrierWave, but all my images are stored in the root folder of the cloud.
I want all my files to go to a specific remote folder.
I try this in my class:
class PhotoUploader < CarrierWave::Uploader::Base
include Cloudinary::CarrierWave
def storage_identifier
'specific_folder'
end
end
I know there is some options with Cloudinary::Upload.upload, but as I use simple form in ruby on rails, I don't have access to this method.
Does anybody has an idea to fix that ?
When using Carrierwave with server-side uploads, you can use a code like the following:
classPictureUploader < CarrierWave::Uploader::Base
include Cloudinary::CarrierWave
...
def public_id
return "my_folder/" + model.short_name
end
end
If you're using client-side upload, you can set the folder parameter when building the upload tag:
<%= f.cl_image_upload(:image, :folder => "my_folder") %>
thanks to your answer, I solve that with your help, and I can give some other tips.
I did something like this to separate folders, but sometimes model.id doesn't exist (I should add an if else)
def public_id
"artpieces/#{model.class}/#{model.id}"
end
This code doesn't work for me :
<%= f.cl_image_upload(:image, :folder => "my_folder") %>
I can write directly :
<%= f.input :photo, input_html: {accept: ('image') } %>
as my input is already a PhotoUploader declare like this in the model :
mount_uploader :photo, PhotoUploader

Carrierwave multiple image upload file_field is not enabling selection of multiple images in rails

I am having problems enabling selection of multiple images in implementing Carrierwave multiple images. When I click on the upload button in the new view, I just get the file selection window for a single file and can only select a single file.
In the input form, I have:
<%= simple_form_for #car do |f| %>
...
<%= f.file_field :pictures, multiple: true %>
...
In the Car model, I have:
mount_uploader :pictures, PictureUploader
serialize :pictures, JSON
In the cars controller, I have:
params.require(:car).permit(:name, :make, :year, :color, :seats,
:location, :transmission, :price, :photo, :photo_cache, {pictures: []})
I have a pictures column in the Cars table. I have include cloudinary::Carrierwave in PictureUploader.
Is there anything I'm missing?
At this time multiple image uploads are not supported with Cloudinary’s GEM and Carrierwave integration. However it is on Cloudinary’s road map of implementations. As a workaround for the time being feel free to reference this sample project for multiple uploads using Cloudinary’s GEM and Carrierwave: https://github.com/taragano/Cloudinary_multiple_uploads

Refile gem : multiple file uploads

I'm using Refile with Rails 4. I'm following their tutorial for multiple image upload. Each Post can have multiple Images. My models look like this:
Post.rb:
has_many :images, dependent: :destroy
accepts_attachments_for :images, attachment: :file
Image.rb:
belongs_to :post
attachment :file
I can upload files, fine by using:
<%= f.attachment_field :images_files, multiple: true, direct: true, presigned: true %>
but when I try to retrieve an image like:
<%= attachment_image_tag(#post.images, :file, :small) %>
I get the error:
undefined method file for #<Image::ActiveRecord_Associations_CollectionProxy:0x007fbaf51e8ea0>
How can I retrieve an image with refile using multiple image upload?
In order to retrieve images who belongs to a post, you need to iterate through the array of images.
<% #post.images.each do |image| %>
<%= attachment_image_tag(image, :file, :fill, 300, 300) %>
<% end %>
The helper attachment_image_tag take:
[Refile::Attachment] object : Instance of a class which has an attached file.
[Symbol] name : The name of the attachment column
So here, #posts.images holds an array of image object. It's that object who has an attached file.
class Image < ActiveRecord::Base
belongs_to :post
attachment :file
end
Then when you iterate images, you give to the helper the image object, and the name of the attachment column, here :file.
Are you on the master branch?
gem 'refile', require: "refile/rails", git: 'https://github.com/refile/refile.git', branch: 'master'

Resources