Find an article

Start typing to search the archive.

← All articles

Simple form array text input

Use simple_form to generate an array of text fields. Add a custom input for a PostgreSQL array in Rails.

Since Rails 4 supports the PostgreSQL array type out of the box, it would be great to use it to receive an array of text fields from a client-side form (a collection of ZIP codes, for example) and pass it to a model. I love the SimpleForm gem for generating forms in Rails, but it only lets you generate collections of radio buttons or checkboxes and unfortunately doesn’t support this functionality. In this post, I will show how to solve the problem.

Problem

This screenshot will explain what I have to do without any words:

Account and User relation

In the database, I’m going to use PostgreSQL arrays in ActiveRecord to save the data. But in the view, Rails doesn’t provide a tool to generate these fields out of the box. So let’s do it with SimpleForm.

SimpleForm and custom inputs

It may not be a surprise to you, but SimpleForm lets us define our own custom input. It’s very easy: just define your own class inheriting from a base SimpleForm input and call it when generating inputs.

This is the custom attribute for the array input:

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

In the form use this example to generate a collection of text fields:

form_for @location do |f|
  f.input :zips, as: :array
end

One note here: the @location object must have a zips attribute, and it should be an array. If you have a nonempty zips array, this piece of code will generate its entries in a form. If you have an empty array, you will have to think about initializing the attribute on the server before rendering the form.

All generated inputs will get a name like this: location[zips][]. They will all have the same name. The browser will join them together before sending them to the server. Rails will understand that this is an array of inputs because their names end with []. Finally, you will get an array parameter in the controller’s params object. This means you can get the values in a controller with this code: params[:location][:zips].

We’ve just said that the browser joins parameters with the same name into one line. Here is an example of how they go to the server: "location[zips][]=11111&location[zips][]=22222". Rails will parse them into a hash with the Rack::Utils.parse_nested_query method. Let’s summarize the process and see the results of parsing these attributes:

$irb --simple-prompt
>> require 'rack'
=> true
>> Rack::Utils.parse_nested_query "location[zips][]=11111&location[zips][]=22222"
=> {"location"=>{"zips"=>["11111", "22222"]}}

If you use StrongParameters to filter parameters from a client, you may be interested in how to manage this. It’s very simple: just don’t forget to indicate that you expect an array rather than one attribute.

params.require(:location).permit(zips: [])

Note that [] comes after zips: — it’s important to write it like this. Otherwise (if you write permit(:zips), for example), you will get an exception because an array comes from the server, but StrongParameters expects a single object with that definition.

Implementing the “+Add another” link won’t be described in this post because it’s rather trivial. You will have to write a little JavaScript, and it’s up to you.

Hope this article will help somebody.