Ruby on Rail, Carrierwave - file upload fields disappear on form validation failure

I have been banging my head against the wall last week trying to figure out this problem.

I have a Ruby on Rails application that has a view form that has a lot of file uploads as well as input fields. Upon successful completion, the form information is saved to the Postgres database and an email is sent with the provided information and attached files. This works correctly if I turn off validation or if the form does not complete validation, however, if validation fails, the problem occurs.

Fields when loading Selected documents Missing / disabled fields

If a file is selected and then the submit button is clicked and the form fails validation, some of the file fields will either be disabled or completely removed from the html. It will also change the margin fields from the original. For example, the additional document (s) is upload49, but after a failed validation, the file field now uploads25.

I've tried every Google search I can think of to track down the problem. The closest I've found is an AJAX issue and file uploads are not allowed by default for security reasons, so Carrierwave is required. I noticed that when the "ajax: aborted: file" error occurred, it was what was removing the file fields. The other thing I found is that when the submit button is clicked and the form is processed that all file fields are temporarily disabled and I can only assume that something was breaking somewhere that was preventing them from being enabled again when validations failed.

If I added a listener for 'ajax: aborted: file', and if it returned false, it would stop the file fields from getting messed up, but it would cause the submission and form validation to stop working.

Any help would be greatly appreciated. Let me know if you have any questions or need more code.

Here are some code snippets.

# uploaders/AttachmentsUpload.rb
class AttachmentsUploader < CarrierWave::Uploader::Base
  # Choose what kind of storage to use for this uploader:
  # storage :fog
  storage :file # for local testing, saving to S3 in production

  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end
end

# models/buyer_under_contract.rb
class BuyerUnderContract < ApplicationRecord
  include AttributeOption
  belongs_to :user
  belongs_to :team
  mount_uploader :upload1, AttachmentsUploader
  mount_uploader :upload2, AttachmentsUploader
  mount_uploader :upload3, AttachmentsUploader
  mount_uploader :upload4, AttachmentsUploader
  mount_uploader :upload5, AttachmentsUploader
  mount_uploader :upload6, AttachmentsUploader
  ...
  ...
  mount_uploaders :upload49, AttachmentsUploader

  after_create :send_notification

  # Agent Info Validations
  validates :location, :agent_fname, :agent_lname,
            :agent_email,
            presence: true
  validates :prior_contract,
            presence: true,
            if: :prior_contract_status?
  validates :previous_transaction_coordinator,
            presence: true,
            if: :previous_contract?
  ....
  ....
  ....

  def send_notification
    NotificationMailer.buyer_under_contract_form(self).deliver
  end
end

# controllers/buyer_under_contracts.rb
class BuyerUnderContractsController < ApplicationController
  include ListItemsController
  responders :ajax_modal, :collection
  respond_to :js, only: %i[index new create edit update]
  respond_to :html, only: [:index]
  load_and_authorize_resource

  before_action :restrict_users, only: %i[new create edit update]

  def new
    list_item.user = current_user
    respond_with(list_item)
  end

  def create
    puts '@create' + list_item.inspect
    created = list_item.save

    if created
      redirect_to URI(request.referer).path
      return
    end
    puts '@errors' + list_item.errors.inspect
    respond_with(list_item)
  end

  def update
    updated = list_item.update_attributes(resource_params)

    return if resource_params[:_update_type] == 'inline'

    if updated
      target = URI(request.referer).to_s
      redirect_to target
      return
    end
    respond_with(list_item)
  end

  def buyer_under_contract_params
    numeric = %i[location zip mls purchase_price broker_commission_percentage
                 broker_commission_fee buyer_transaction_fee]

    params[:buyer_under_contract].each do |param, value|
      if param.to_sym == :communication_pref
        value.delete_if { |x| x == '' }
        params[:buyer_under_contract][param] = value.join(', ')
      end
      if param.to_sym == :documents_present
        value.delete_if { |x| x == '' }

        params[:buyer_under_contract][param] = value.join(', ')
      end
      next unless numeric.include?(param.to_sym)
      params[:buyer_under_contract][param] = value.gsub(/[^0-9.]/, '')
    end

    # I know this is bad practice, was having problems with 
    # unpermitted parameters even though I permitted them,
    # just for testing now.
    params.require(:buyer_under_contract).permit!
  end


