7

Just created a new Rails 6 app, and I am trying to allow adding images to an active_storage blob instead of replacing them, through a form generated with rails scaffold.

Followed the documentation (https://guides.rubyonrails.org/active_storage_overview.html#has-many-attached), using #attach into my controller, but it leads to an error page and keep the "default" behavior of replacing all the images instead of adding new images.

Using Rails 6.0.0 with active_storage 6.0.0

I first made a Page model using rails g scaffold Page name:string and added then in my page.rb model the association with ActiveStorage has_many_attached :images

In my form I added a file_field, allowing multiple uploads:

<%= form.file_field :images, multiple: true %>

Here is my controller update action, note @page.images.attach(params[:images]) that is supposed to do the job, according to the documentation


def update

    respond_to do |format|
      if @page.update(page_params)

        @page.images.attach(params[:images])

        format.html { redirect_to site_pages_path(@site), notice: 'Page was successfully updated.' }
        format.json { render :show, status: :ok, location: @page }
      else
        format.html { render :edit }
        format.json { render json: @page.errors, status: :unprocessable_entity }
      end
    end
  end

When filling the form, attaching new pictures and posting it, I got the following error :

ArgumentError in PagesController#update
Could not find or build blob: expected attachable, got nil 

Pointing the line @page.images.attach(params[:images])

When checking the server logs, I noticed that despite the error, the default behavior is still running : The old images get deleted and the new ones get attached.

Simon Mo
  • 503
  • 2
  • 4
  • 14
  • How many images are you trying to attach? If there are multiple in the params you'll need to iterate through them and attach them indifidually – Mark Oct 08 '19 at 08:56
  • Hi Mark, I tried to attach 2 or 3 pictures to test, it should accept any amount of pictures. Do you mean that I should do something like a loop inside the controller looping over all attachements and attaching them individually ? e.g. `params[:images].each do |image|` ? – Simon Mo Oct 08 '19 at 09:02
  • Tried the following : `params[:page][:images].each do |image| @page.images.attach(image) end` No more errors, but old pictures still get deleted, and new images get attached 2 times ! (because I tested with 2 images) – Simon Mo Oct 08 '19 at 09:11
  • Yeah that's what I was thinking - I've no idea why your original image is getting overwritten :( – Mark Oct 08 '19 at 10:11
  • I think it maybe comes from the original `if @page.update(page_params)` generated by rails scaffold, I heard that "update" actually replaces the image by default in Rails 6. Maybe I should go to something more custom than using the generated action. – Simon Mo Oct 08 '19 at 10:54
  • Ahh good spot - if that's the case you can strip out the images params from page_params (maybe `def page_params_without_images`), use that to update, then proceed as you were – Mark Oct 08 '19 at 10:56
  • @Mark thanks for the help ! could fix the issue, posted my solution below ;) – Simon Mo Oct 08 '19 at 13:24
  • Glad you got there :) – Mark Oct 08 '19 at 14:07

3 Answers3

18

Under Rails 6, the default behavior for has_many_attached was changed from Rails 5. Previously, files were appended to the attachment list instead of being overridden.

Luckily this default can be changed, in your application.rb:

config.active_storage.replace_on_assign_to_many = false

You can then keep the images: [] section in the permitted list and remove the @page.images.attach(params[:images]) call completely.

user10704464
  • 204
  • 1
  • 6
  • oh man thx for this! I build a workaround for this for an hour. why is this not documented in the ActiveStorageGuide :/ where I can find those information? – larz Dec 29 '19 at 19:13
  • It's under active storage notable changes in the rails 6 release notes. https://edgeguides.rubyonrails.org/6_0_release_notes.html#active-storage-notable-changes – user10704464 Dec 31 '19 at 00:25
  • config.active_storage.replace_on_assign_to_many is deprecated and will be removed in Rails 7.1. https://github.com/rails/rails/blob/c8c447240c95921d98a79cf6380e5d843bba0331/activestorage/lib/active_storage/attached/model.rb#L150 – Matthew Lemieux May 09 '22 at 08:38
4

Ok, I could fix the issue !

The problem : update action will replace all the pictures.

So here is what I did :

1) I removed images: [] from the permitted list (strong parameters)

2) I wrote this code to attach individually each of the new images and put it into create and update actions :

    if params[:page][:images].present?
      params[:page][:images].each do |image|
        @page.images.attach(image)
      end
    end

#attach will actually permit the param.

Not sure it was the best way, but now it's working

Simon Mo
  • 503
  • 2
  • 4
  • 14
  • Thought I should add that "`#attach` will actually permit the param" is not entirely true. Permitted parameters are used when mass assigning a ActionController::Parameters object to an ActiveRecord/ApplicationRecord etc. Here, you directly refer to the param using `params[:page][:images]` which contains e.g. the UploadedFile object for further processing, and thus there is no need to permit the param. :) – randmin Jun 16 '23 at 16:49
0

I will let another way to solve that "more clear"

I create a private method on controller class:

before_action :append_images, only: %i[create update]

I assign that method to run on before_action macro:

def append_images
  return if params[:model_name][:images].blank?

  params[:model_name][:images].each do |image|
    @model_name.images.attach(image)
  end
end

And finally remove images: [] from permit params

Rafael Gomes Francisco
  • 2,193
  • 1
  • 15
  • 16