608.620.5104 | info@tenforwardconsulting.com

Integrating an array column with Simple Form in Rails

Published July 14, 2016

THE PROBLEM

Zach and I were working on a project where we migrated an attribute on a model (Course) from a string (email) to an array (emails), and we needed to integrate it into the SimpleForm-built, user-facing form.

We wanted an experience that was more user-friendly than simply a textbox with comma-separated emails; we found a blog post that helped us get started, but in their version, if there weren't already a value for the array attribute, it wouldn't show any input fields when the form loaded.

THE CODE

As the post describes, you can define custom inputs for SimpleForm, saving them in app/inputs.

array_input.rb

class ArrayInput < SimpleForm::Inputs::StringInput   
  def input     
    input_html_options[:type] ||= input_type     
    Array(object.public_send(attribute_name)).map do |array_el|
      @builder.text_field(nil, input_html_options.merge(value: array_el, name: "#{object_name}[#{attribute_name}][]"))
    end.join.html_safe  
  end   
  
  def input_type     
    :text   
  end
end


Then, integrate the change into your form:

Form view (using Haml)

= f.input :emails, as: :array
%button.add-email-button Add additional email


Finally, if you're using strong params, include your attribute and designate it as an array:

Controller

def model_params  
  params.require(:course).permit(:attribute1, :attribute2, emails: [])
end


This wasn't detailed in the original post we looked at, but we also added some JavaScript to handle inserting another email field:

CoffeeScript

$(document).ready ->  
  $('.add-email-button').on 'click', @addEmailField    
  
  addEmailField: (ev) ->    
    ev.preventDefault()    
    $lastEmailField = $('input[name="course[emails][]"]:last-of-type').clone()    
    $lastEmailField.val("")    
    $(".input.course_emails").append($lastEmailField)


THE SOLUTION

The way we ensured there was always a blank input field for the user was to insert a few more lines of code into our ArrayInput modification file:

class ArrayInput < SimpleForm::Inputs::StringInput  
  def input
    input_html_options[:type] ||= input_type    
    existing_values = Array(object.public_send(attribute_name)).map do |array_el|
      @builder.text_field(nil, input_html_options.merge(value: array_el, name: "#{object_name}[#{attribute_name}][]"))
    end      

    existing_values.push @builder.text_field(nil, input_html_options.merge(value: nil, name: "#{object_name}[#{attribute_name}][]"))
    existing_values.join.html_safe  
  end    
  
  def input_type    
    :text  
  end
end


By pushing a blank value to the input, it essentially creates an empty field when the form loads, whether or not the saved emails column is blank. 

 

Finally, to ensure we didn't save blank emails to the database, we added the following to the Course model:

before_save :remove_blank_emails 

def remove_blank_emails
  emails.reject!(&:blank?)
end


FURTHER READING

Author details

Hilary Stohs-Krause

Software developer
@hilarysk