# views/_form.html.haml
= simple_form_for(@buyer_under_contract, remote: true) do |form|
  -if @buyer_under_contract.errors.any?
    .modal-header.error-container
      -@buyer_under_contract.errors.messages.each do |attr, _msg|
        .row
          =I18n.t(:"simple_form.labels.buyer_under_contract.#{attr}") + " is required"

  .modal-body.container
    %div.hidden.container
      = form.input :user_id, value: User
    %div.agent_info.container
      %h2 Agent Information
      %div.container
        %h3.form *Location
        = form.input :location, placeholder:'Location', label:false, collection: Team.all, prompt: 'Location', wrapper_html: {class: 'col-sm-5'}, input_html: { data: { placeholder: 'Location', 'allow-clear': false } }
        Select the city your office is located in
      %div.container
        %h3.form Agent Name (Your Name)
        = form.input :agent_fname, label:'First', wrapper_html: { class: 'col-sm-5' }
        = form.input :agent_lname, label:'Last', wrapper_html: { class: 'col-sm-5' }
     ...
     ...
     ...
     %div.documents.container(id = 'doc_uploads')
        %h3.form Documents Upload(s)
        %div.container.hidden(id = 'doc_addendum_purchase_agree_arbitration')
          %h5 Addendum to Purchase Agreement/Arbitration
          = form.file_field :upload1, multiple: false, wrapper_html: { class: 'col-sm-6'}
        %div.container.hidden(id = 'doc_addendum_purchase_agree_cic')
          %h5 Addendum to Purchase Agreement/CIC (if applies)
          = form.file_field :upload2, multiple: false, wrapper_html: { class: 'col-sm-6'}
        %div.container.hidden(id = 'doc_addendum_purchase_agree_inspect')
          %h5 Addendum to Purchase Agreement/Inspections
          = form.file_field :upload3, multiple: false, wrapper_html: { class: 'col-sm-6'}
        ...
        ...
        %div.container.hidden(id = 'doc_additional')
          %h5 Additional Documents
          = form.file_field :upload49, multiple: true, wrapper_html: { class: 'col-sm-6'}


  .modal-footer
    = form.button :submit, class: "btn btn-success contract_submit_button", data: {disable_with: "Please wait…"}, value: 'Submit'
    = link_to "Cancel", "#", class: "btn btn-danger", data: {dismiss: "modal"}, type: "button"

# assets/buyer_under_contract.js.coffee
# Some complicated JS to hide and unhide fields, but no 
# disabling of fields, I can attach if it needed

# mailers/notification_mailer.rb
class NotificationMailer < ApplicationMailer
  default from: "info@test.com"

  def buyer_under_contract_form(submission)
    @submission = submission
    @user = User.find_by_id(@submission.user_id)
    @address = @submission.address
    @location = Team.find_by_id(@submission.location)
    puts 'address: ' + @address.to_s
    puts 'user: ' + @user.to_s
    puts 'location: ' + @location.to_s

    attachments.inline['white-logo.png'] = File.read('app/assets/images/white-logo.png')
    (1..49).to_a.each do |value|
      begin
        if @submission.send("upload#{value}").is_a?(Array)
          @submission.send("upload#{value}").each do |attachment|
            file_url = attachment.to_s
            next if file_url == ''
            puts 'file url: ' + file_url
            puts "#{Rails.root}/public/#{file_url}"
            attachments.inline[attachment.filename] = File.read("#{Rails.root}/public/#{file_url}")
          end
        else
          file_url = @submission.send("upload#{value}").to_s
          next if file_url == ''
          puts 'file url: ' + file_url
          puts "#{Rails.root}/public/#{file_url}"
          attachments.inline[@submission.send("upload#{value}").filename] = File.read("#{Rails.root}/public/#{file_url}")
        end
      rescue
        puts 'Mailer/Attachment Failure'
        puts $!.message
        next
      end
    end
    mail(to: "testuser@example.com",
         subject: "New Buyer Closing File at #{@address} - #{@location}",
         reply_to: @user.email
    )
  end
end

      

+3


source to share


1 answer


Had the same problem and took days to fix it. But when I found out about this solution, I was a little disappointed.

If you are using jquery_ujs instead of rails-ujs try changing it to rails-ujs in application.js.



And it's probably too late to answer, but someone else will have the same problem.

0


source







All Articles