ActiveAdmin is a great framework for quickly creating attractive and powerful administrative interfaces for Ruby on Rails applications. It can handle all sorts of data fields, but for more complex database columns (such as PostgreSQL’s jsonb column), it needs to be customized in to handle the data.

In the LIXY assessment platform we want to allow our customers to configure the behavior of our application for certain accounts and users to accommodate their specific needs.

There are several ways to accomplish this in Ruby on Rails, but I like using JSON data for this. Configurations are specific to the particular object that is being configured. When we use JSON as an attribute, it doesn’t require any additional database calls to get the configuration information.

We will begin by creating a database migration to add the jsonb column to the relevant table

bundle exec rails g migration AddConfigToOrganizations config:jsonb

NOTE: I recommend you avoid allowing NULL values, I have found in most cases, and especially when dealing with jsonb data types, it is preferable to require a value. Unfortunately you can’t set null or default values in the command line generator1, so you’ll need to modify the migration file, the final migration should look like:

class AddConfigToOrganizations < ActiveRecord::Migration[5.2]
  def change
    add_column :organizations, :config, :jsonb, null: false, default: {}
  end
end

Next you’ll need to run the migration by executing: bundle exec rake db:migrate, which will apply the change to your database.

At this point, you can now view any existing organizations:

Display organization object details

But if you try to create or edit one, you will get a Formtastic::UnknownInputError - Unable to find input class JsonbInput exception, as ActiveAdmin - which uses Formtastic to build it’s forms - doesn’t know what to do with a jsonb column:

JsonbInput exception details

Uh oh!

But don’t worry, there are a couple easy ways to solve this problem:

  1. Edit the JSON data directly
  2. Build a form that allows for editing the data, without needing to know the underlying structure

NOTE: Pick one option or the other, don’t do both.

Option 1:

If you don’t want / need well structured data, or will have only technical people that need to edit the data, you can simply use the activeadmin_json_editor gem, which will create a very pretty editing window for your JSON data:

The install / configuration instructions can be found on the gem’s page, but the code needed to get it to display on ActiveAdmin is straightforward:

# filename: app/admin/organizations.rb

permit_params :name, :config

form do |f|
  f.inputs do
    f.input :name
    f.input :config, as: :jsonb
  end
end

Fancy json editor

If that’s all you need, then you are done, you can now edit your JSON data right in Active Admin! Pretty cool right?

Option 2:

For my use case we will have structured data since I will be using it to configure specific functionality. I’m designing it for people that are not very technical, so we need to be able to generate a much more user friendly form.

Since I know what my data will look like, I can create a form that allows the user to directly modify the specific configuration element they want, without having to worry about the structure of the data.

For example, this application is for building assessments, and each assessment has a set of steps, but not everybody uses the word “assessment”, or “step”. Maybe they call it a “test”, and “skill”, so we want to allow them to make those customizations. For this we’ll use a keys of “assessment_label”, and “skill_label” for the config param.

This is where things start to get a little tricky. Formtastic is great for working with Rails models, but it’s not designed to work with any arbitrary data structure.

First we’ll need to set up the params that ActiveAdmin accepts, and then tell formtastic how to present it:

# filename: app/admin/organizations.rb

permit_params :name, config: :assessment_label

form do |f|
  f.inputs do
    f.input :name, as: :string
    f.inputs name: 'Config', for: :config do |g|
      g.input :assessment_label,
              require: false,
              input_html: { value: organization.config['assessment_label'] }
      g.input :skill_label,
              require: false,
              input_html: { value: organization.config['skill_label'] }
    end
  end
end

jsonb form fields

Now, it’s a little laborious to have to type organization.config['key_name'] for every attribute we want to use. We can use rails’ store_accessor2 3 to add accessor methods to the model dry up our code:

# filename: app/models/organization.rb

class Organization < ApplicationRecord
  validates :name, presence: true

  store_accessor :config, :assessment_label, :skill_label
end

This now allows us to directly access the keys in the config, allowing us to change

input_html: { value: organization.config['skill_label'] }

to

input_html: { value: organization.skill_label }

Or if we want to get even DRYer we can change our form definition code to iterate over the array of accessors to automatically generate all the inputs:

# filename: app/admin/organization.rb

ActiveAdmin.register Organization do
  permit_params :name, config: Organization.stored_attributes[:config]

  form do |f|
    f.inputs do
      f.input :name
      f.inputs name: 'Config', for: :config do |g|
        Organization.stored_attributes[:config].each do |accessor|
          g.input accessor,
                  required: false,
                  input_html: { value: organization.send(accessor) }
        end
      end
    end

    f.actions
  end
end

More Neat Tricks:

One cool little thing I discovered while writing this was that the nesting of the config portion of the form is optional, if you remove the |g| from the inputs block, the nesting goes away:

# filename: app/admin/organization.rb

form do |f|
  f.inputs do
    f.input :name
    f.inputs name: 'Config', for: :config do
      Organization.stored_attributes[:config].each do |accessor|
        f.input accessor,
                required: false,
                input_html: { value: organization.send(accessor) }
      end
    end
  end

  f.actions
end

Which creates the form without nesting the config fields:

jsonb form fields with no nesting

Or even without the ‘Config’ section at all:

# filename: app/admin/organization.rb

form do |f|
  f.inputs do
    f.input :name
    f.inputs for: :config do
      Organization.stored_attributes[:config].each do |accessor|
        f.input accessor,
                required: false,
                input_html: { value: organization.send(accessor) }
      end
    end
  end

  f.actions
end

Which creates the form with no separator:

jsonb form fields with no label separator


Requirements:

We assume you are using at least the following:

  • Ruby on Rails 4.2+
  • postgres 9.4+
  • ActiveAdmin

NOTE: Since ActiveAdmin uses Formtastic for building it’s forms, you may be able to use the same approach to building forms for jsonb columns for Formtastic, and possibly adapt it withhout too much effort to other rails form builders