Sorting Images using Active Storage
I'm experimenting with Active Storage to manage images. I got the multiple uploads working, but I want to use acts_as_list position with jQuery sortable to manage their order using ajax, like in the video on this site. In a normal model, I would run a migration to add position to the model. But it looks like I have no access to the Blob or Attachement models. How would I tackle this? Or do I have to go back to using Carrierwave? Any help would be appreciated.
Sorry everyone. I didn't see your replies. IF you're still interested I can probably put something together.
Hey Tony! I am in the middle of tackling this EXACT issue and it's making me pretty crazy. Any chance you could share your solution? I am really struggling with getting acts_as_list into the attachments model, etc.
It took me a MONTH to figure this out. Sizing images, storage at AWS, sorting, etc. There was zero documentation. I'm pretty sure I was the first person to figure it out but it was only for me and my projects. I use this all the time in every project now. How far have you progressed?
Here's my really quick directions. Hope this helps.
Make sure you have jQuery installed.
You first have to get Active Storage installed.
rails active_storage:install
rails db:migrate
Add this for image processing and to use AWS.
gem 'image_processing', '~> 1.2'
gem 'aws-sdk-s3'
You need to add the position field to the model.
Here is the migration:
add_column :active_storage_attachments, :position, :integer
rails db:migrate
Add Acts as List gem.
gem 'acts_as_list'
Make sure you run: bundle install
I use this code to update the position of the images.
jQuery(document).ready(function($) {
$(".sort-me").sortable({
update: function(e, ui){
ui.placeholder.height(ui.item.height());
$.ajax({
url: $(this).data("url"),
type: "PATCH",
data: $(this).sortable('serialize')
});
}
})
});
These are my routes to sort and delete images. Example is a Product model.
resources :products, only: [:sort_attachments, :delete_image] do
collection do
patch '/sort/:id', action: :sort_attachments, as: 'sort_attachments'
delete '/:id/images/:image_id', action: 'delete_image', as: 'delete_image'
end
end
Lastly, the view. This is where Acts as List comes in. The code loops through the images for this Product and orders them by the Active Storage position field you added.
<% if product.persisted? %>
<% if product.images.present? %>
<%= image_tag attachment.variant(combine_options: {auto_orient: true, thumbnail: '150x150', gravity: 'center', extent: '150x150' }), class: "image-fluid img-thumbnail" %>
<%= link_to delete_image_admin_products_path(product.id, attachment.id), method: :delete, class: "btn btn-xs btn-danger text-white", data: {confirm: "Are you sure?"} do %>
<% end %>
<% end %>
<% end %>
<% end %>
You'll probably have to add some CSS to make everything look nice. You can see the classes I used on the images.
I never had anyone help me (self-taught dev, still learning) and had to figure this out on my own. I'm glad to help as I wish I had someone to point me in the right direction a few year back.
Let me know how you make out.
Sorry to everyone else on this thread who I didn't reply to. This is the first notification I received since I posted this way back.
I am soooo close based on what you sent. And, yes, it is a freaking desert out there on this topic.
I pretty much have everything working except the URL call to do the re-ordering on the backend.
How did you manage to get acts_as_list added to the ActiveStorage::Attachment model?
In your example where does your "sort_attachments" method live?
Thank you SO MUCH for helping!!!
To sort you have to have the position field added via the migration. Haha. It would help if I added the controller method.
def sort_attachments
params[:attachment].each_with_index do |id, index|
ActiveStorage::Attachment.where(id: id).update_all(position: index + 1)
end
head :ok
end
def delete_image
ActiveStorage::Attachment.where(id: params[:image_id])[0].purge
redirect_to edit_admin_product_path(params[:id]), notice: 'Image was successfully deleted.'
end
Also, make sure you PERMIT images: [] in your params. In this case I added to product_params in the controller.
Thank you...I understand all this. However, you are using acts_as_list, as am I. That gem needs to be declared in the model of the items that are being sorted -in this case the attachments. That's what exposes the position update method. My question is where did you declare acts_as_list? Which model? If in Product it would assume you were sorting Products and not attachments.
Well that's great news! One last thing before I start hacking away. In your view, where is the url declaration for the sort_attachments ajax request? Sortable references it but I don't see in your view example.
If I'm understanding you correctly, I added the images at the bottom of my form edit in my admin. The path is referenced right in the loop of Product images.
I think it's just the view code you pasted got cut-off. It came through like this:
<% if product.persisted? %>
<% if product.images.present? %>
" class="ui-state-default image-box float-left">
Obviously a chunk missing!
This is what you sent before. But take a look - after your <% if product_images.present? %> it goes right to " class="ui-state-default image-box float-left"> which makes no sense. That is cut-off code. I would assume that missing bit before "class= is what I need. Sorry to be a pest! But the code that actually calls sort is where the magic will happen. Do you see that blank space on your end?
<% if product.persisted? %>
<% if product.images.present? %>
" class="ui-state-default image-box float-left">
Took me a minute to figure out the syntax of the code block.
<% if product.persisted? %>
<% if product.images.present? %>
<div class="row mb-3">
<div class="col-md-12">
<div class="sort-me mt-3" data-url="<%= sort_attachments_admin_products_path %>">
<% product.images.order(:position).each do |attachment| %>
<div id="<%= dom_id(attachment) %>" class="ui-state-default image-box float-left">
<%= image_tag attachment.variant(combine_options: {auto_orient: true, thumbnail: '150x150^', gravity: 'center', extent: '150x150' }), class: "image-fluid img-thumbnail" %>
<%= link_to delete_image_admin_products_path(product.id, attachment.id), method: :delete, class: "btn btn-xs btn-danger text-white", data: {confirm: "Are you sure?"} do %>
<i class="czi-trash"></i>
<% end %>
</div>
<% end %>
</div>
</div>
</div>
<% end %>
<% end %>