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.