If you want to add images to a record but you don’t want to use a JavaScript plugin or write any custom JavaScript, you can use a regular file field, Active Storage, and vanilla Rails.
If you want to be able to:
Here is a tutorial to help you accomplish that.
To use as an example for the tutorial, we are going to have a Product
record that has many attached images like this:
class Product < ApplicationRecord
has_many_attached :images
end
If you want to add many attachments to a record using just a file field, but you don’t want to remove the previous images from the record on every update, you can use a virtual attribute “new_images”.
Instead of using the :images
attribute, you can add a virtual :new_images
attribute, using an attr_reader
and a custom writer like this:
class Product < ApplicationRecord
attr_reader :new_images
has_many_attached :images
def new_images=(images)
self.images.attach(images)
end
end
And use that attribute in your file_field
:
<%= form_with(model: product) do |form| %>
<%= form.label :new_images %>
<%= form.file_field :new_images, multiple: true %>
<%= form.submit %>
<% end %>
Also, update your “params method” in the controller:
def create
@product = @site.products.new(product_params)
#...
end
def update
@product.update(product_params)
# ...
end
private
def product_params
params.require(:product).permit(new_images: [])
end
This way, when you assign new images, your record will attach the received images instead of updating the images field with the new images.
To display the images, you can put an HTML like this:
<div class="product-images">
<% product.persisted_images.each do |image| %>
<figure>
<%= image_tag image %>
</figure>
<% end %>
</div>
Where “persisted_images” is:
def persisted_images
images.select(&:persisted?)
end
Maybe you don’t need this, but I use this HTML inside the form, and I had some troubles when the record was not valid; it was showing images without content, this solved my problem.
If you are not displaying the images inside the form, maybe you can put a button_to
remove the image near the image.
<div class="product-images">
<% product.persisted_images.each do |image| %>
<figure>
<%= image_tag image %>
<%= button_to(
"Remove",
product_image_path(product, image),
method: :delete) %>
</figure>
<% end %>
</div>
But if you want to put the images inside your form, you can’t use a button_to
because you can’t have a form inside a form.
So one thing you can do is to have a button that will trigger a form that is outside the main form, like this:
<%= form_with(model: product) do |form| %>
<div>
<%= form.label :new_images %>
<%= form.file_field :new_images, multiple: true %>
</div>
<div class="product-images">
<% product.persisted_images.each do |image| %>
<figure>
<%= image_tag image %>
<%= tag.button(
"Remove",
type: "submit",
form: "delete_image_#{image.id}") %>
</figure>
<% end %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
<% product.persisted_images.each do |image| %>
<%= form_with(
model: image,
url: product_image_path(product, image),
method: :delete,
id: "delete_image_#{image.id}") do %>
<% end %>
<% end %>
In the form, we are using a button
tag of type submit
that will trigger a form with id "delete_image_#{image.id}"
.
<%= tag.button(
"Remove",
type: "submit",
form: "delete_image_#{image.id}") %>
And we are creating a form for each image specifying that id:
<% product.persisted_images.each do |image| %>
<%= form_with(
model: image,
url: product_image_path(product, image),
method: :delete,
id: "delete_image_#{image.id}") do %>
<% end %>
<% end %>
Then on the controller that handles the product_image_path
, you can remove the image with purge
.
class ImagesController < ApplicationController
before_action :set_product
def destroy
@product.images.find(params[:id]).purge
#...
end
private
def set_product
@product = Product.find(params[:product_id])
end
end
I have a sample application with code available at https://github.com/bhserna/simpleimagemanagment.
Here I try to share knowledge and fixes to common problems and struggles for ruby on rails developers, like How to fetch the latest-N-of-each record or How to test that an specific mail was sent or a Capybara cheatsheet. You can see more examples on Most recent posts or all posts.