Rails 5.2 Active Storage с формами Cocoon

Я хочу сохранить некоторые изображения в модели, используя форму динамического кокона с Active Storage для обработки файлов.

У меня есть класс фермера, в котором много яблок, и фермер может добавить несколько изображений для каждого из разных видов яблок через форму фермера.

class Farmer < ActiveRecord::Base
  has_many :apples, inverse_of: :farmer
  accepts_nested_attributes_for :apples, allow_destroy: true,
                                reject_if: :all_blank
end

class Apple < ActiveRecord::Base
  has_many_attached :apple_images
end

Внутри контроллера фермера у меня есть:

class Farmer < ApplicationController


  def update
    @farmer = Farmer.find(params[:farmer_id])

    if @farmer.valid?
      @farmer.update!(farmer_params)
      redirect_to edit_farmer_path(farmer_id: @farmer.id)
    else
      ...
      ...
    end
  end

  private
  def farmer_params
    params.require(:farmer).permit(
      :farmer_name,
      apples_attributes: [
        :id,
        :color,
        :size,
        :_destroy
      ])
  end
end

на мой взгляд, я только что добавил это в свои поля кокона

<div class="form-field">
  <%= f.label :apple_images, "Please upload apple images" %>
  <%= f.file_field :apple_images, multiple: true, data: { validates: {required: {}} } %>
</div>

Теперь cocoon сохранит атрибуты яблока с помощью вызова accepts_nested_attributes_for после сохранения объекта фермера. Все работает нормально, пока я не попытался добавить apple_images в форму.

Читая файл readme для Active Storage, я вижу, что он предлагает вам прикрепить файлы сразу после того, как элемент был сохранен.

Вы можете прочитать здесь

но вкратце, если вы хотите, чтобы в контроллере было одно изображение, сделайте следующее:

#inside an update method
Current.user.avatar.attach(params.require(:avatar))

или если вам нужно несколько изображений:

def create
  message = Message.create! params.require(:message).permit(:title, :content)
  message.images.attach(params[:message][:images])
  redirect_to message
end

Это кажется довольно простым, когда изображение находится непосредственно на модели, которую я сохраняю в контроллере.

Сначала я подумал, что это может быть так же просто, как добавить apple_images в параметры следующим образом:

  def farmer_params
    params.require(:farmer).permit(
      :farmer_name,
      apples_attributes: [
        :id,
        :color,
        :size,
        :apple_images,
        :_destroy
      ])
  end

но это вернет ошибку:

ActiveModel::UnknownAttributeError - unknown attribute 'apple_images' for Apple.:

Я думаю об использовании обратного вызова after_save в модели яблока, чтобы прикрепить изображения после обновления / создания объекта яблока. Хотя я тоже не знаю, как этого добиться.

Чувствуя себя немного потерянным, мы будем очень благодарны за любые идеи или предложения

ИЗМЕНИТЬ

Вот как выглядят параметры во время обновления:

 <ActionController::Parameters {"utf8"=>"✓", "_method"=>"patch",
   "farmer"=>{"farmer_name"=>"billy the farmer", "apples_attributes"=>
     {"0"=>{"color"=>"Green", 
            "size"=>"A", 
            "apple_images"=>[#<ActionDispatch::Http::UploadedFile:0x007f9e8aa93168 @tempfile=#<Tempfile:/var/folders/n7/65r5561n44q0w4bdnmw42l880000gn/T/RackMultipart20171211-87415-1m2w7gh.png>, @original_filename="Screen Shot 2017-12-07 at 09.13.28.png", @content_type="image/png", @headers="Content-Disposition: form-data; name=\"farmer[apples_attributes][0][apple_images][]\"; filename=\"Screen Shot 2017-12-07 at 09.13.28.png\"\r\nContent-Type: image/png\r\n">, 
                             #<ActionDispatch::Http::UploadedFile:0x007f9e8aa93118 @tempfile=#<Tempfile:/var/folders/n7/65r5561n44q0w4bdnmw42l880000gn/T/RackMultipart20171211-87415-1gdbax2.jpeg>, @original_filename="WhatsApp Image 2017-12-06 at 1.23.35 PM.jpeg", @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"farmer[apples_attributes][0][apple_images][]\"; filename=\"WhatsApp Image 2017-12-06 at 1.23.35 PM.jpeg\"\r\nContent-Type: image/jpeg\r\n">], 
      "_destroy"=>"false", "id"=>"4"}}}, 
     "commit"=>"Next", 
     "controller"=>"farmer/produce", 
     "action"=>"update", 
     "farmer_id"=>"3"} permitted: false>

person TheLegend    schedule 11.12.2017    source источник
comment
загляните в свой журнал и посмотрите, какие параметры / имена полей действительно проходят ... отредактируйте свой вопрос и добавьте их туда.   -  person Taryn East    schedule 12.12.2017
comment
Привет, @TarynEast, я добавил параметры. вы заметите, что apple images переместится вместе с остальными параметрами яблока, которые будут сохранены через фермера accepts_nested_attributes_for. ActiveStorage хочет, чтобы изображения были прикреплены к объектам после сохранения. Я считаю, что ActiveStorage устанавливает полиморфные таблицы. Мне было интересно, могу ли я передать изображения яблока в класс Apple вне атрибутов яблока, чтобы мы могли настроить обратный вызов after_save для прикрепления изображений?   -  person TheLegend    schedule 12.12.2017


Ответы (2)


Вам следует удалить apple_images из farmer_params (потому что это неизвестный атрибут Apple). Но удаление этого гарантирует, что изображения не будут сохранены. Однако именно так он и должен работать (немного странно, имхо).

Если вы проверите документацию, они явно игнорируют атрибут images и устанавливают его отдельно:

message = Message.create! params.require(:message).permit(:title, :content)
message.images.attach(params[:message][:images])

Я не совсем уверен, как решить эту проблему во вложенной форме. Вы можете перебрать все яблоки в параметрах и попытаться установить apple_images, но это кажется очень подверженным ошибкам (как сопоставить новое яблоко без идентификатора с тем, что было сохранено?).

Вы можете попробовать добавить метод следующим образом (и оставить apple_images в разрешенных параметрах):

def apple_images=(images)
  self.apple_images.attach(images)
end 

Но не уверен, работает ли это до сохранения apple.

person nathanvda    schedule 12.12.2017
comment
Спасибо @nathanvda, я переместил загрузку изображений из кокона на другой шаг, чтобы мы могли добавлять их прямо в объект. Мы обнаружили, что ActiveStorage использует какую-то анонимную полиморфную таблицу для сохранения изображений, поэтому экземпляр объекта необходимо сохранить, чтобы у нас был доступ к идентификатору, поэтому вам нужно будет сделать это в некотором обратном вызове after_save в модель. Единственный способ увидеть, как это работает, - это если у Cocoon был доступ к объекту для передачи атрибутов для принятых вложенных атрибутов, а затем какой-то способ определить обратный вызов на уровне модели? Еще раз спасибо! - person TheLegend; 13.12.2017
comment
Звучит очень разумно. Честно говоря, в целом я делаю то же самое: только добавляю вложения к уже существующим элементам (я большой поклонник dropzonejs). - person nathanvda; 14.12.2017

Добавьте apple_images в качестве ключа к хешу с пустым массивом, например:

 params.require(:farmer).permit(
  :farmer_name,
  { apple_images: [] },
  apples_attributes: [
    :id,
    :color,
    :size,
    :_destroy
  ])

Это связано с тем, что когда вы загружаете сразу несколько изображений, они отправляются в параметрах в виде массива.

person snowangel    schedule 15.04.2018