Home > database >  Rails 7 help DRYing out image attachments?
Rails 7 help DRYing out image attachments?

Time:03-05

Bear with me, I am new to posting and Rails so sorry if I mess up phrasing!!

I am working on a Rails app with many similar models. Each view has a _form.html.haml partial that differs in content but contains similar components, such as buttons to submit, delete, etc. Every model has_many_attached photos, and in the form you can add or delete photos. The haml for deleting photos looks like this, where variable is replaced with whatever view _form.html.haml is in:

.gallery#form
  - @VARIABLE.photo.each_with_index do |image, index|
    .overlay-container
      .img-square{ :style => "background-image: url(#{rails_blob_url(photo(@VARIABLE, index))})", :alt => "Photo of #{image}" }
      = link_to("Delete", delete_image_attachment_VARIABLE_url(image), method: :delete, class: 'button delete overlay')

To make the delete work on each photo, this code is in each controller:

  def delete_image_attachment
    @photo = ActiveStorage::Attachment.find(params[:id])
    @photo.purge
    redirect_back fallback_location: @VARIABLE
    flash[:success] = 'Photo was successfully deleted.'
  end

And routes.rb has this chunk of code for each model:

  resources :VARIABLE do
    member do
      delete :delete_image_attachment
    end
  end

However, I have about a dozen models I need to do this on. My goal is to bring the gallery into a new partial, since it will be used in every _form regardless of the other content. However, the delete function (though the same for every controller) is tied to the controller/routes.rb of each model.

There must be some way of DRYing this functionality into a couple files instead of copy-pasting for each model, but my Google searches have not turned up anything. So, any guidance or better Rails convention is greatly appreciated!

CodePudding user response:

If I'm understanding the structure properly, it sounds like you would want to do something like the following:

Create a top-level controller that handles deleting images

  • This could be used by any page.
  • This would require the following query parameters:
    • id the photo's ID to delete by.
    • redirect where to redirect the user after the action is completed.

Routes

Now there will only be 1 route to handle the controller above.

Create a reusable partial

For rendering a collection of images with a redirect url that allows you to set the following state for the partial:

  • photos a collection of images to render.
  • redirect_url this is passed as a query parameter to the centralized delete image controller.

Thoughts & Mocked Examples

This should be able to DRY up your implementation. Currently the only thing I see that couples the view with the deletion is the redirect URL. By abstracting that out and moving that essentially to a parameter for the partial will allow for re-use and flexibility.

You've already identified your coupling via @VARIABLE, here's a quick mock of how I would expect it to end up looking like:

Partial Template

.gallery#form
  - @photos.each_with_index do |image, index|
    .overlay-container
      .img-square{ :style => "background-image: url(#{rails_blob_url(photo(@VARIABLE, index))})", :alt => "Photo of #{image}" }
      = link_to("Delete", delete_image_attachment_url(image, redirect: @redirect_url), method: :delete, class: 'button delete overlay')

Ths would require: photos, and redirect_url So make sure to set @photos and @redirect_url on the consuming controller.

Example with instance properties to access in the template

@photos = GET_PHOTOS_HERE
@redirect_url = 'some-redirect-path'
render partial: 'photos_partial'

Example with locals parameter for the template

photos = GET_PHOTOS_HERE
render partial: 'photos_partial', locals: { photos: photos, redirect: 'some-redirect-path' }`

Note: You may need to change how you access the local variables in the template.

https://guides.rubyonrails.org/layouts_and_rendering.html#passing-local-variables

Controller

  def delete_image_attachment
    @photo = ActiveStorage::Attachment.find(params[:id])
    @photo.purge
    redirect_back fallback_location: params[:redirect]
    flash[:success] = 'Photo was successfully deleted.'
  end

Routes

Here you would only have the single route for deleting any image attachment, and point at the single controller above.

delete 'resources/image_attachment/:id', to: 'resources#delete_image_attachment'

Note: Replace "resources" with whatever your controller name is, or the scoping/naming you would like.

PS: It's been a while since I've done Rails so I'm not completely certain on the accuracy or your environment.

  • Related