Search code examples
ruby-on-railsruby-on-rails-6rails-activestorage

Do Not purge image when updating record in Rails 6 Active Storage


I've been researching this issue for a couple days and can't seem to find a solution.

I have a model in Rails 6 for bars that has_one_attached :image

A user can create a new bar and attach an image. This works fine.

When a user edits bar information like a bar's name and submits that information Active Storage purges the originally uploaded image by default, even if no new image was submitted.

During an edit, if a user does not submit a new image, and ONLY changes the record, e.g., Bar Name, how do I keep Active Storage from automatically purging the image? I would like to keep the image associated with the Bar record.

I have tried dependent: :purge_later in my model to no success. I have also tried submitting params without the image param if bar_info.image.attached?

My Model bar_info.rb

class BarInfo < ApplicationRecord
    belongs_to :user
    has_many :bar_specials, dependent: :destroy
    has_one_attached :image
    validates :user_id, presence: true
    validates :barname, presence: true, length: { maximum: 100 }
    validates :city, presence: true, length: { maximum: 50 }
    validates :state, presence: true, length: { maximum: 50 }
    validates :image, content_type: { in: %w[image/jpeg image/gif image/png],
                                            message: "must be a valid image format" },
                          size:         { less_than: 5.megabytes,
                                          message: "should be less than 5MB" }
end

My Controller bar_infos_controller.rb

class BarInfosController < ApplicationController
def update
        @bar_info = BarInfo.find(params[:id])
        @bar_info.user_id = current_user.id
        @bar_info.image.attach(params[:bar_info][:image])
        if @bar_info.update(bar_info_params)
            flash[:notice] = "Bar Info Updated!"
            redirect_to bar_info_path(@bar_info)
        else
            render "edit"
        end
end

private

        def bar_info_params
            attributes = [ :barname, :city, :state, :image ]
            params.require(:bar_info).permit(attributes)
        end
end

My Edit View _form.html.erb

<div class="row">
    <div class="col-md 6">
        <%= form_with(model: @bar_info, local: true) do |f| %>
        <%= render 'shared/error_messages', object: f.object %>

        <%= f.label "Bar Name (required)" %>
        <%= f.text_area :barname, placeholder: "Bar name here", class: "form-control", size: "70x1" %>

        <%= f.label "City (required)" %>
        <%= f.text_area :city, placeholder: "San Diego", class: "form-control", size: "70x1" %>

        <%= f.label "State (required)" %>
        <%= f.select :state, CS.states(:us), class: "form-control" %>

        <span class="image">
            <p>Upload an image of Bar here!</p>
            <%= f.file_field :image, accept: "image/jpeg,image/gif,image/png" %>
        </span>

        <%= f.submit "Submit", class: "btn btn-primary" %>
        <% end %>

        <script type="text/javascript">
            $("#bar_info_image").bind("change", function() {
                const size_in_megabytes = this.files[0].size/1024/1024;
                if (size_in_megabytes > 5) {
                    alert("Maximum file size is 5MB. Please choose a smaller file.");
                    $("$bar_info_image").val("");
                }
            });
        </script>
    </div>
</div>

bar_infos routes:

bar_infos     GET    /bar_infos(.:format)          bar_infos#index
              POST   /bar_infos(.:format)          bar_infos#create
new_bar_info  GET    /bar_infos/new(.:format)      bar_infos#new
edit_bar_info GET    /bar_infos/:id/edit(.:format) bar_infos#edit
bar_info      GET    /bar_infos/:id(.:format)      bar_infos#show
              PATCH  /bar_infos/:id(.:format)      bar_infos#update
              PUT    /bar_infos/:id(.:format)      bar_infos#update
              DELETE /bar_infos/:id(.:format)      bar_infos#destroy

TL;DR I'd like to edit a bar's name and still keep the original image that was uploaded with it. Active Storage currently purges that image on update.

Thank you.


Solution

  • It is because of this line in your update controller action:

          def update 
            ...
            @bar_info.image.attach(params[:bar_info][:image])
            ...
          end
    

    if user doesn't fill in the image in edit action you will set image to nil which deletes the file from activestorage. You should leave this update completely on ActiveRecords update action. You have image in your permitted params so it should work without any problems:

          def update
            @bar_info = BarInfo.find(params[:id])
            @bar_info.user_id = current_user.id
    
            if @bar_info.update(bar_info_params)
              flash[:notice] = "Bar Info Updated!"
              redirect_to bar_info_path(@bar_info)
            else
              render "edit"
            end
          end
    
    private
    
          def bar_info_params
            attributes = [ :barname, :city, :state, :image ]
            params.require(:bar_info).permit(*attributes)
          end
    

    (also I think there should be * in permit call params.require(:bar_info).permit(*attributes))