---
title: Site-wide configuration with Administrate
teaser: 'Adding site-wide configuration with a Rails model can be quite easy to do
  with Administrate.

  '
tags: rails,administrate
author: Nick Charlton
published_on: 2022-11-17
---

Every so often, you'll have certain things you'd like to configure site-wide.
Not quite a [feature flag][1] as we don't want to control a full feature, but
something helpful to be able to let admins control. They might cover many
aspects of the system, and you might have a few, too, so holding them together
in one place is helpful.

You could use environment variables for some of these (and sometimes that might
be appropriate), but that can be error-prone (what if you set the wrong type
for a value?) and restrictive for who might want to change the value.

With Administrate and some model tricks, we can implement a dashboard
accessible to admin users and lean on the singleton pattern to provide a nice
way to access and allow us to validate configuration values.

## A configuration singleton

A singleton is a class we can have only one instance of, so calling it will
give us back the same object each time (creating it, if necessary, the first
time). In this case, we'll have only one record and referring to it will mean
we retrieve the same record each time.

There are some tradeoffs with singletons, as their global nature can make
things hard to test because they couple state in different areas of code, but
for occasional use, it's a helpful pattern.

To start with, a migration:

```ruby
class CreateConfigurations < ActiveRecord::Migration[7.0]
  def change
    create_table :configurations do |t|
      t.integer :example_attribute, default: 45, null: false

      t.timestamps
    end
  end
end
```

We want a default value here to simplify our model later on. Ensuring it can't
be null avoids some error cases too. Then, our model:

```ruby
class Configuration < ApplicationRecord
  before_create :check_for_existing
  before_destroy :check_for_existing

  def self.load
    config = Configuration.first

    if config.nil?
      config = Configuration.create
    end

    config
  end

  private

  def check_for_existing
    raise ActiveRecord::RecordInvalid if Configuration.count >= 1
  end
end
```

Of course, a test:

```ruby
require "rails_helper"

FactoryBot.define do
  factory :configuration do
    example_attribute { 45 }
  end
end

RSpec.describe Configuration do
  it "has a valid factory" do
    expect(build(:configuration)).to be_valid
  end

  it "does not allow another record to be created" do
    create(:configuration)
    config_2 = build(:configuration)

    expect(config_2.save).to be_falsey
  end

  it "does not allow destruction of the record" do
    config = create(:configuration)

    expect do
      config.destroy
    end.to raise_error(ActiveRecord::RecordInvalid)
  end

  describe ".load" do
    context "when a configuration record exists" do
      it "returns it" do
        config = create(:configuration)

        expect(described_class.load).to eq(config)
      end
    end

    context "when a configuration doesn't yet exist" do
      it "creates and returns a new one" do
        config = described_class.load

        expect(config).to be_a(Configuration)
      end
    end
  end
end
```

We end up with a database table called `configurations`, then our matching
Configuration model. We create a class method to facilitate loading, which
ensures we have a record, then lean on `before_create`/`before_destroy` to
ensure we don't accidentally create another record (or destroy our current
one).

## Creating an Administrate dashboard

Assuming you've [already configured administrate][3], we can create a new
dashboard:

```sh
rails g administrate:dashboard configuration
```

The dashboard, once trimmed down, is very conventional. We just override the
`display_resource` method to always show "Configuration" as a string:

```ruby
require "administrate/base_dashboard"

class ConfigurationDashboard < Administrate::BaseDashboard
  ATTRIBUTE_TYPES = {
    id: Field::Number,
    example_attribute: Field::Number,
    created_at: Field::DateTime,
    updated_at: Field::DateTime
  }.freeze

  COLLECTION_ATTRIBUTES = %i[
    id
    created_at
    updated_at
  ].freeze

  SHOW_PAGE_ATTRIBUTES = %i[
    example_attribute
    updated_at
  ].freeze

  FORM_ATTRIBUTES = %i[
    example_attribute
  ].freeze

  def display_resource(configuration)
    "Configuration"
  end
end
```

In the controller we need to do a few more things:

```ruby
module Admin
  class ConfigurationsController < Admin::ApplicationController
    def index
      redirect_to admin_configuration_path(find_resource)
    end

    def find_resource(*)
      ::Configuration.load
    end
  end
end
```

[Administrate controllers use `find_resource` to fetch the relevant record
(usually based on the path)][4] but here, we'll use our `.load` method. We
redirect from the index to our model, mainly to make the route helpers nicer to
use as it's more straightforward to refer to.

Finally, set the routes:

```ruby
namespace :admin do
  resources :configurations, only: [:index, :show, :edit, :update]
end
```

I implemented this over four months before writing this, and it's worked very
well. It's helped us provide a clear place to put the few configuration options
we have; for the more complex ones, we can also provide validations. Because
most of Administrate is straightforward in its implementation, implementing
cases like this doesn't become complicated.

[1]: https://martinfowler.com/articles/feature-toggles.html
[2]: https://github.com/thoughtbot/administrate
[3]: https://administrate-demo.herokuapp.com/getting_started
[4]: https://administrate-demo.herokuapp.com/customizing_controller_actions
