Model-independent Rails 4 file upload in dynamically loaded (nested) form field

306 views Asked by At

I'm looking for a way for simple image upload onto the file system so that the image is not stored in the database. I understand this step is rather easy, just using a file_input tag, however I have problems making it happen because of how my form is built.

I have a project where I create static campaign pages by having a user specify a page layout, or a template, for the page. Once the user selects which template they want to load for their page, a form is loaded that contains a section for each of the widgets that the specified template contains. The user inputs their information, presses submit, and a static page is created.

One of the widgets is an image widget, where the user should select a file from their system and that should get uploaded to the server on submitting the form. The problem is that because of how I'm using the form helpers, I'm facing the issue, where file upload gives filename as a string instead of the IO object (https://www.ruby-forum.com/topic/125637). I need help on trying to work around this.

The form for inputting data onto the page (which is stored in the table campaign_pages) looks like this:

= form_for(@campaign_page, class: 'form-horizontal') do |f|
  fieldset
    legend Basic page information
    .form-group
      = f.label :featured, class: 'control-label col-lg-2'
      .col-lg-10
        = f.check_box :featured, class: 'form-control'

      = f.label :title, class: 'control-label col-lg-2'
      .col-lg-10
        = f.text_field :title, placeholder: 'Unique and really catchy page title!', class: 'form-control'

      = f.label :campaign_id, 'Campaign: ', class: 'control-label col-lg-2'
      .col-lg-10
        = f.select :campaign_id, options_from_collection_for_select(@campaigns, 'id', 'campaign_name', @campaign), {}, html_options= {:class => 'form-control'}

      = f.label :language_id, 'Language: ', class: 'control-label col-lg-2'
  .col-lg-10
        = f.select :language_id, options_from_collection_for_select(Language.all,'id', 'language_name'), {}, html_options= {:class => 'form-control'}

      label for='template_id' class='control-label col-lg-2' Template: 
  .col-lg-10
        = select_tag 'template_id', options_from_collection_for_select(@templates, 'id', 'template_name', @template), class: 'form-control'
      | data-no-turbolink="true"

This is where forms for content for each widget on the page gets loaded once a template has been selected:

    #widget_location
      = render :file => 'templates/show_form', layout: false

    .form-group
      = f.submit 'Save Page', class: 'btn btn-sm btn-primary'

The form for the image widget (which creates the problem) looks like so:

#image_widget_form.form-group
  = label_tag 'widgets[image][image_url]', 'Image URL:', class: 'control-label col-lg-2'
  .col-lg-10
    = text_field_tag 'widgets[image][image_url]', options['image_url'], class: 'form-control'
    = hidden_field_tag 'widgets[image][widget_type]', WidgetType.find_by(:widget_name => 'image').id
    = file_field_tag 'widgets[image][image_upload]'

After reading the documentation (http://guides.rubyonrails.org/form_helpers.html#uploading-files) and other issue reports, my understanding is that the problem is "multipart => true", which you have to specify if you want your file field tag to work. That shouldn't be a problem, because I'm using form_for @campaign_page instead of form_tag. However, the widgets and their content do not belong to the campaign page table, and so they aren't specified in the model for a campaign page. Thus, I can't simply use f.file_field for the file input.

I'm looking for a solution for this. Would using nested attributes and then using field_for for the image upload work?

Another issue is that specifications and content for the widgets are stored in a Postgres JSONB field, and I'm not sure how nested attributes would work for those.

1

There are 1 answers

0
Tuuli Pöllänen On BEST ANSWER

I assumed that the form had multipart set, because specifying multipart => true for a form_for helper is not necessary according to Rails documentation. The fix turned out to be pretty simple, however - it works after setting multipart: true in a html options hash for the form tag:

= form_for(@campaign_page, class: 'form-horizontal', html: {multipart: true}) do |f|
...