activeadmin_configurable_columns 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bdf4162f0335b3294ed14cc99cc72ed22ce80e798276f9903e3f6a55fe2858f6
4
+ data.tar.gz: 6082979b4e9332a4f3d904b6ae1838855515d4a26756ba6dc2f54edd0cb06720
5
+ SHA512:
6
+ metadata.gz: 33db6eb5c5af30b9fc6e092942549b791fbf48f9e691daa3ee9d273f30b0af3bb41f55137429a626465497cf83a35f5bfadb8683e47fc86c837e80a14c66b33b
7
+ data.tar.gz: 02a8485aa3eaac9ea24037f261ec15e82b519f216b5de39e61057fdbb7582face1cc01cb18ad7fd89854d176e2440f2fb85f7f8abc07423f3de32f2f5d5e1959
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0 (2026-09-30)
4
+
5
+ Initial release.
6
+
7
+ - `configurable_columns` DSL to declare the columns an admin may pick from.
8
+ - `configurable_columns` index-table renderer that shows the columns the
9
+ current admin chose, falling back to the declared defaults.
10
+ - "Columns" picker in the sidebar of every configurable index.
11
+ - "Table columns" admin page to review and change every table at once.
12
+ - Per-admin persistence in the `activeadmin_configurable_columns_preferences`
13
+ table, with a pluggable preference store.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Kyryl Oliinyk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,170 @@
1
+ # activeadmin_configurable_columns
2
+
3
+ [![CI](https://github.com/kyryloliinyk/activeadmin_configurable_columns/actions/workflows/ci.yml/badge.svg)](https://github.com/kyryloliinyk/activeadmin_configurable_columns/actions/workflows/ci.yml)
4
+
5
+ Per-admin configurable index table columns for [ActiveAdmin](https://activeadmin.info) 4.
6
+
7
+ ActiveAdmin has no built-in way to let each admin choose which columns of an
8
+ index table they see. This gem adds one, built on ActiveAdmin's own extension
9
+ points:
10
+
11
+ - a `configurable_columns do ... end` DSL declares the columns an admin may pick from;
12
+ - `configurable_columns` inside an `index` block renders the ones the current admin chose;
13
+ - a "Columns" picker appears in the sidebar of every configurable index;
14
+ - a "Table columns" admin page lets an admin review and change every table at once;
15
+ - each admin's choice is stored in the database, per admin and per table.
16
+
17
+ ## Requirements
18
+
19
+ - Ruby >= 3.1
20
+ - Rails >= 7.1
21
+ - ActiveAdmin 4.0 (beta15 or later)
22
+
23
+ ## Installation
24
+
25
+ Add the gem to your Gemfile:
26
+
27
+ ```ruby
28
+ gem 'activeadmin_configurable_columns'
29
+ ```
30
+
31
+ Install the migration and migrate:
32
+
33
+ ```sh
34
+ bin/rails activeadmin_configurable_columns:install:migrations
35
+ bin/rails db:migrate
36
+ ```
37
+
38
+ This creates the `activeadmin_configurable_columns_preferences` table. The
39
+ `admin` reference is polymorphic with a string id, so it works with any
40
+ current-user model and any primary key type (integer, uuid, ...).
41
+
42
+ ## Usage
43
+
44
+ Declare the available columns in an ActiveAdmin registration, and render them
45
+ in the index:
46
+
47
+ ```ruby
48
+ ActiveAdmin.register Product do
49
+ configurable_columns do
50
+ column :brand
51
+ column :sku, label: 'SKU'
52
+ column(:qty, sortable: 'stock_items_quantity') { |product| product.total_quantity }
53
+ column :notes, default: false # available, but hidden until picked
54
+ end
55
+
56
+ index do
57
+ selectable_column
58
+ configurable_columns
59
+ actions
60
+ end
61
+ end
62
+ ```
63
+
64
+ `column` mirrors the arguments of ActiveAdmin's own `column`, so a declaration
65
+ reads the same as the index DSL it replaces:
66
+
67
+ | Argument | Meaning |
68
+ | --- | --- |
69
+ | `key` | Identifies the column in the picker and in stored preferences. Also the rendered attribute unless `attribute:` or a block says otherwise. |
70
+ | `label:` | Column header and checkbox label. Defaults to the model's `human_attribute_name`. |
71
+ | `default:` | Whether the column is visible for admins who never picked anything. Defaults to `true`. |
72
+ | `attribute:` | The attribute to render when it differs from the key. |
73
+ | a block | Renders the cell. The block is evaluated against the index table, so view helpers work, exactly as in a regular `index` column. |
74
+ | anything else | Passed through to ActiveAdmin's `column` (e.g. `sortable:`, `class:`). |
75
+
76
+ Every resource that declares `configurable_columns` gets a "Columns" sidebar
77
+ section on its index, and a card on the "Table columns" page. Selections are
78
+ saved per admin; unknown or stale keys are dropped on read and write, and an
79
+ empty selection falls back to the declared defaults, so an admin can never end
80
+ up with an empty table.
81
+
82
+ ## Configuration
83
+
84
+ All settings are optional. In `config/initializers/activeadmin_configurable_columns.rb`:
85
+
86
+ ```ruby
87
+ ActiveadminConfigurableColumns.configure do |config|
88
+ # Options for the "Table columns" page's `menu` call, or false to keep the
89
+ # page out of the navigation. Default: a translated label, top level.
90
+ config.menu = { parent: 'Settings', priority: 0 }
91
+
92
+ # Priority of the "Columns" sidebar section. ActiveAdmin's filters sidebar
93
+ # uses 10; the default (20) renders right below it.
94
+ config.sidebar_priority = 20
95
+
96
+ # Where selections are read from and written to. Any object answering
97
+ # visible_column_keys(admin, resource_key) -> [Symbol] | nil (nil = never saved)
98
+ # store(admin:, resource_key:, keys:)
99
+ # can be plugged in. Default: the bundled Preference model.
100
+ config.preference_store = MyOwnStore
101
+ end
102
+ ```
103
+
104
+ The current admin is whatever ActiveAdmin's `current_active_admin_user`
105
+ returns, so the gem follows your `config.current_user_method` setting.
106
+
107
+ ## Translations
108
+
109
+ The gem ships English strings under standard ActiveAdmin keys; override them
110
+ per locale in your application:
111
+
112
+ ```yaml
113
+ en:
114
+ active_admin:
115
+ sidebars:
116
+ columns: "Columns"
117
+ table_columns:
118
+ title: "Table columns"
119
+ save: "Save columns"
120
+ saved: "Columns saved"
121
+ empty: "No table has configurable columns yet."
122
+ unknown_resource: "Unknown table"
123
+ ```
124
+
125
+ Column checkbox labels come from `human_attribute_name`, so your existing
126
+ Active Record attribute translations apply.
127
+
128
+ ## Tailwind CSS
129
+
130
+ ActiveAdmin 4 builds its stylesheet with Tailwind, which only emits classes it
131
+ finds in scanned sources. Add the gem's views to your Tailwind entrypoint (or
132
+ config) so the picker and the "Table columns" page keep their layout:
133
+
134
+ ```css
135
+ @source "<path to the installed gem>/app/views/**/*.{arb,erb,html,rb}";
136
+ ```
137
+
138
+ If a rake task generates your ActiveAdmin Tailwind entrypoint, resolve the path
139
+ with `Gem.loaded_specs['activeadmin_configurable_columns'].full_gem_path`.
140
+
141
+ ## How it works
142
+
143
+ - `ActiveadminConfigurableColumns::Dsl` is mixed into `ActiveAdmin::ResourceDSL`;
144
+ `configurable_columns do ... end` builds a `Registry` of `Column`s, stores it on
145
+ the resource, and adds the sidebar section.
146
+ - `ActiveadminConfigurableColumns::TableRenderer` is mixed into
147
+ `ActiveAdmin::Views::IndexAsTable::IndexTableFor`; `configurable_columns` inside
148
+ `index` looks up the current admin's saved keys and emits the matching columns.
149
+ - The "Table columns" page is a regular `ActiveAdmin.register_page`, loaded
150
+ through ActiveAdmin's own load paths; its update action also serves the
151
+ sidebar forms.
152
+ - Rendering an index whose resource never declared its columns raises
153
+ `ActiveadminConfigurableColumns::NotDeclared` with a hint, rather than an
154
+ empty table.
155
+
156
+ ## Development
157
+
158
+ ```sh
159
+ bundle install
160
+ bundle exec rspec
161
+ ```
162
+
163
+ The test suite is standalone: it boots a dummy Rails application
164
+ (`spec/dummy`) with an in-memory sqlite database and a real ActiveAdmin
165
+ registration, and exercises the gem's migration on every run.
166
+
167
+ ## License
168
+
169
+ The gem is available as open source under the terms of the
170
+ [MIT License](LICENSE.txt).
@@ -0,0 +1,47 @@
1
+ module ActiveadminConfigurableColumns
2
+ # One row per admin per ActiveAdmin index table, holding the columns that
3
+ # admin chose to see there. The available columns themselves are declared in
4
+ # the ActiveAdmin registration with the `configurable_columns` DSL.
5
+ #
6
+ # `admin` is polymorphic and its id column is a string, so any current-user
7
+ # model works, whatever its class and primary key type (integer, uuid, ...).
8
+ class Preference < ::ActiveRecord::Base
9
+ self.table_name = 'activeadmin_configurable_columns_preferences'
10
+
11
+ belongs_to :admin, polymorphic: true
12
+
13
+ # Stored as JSON text rather than a database-specific json type, so the
14
+ # table works the same on every Active Record adapter. The array shape is
15
+ # enforced by the validation below, not by the coder, so a bad assignment
16
+ # surfaces as a validation error instead of a serialization exception.
17
+ serialize :visible_columns, coder: JSON
18
+
19
+ after_initialize { self.visible_columns ||= [] if has_attribute?(:visible_columns) }
20
+
21
+ validates :resource_key, presence: true,
22
+ uniqueness: { scope: %i[admin_type admin_id], case_sensitive: false }
23
+ validate :visible_columns_must_be_an_array
24
+
25
+ # Returns the keys this admin picked for the table, or nil when they never
26
+ # saved anything (the caller then falls back to the declared defaults).
27
+ def self.visible_column_keys(admin, resource_key)
28
+ return nil if admin.blank?
29
+
30
+ find_by(admin: admin, resource_key: resource_key)&.visible_columns&.map(&:to_sym)
31
+ end
32
+
33
+ def self.store(admin:, resource_key:, keys:)
34
+ preference = find_or_initialize_by(admin: admin, resource_key: resource_key)
35
+ preference.update!(visible_columns: keys.map(&:to_s))
36
+ preference
37
+ end
38
+
39
+ private
40
+
41
+ def visible_columns_must_be_an_array
42
+ return if visible_columns.is_a?(Array)
43
+
44
+ errors.add(:visible_columns, :invalid)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,25 @@
1
+ <%# Checkbox picker for one index table. Rendered both in the index sidebar and
2
+ on the "Table columns" page, so it always posts back to the same action. %>
3
+ <% registry = resource.column_registry %>
4
+ <% selected = ActiveadminConfigurableColumns.visible_columns(registry, current_active_admin_user).map(&:key) %>
5
+ <% update_path = send(ActiveadminConfigurableColumns.route_name(active_admin_namespace, :update)) %>
6
+
7
+ <%= form_with url: update_path, method: :patch, class: 'filters-form' do %>
8
+ <%= hidden_field_tag :resource_key, registry.resource_key, id: nil %>
9
+ <%# Keeps the parameter present when every box is unchecked, which restores the defaults. %>
10
+ <%= hidden_field_tag 'visible_columns[]', '', id: nil %>
11
+
12
+ <div class="flex flex-col gap-2 mb-4">
13
+ <% registry.columns.each do |column| %>
14
+ <% checkbox_id = "#{registry.resource_key}_column_#{column.key}" %>
15
+ <div class="flex items-center gap-2">
16
+ <%= check_box_tag 'visible_columns[]', column.key, selected.include?(column.key), id: checkbox_id %>
17
+ <%= label_tag checkbox_id, column.human_label(resource.resource_class), class: 'text-sm' %>
18
+ </div>
19
+ <% end %>
20
+ </div>
21
+
22
+ <div class="filters-form-buttons">
23
+ <%= submit_tag t('active_admin.table_columns.save'), class: 'filters-form-submit' %>
24
+ </div>
25
+ <% end %>
@@ -0,0 +1,16 @@
1
+ <% if resources.empty? %>
2
+ <div class="panel">
3
+ <div class="panel-body"><%= t('active_admin.table_columns.empty') %></div>
4
+ </div>
5
+ <% else %>
6
+ <div class="grid gap-6 md:grid-cols-2 xl:grid-cols-3">
7
+ <% resources.each do |resource| %>
8
+ <div class="panel">
9
+ <h3 class="panel-title"><%= resource.plural_resource_label %></h3>
10
+ <div class="panel-body">
11
+ <%= render 'activeadmin_configurable_columns/form', resource: resource %>
12
+ </div>
13
+ </div>
14
+ <% end %>
15
+ </div>
16
+ <% end %>
@@ -0,0 +1,2 @@
1
+ <h3 class="filters-form-title"><%= t('active_admin.sidebars.columns') %></h3>
2
+ <%= render 'activeadmin_configurable_columns/form', resource: resource %>
@@ -0,0 +1,10 @@
1
+ en:
2
+ active_admin:
3
+ sidebars:
4
+ columns: "Columns"
5
+ table_columns:
6
+ title: "Table columns"
7
+ save: "Save columns"
8
+ saved: "Columns saved"
9
+ empty: "No table has configurable columns yet."
10
+ unknown_resource: "Unknown table"
@@ -0,0 +1,18 @@
1
+ class CreateActiveadminConfigurableColumnsPreferences < ActiveRecord::Migration[7.1]
2
+ def change
3
+ create_table :activeadmin_configurable_columns_preferences do |t|
4
+ # A string id fits every primary key type a current-user model may have.
5
+ t.string :admin_type, null: false
6
+ t.string :admin_id, null: false
7
+ t.string :resource_key, null: false
8
+ # JSON-serialized array of column keys; plain text so every adapter works.
9
+ t.text :visible_columns
10
+ t.timestamps
11
+ end
12
+
13
+ add_index :activeadmin_configurable_columns_preferences,
14
+ %i[admin_type admin_id resource_key],
15
+ unique: true,
16
+ name: 'index_aacc_preferences_on_admin_and_resource_key'
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ # The "Table columns" page: a central place to review and change visible table
2
+ # columns at once. The same form is rendered in the "Columns" sidebar of every
3
+ # configurable index, and both post back to the page's update action.
4
+ ActiveAdmin.register_page 'Table columns' do
5
+ menu ActiveadminConfigurableColumns.menu
6
+
7
+ content title: proc { I18n.t('active_admin.table_columns.title') } do
8
+ resources = ActiveadminConfigurableColumns.resources(active_admin_namespace).select do |resource|
9
+ authorized?(ActiveAdmin::Authorization::READ, resource.resource_class)
10
+ end
11
+
12
+ render 'activeadmin_configurable_columns/page', resources: resources
13
+ end
14
+
15
+ page_action :update, method: :patch do
16
+ page_path = send(ActiveadminConfigurableColumns.route_name(active_admin_namespace))
17
+ resource = ActiveadminConfigurableColumns.resource_for(active_admin_namespace, params[:resource_key])
18
+
19
+ if resource.nil?
20
+ redirect_back fallback_location: page_path,
21
+ status: :see_other,
22
+ alert: t('active_admin.table_columns.unknown_resource')
23
+ else
24
+ registry = resource.column_registry
25
+
26
+ ActiveadminConfigurableColumns.preference_store.store(
27
+ admin: current_active_admin_user,
28
+ resource_key: registry.resource_key,
29
+ keys: registry.selected_keys(params[:visible_columns])
30
+ )
31
+
32
+ redirect_back fallback_location: page_path,
33
+ status: :see_other,
34
+ notice: t('active_admin.table_columns.saved')
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,49 @@
1
+ module ActiveadminConfigurableColumns
2
+ # A single column an admin may show or hide. Mirrors the arguments of
3
+ # ActiveAdmin's `column` so a declaration reads the same as the index DSL it
4
+ # replaces.
5
+ class Column
6
+ attr_reader :key, :attribute, :label, :options, :block
7
+
8
+ def initialize(key, label: nil, default: true, attribute: nil, block: nil, **options)
9
+ @key = key.to_sym
10
+ @attribute = (attribute || key).to_sym
11
+ @label = label
12
+ @default = default
13
+ @options = options
14
+ @block = block
15
+ end
16
+
17
+ def default? = @default
18
+
19
+ # Emits the column into an ActiveAdmin index table.
20
+ def render_into(table)
21
+ args = []
22
+ args << label if label
23
+ args << attribute unless label && block
24
+ args << options
25
+
26
+ table.column(*args, &cell_renderer(table))
27
+ end
28
+
29
+ # ActiveAdmin renders a column block with a plain `block.call(resource)`
30
+ # (DisplayHelper#find_value), so the block runs with whatever `self` it captured
31
+ # where it was written. Written inside an `index` block that is the view, which is
32
+ # how view helpers resolve. Ours is written inside a `configurable_columns` block,
33
+ # evaluated against the Builder, so it has to be rebound to the table at render
34
+ # time -- otherwise a helper call in a column block raises NoMethodError.
35
+ def cell_renderer(table)
36
+ return nil unless block
37
+
38
+ proc { |resource| table.instance_exec(resource, &block) }
39
+ end
40
+
41
+ # Human name for the checkbox in the settings form.
42
+ def human_label(resource_class)
43
+ return label if label
44
+ return attribute.to_s.humanize unless resource_class.respond_to?(:human_attribute_name)
45
+
46
+ resource_class.human_attribute_name(attribute, default: attribute.to_s.humanize)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,36 @@
1
+ module ActiveadminConfigurableColumns
2
+ # Mixed into ActiveAdmin::ResourceDSL. Declares the columns an admin may pick
3
+ # from, and adds the "Columns" sidebar section to the index page:
4
+ #
5
+ # ActiveAdmin.register Product do
6
+ # configurable_columns do
7
+ # column :brand
8
+ # column :qty, sortable: 'stock_items_quantity' { |p| p.total_quantity }
9
+ # column :notes, default: false
10
+ # end
11
+ #
12
+ # index do
13
+ # selectable_column
14
+ # configurable_columns
15
+ # actions
16
+ # end
17
+ # end
18
+ module Dsl
19
+ def configurable_columns(&block)
20
+ config.column_registry = Registry.build(config.resource_name.route_key, &block)
21
+ config.sidebar_sections << columns_sidebar_section
22
+ end
23
+
24
+ private
25
+
26
+ def columns_sidebar_section
27
+ ActiveAdmin::SidebarSection.new(
28
+ :columns,
29
+ only: :index,
30
+ priority: ActiveadminConfigurableColumns.sidebar_priority
31
+ ) do
32
+ render 'activeadmin_configurable_columns/sidebar', resource: active_admin_config
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,21 @@
1
+ module ActiveadminConfigurableColumns
2
+ # Wires the library into the host application: autoloads the Preference model,
3
+ # adds the gem's views and locales, and teaches ActiveAdmin the DSL before the
4
+ # application's app/admin files load (ActiveAdmin loads them lazily, long after
5
+ # the initializers ran).
6
+ class Engine < ::Rails::Engine
7
+ engine_name 'activeadmin_configurable_columns'
8
+
9
+ initializer 'activeadmin_configurable_columns.active_admin' do
10
+ ActiveAdmin::Resource.include ResourceExtension
11
+ ActiveAdmin::ResourceDSL.include Dsl
12
+ ActiveAdmin::Views::IndexAsTable::IndexTableFor.include TableRenderer
13
+
14
+ # The "Table columns" page is a regular ActiveAdmin page registration, not
15
+ # a constant, so it lives outside the autoload paths and loads through
16
+ # ActiveAdmin's own load paths, together with the application's app/admin.
17
+ ActiveAdmin.application.load_paths <<
18
+ Engine.root.join('lib', 'activeadmin_configurable_columns', 'admin').to_s
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,46 @@
1
+ module ActiveadminConfigurableColumns
2
+ # The columns declared for one ActiveAdmin resource, in declaration order.
3
+ class Registry
4
+ attr_reader :resource_key, :columns
5
+
6
+ def self.build(resource_key, &block)
7
+ builder = Builder.new
8
+ builder.instance_eval(&block)
9
+ new(resource_key, builder.columns)
10
+ end
11
+
12
+ def initialize(resource_key, columns)
13
+ @resource_key = resource_key.to_s
14
+ @columns = columns.freeze
15
+ end
16
+
17
+ def keys = columns.map(&:key)
18
+
19
+ def default_keys = columns.select(&:default?).map(&:key)
20
+
21
+ # Declared columns matching the given keys, in declaration order. Unknown or
22
+ # stale keys are dropped; a blank selection falls back to the defaults, so an
23
+ # admin can never end up with an empty table.
24
+ def columns_for(keys)
25
+ wanted = Array(keys).map(&:to_sym)
26
+ wanted = default_keys if (wanted & self.keys).empty?
27
+
28
+ columns.select { |column| wanted.include?(column.key) }
29
+ end
30
+
31
+ def selected_keys(keys) = columns_for(keys).map(&:key)
32
+
33
+ # Collects `column` declarations from a `configurable_columns` block.
34
+ class Builder
35
+ attr_reader :columns
36
+
37
+ def initialize
38
+ @columns = []
39
+ end
40
+
41
+ def column(key, **options, &block)
42
+ @columns << Column.new(key, block: block, **options)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,10 @@
1
+ module ActiveadminConfigurableColumns
2
+ # Mixed into ActiveAdmin::Resource so every registered resource can carry the
3
+ # registry declared for it. Resources are rebuilt whenever app/admin is
4
+ # reloaded, so the registry never outlives the code that declared it.
5
+ module ResourceExtension
6
+ attr_accessor :column_registry
7
+
8
+ def configurable_columns? = !column_registry.nil?
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ module ActiveadminConfigurableColumns
2
+ # Mixed into ActiveAdmin::Views::IndexAsTable::IndexTableFor, so that
3
+ # `configurable_columns` inside an `index` block renders the columns the
4
+ # current admin chose (falling back to the declared defaults).
5
+ module TableRenderer
6
+ def configurable_columns
7
+ registry = active_admin_config.column_registry
8
+
9
+ if registry.nil?
10
+ raise ActiveadminConfigurableColumns::NotDeclared, active_admin_config.resource_name.to_s
11
+ end
12
+
13
+ ActiveadminConfigurableColumns
14
+ .visible_columns(registry, helpers.current_active_admin_user)
15
+ .each { |column| column.render_into(self) }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveadminConfigurableColumns
2
+ VERSION = '1.0.0'.freeze
3
+ end
@@ -0,0 +1,98 @@
1
+ require 'active_admin'
2
+
3
+ require_relative 'activeadmin_configurable_columns/version'
4
+
5
+ # Per-admin index table columns for ActiveAdmin.
6
+ #
7
+ # ActiveAdmin has no built-in way to let each admin choose which columns of an
8
+ # index table they see. This gem adds one, built on ActiveAdmin's own extension
9
+ # points:
10
+ #
11
+ # * ResourceDSL -- `configurable_columns` declares the available columns
12
+ # * IndexTableFor -- `configurable_columns` renders the chosen ones
13
+ # * SidebarSection -- the picker next to every configurable index table
14
+ #
15
+ # The choice is stored per admin by the preference store, which defaults to the
16
+ # bundled ActiveadminConfigurableColumns::Preference model.
17
+ module ActiveadminConfigurableColumns
18
+ # ActiveAdmin renders the filters sidebar section with priority 10; the
19
+ # default puts the "Columns" picker right below it.
20
+ DEFAULT_SIDEBAR_PRIORITY = 20
21
+
22
+ # Raised when an index renders `configurable_columns` without a matching
23
+ # `configurable_columns do ... end` declaration.
24
+ class NotDeclared < StandardError
25
+ def initialize(resource_name)
26
+ super("#{resource_name} renders configurable_columns but never declares them. " \
27
+ 'Add a `configurable_columns do ... end` block to its ActiveAdmin registration.')
28
+ end
29
+ end
30
+
31
+ class << self
32
+ def configure
33
+ yield self
34
+ end
35
+
36
+ # Where saved selections live. Any object answering
37
+ #
38
+ # visible_column_keys(admin, resource_key) -> [Symbol] | nil (nil = never saved)
39
+ # store(admin:, resource_key:, keys:) (persists a selection)
40
+ #
41
+ # can be plugged in; the default is the bundled Preference model. Assigning
42
+ # nil restores that default.
43
+ attr_writer :preference_store
44
+
45
+ def preference_store = @preference_store || Preference
46
+
47
+ # Priority of the "Columns" sidebar section. Assigning nil restores the default.
48
+ attr_writer :sidebar_priority
49
+
50
+ def sidebar_priority = @sidebar_priority || DEFAULT_SIDEBAR_PRIORITY
51
+
52
+ # Options passed to `menu` for the "Table columns" page (assign false to keep
53
+ # the page out of the menu entirely). Assigning nil restores the default.
54
+ attr_writer :menu
55
+
56
+ def menu
57
+ return @menu unless @menu.nil?
58
+
59
+ { label: proc { I18n.t('active_admin.table_columns.title') } }
60
+ end
61
+
62
+ # The columns the given admin sees for this resource.
63
+ def visible_columns(registry, admin)
64
+ saved = preference_store.visible_column_keys(admin, registry.resource_key)
65
+
66
+ registry.columns_for(saved || registry.default_keys)
67
+ end
68
+
69
+ # Every resource of the namespace that declares configurable columns.
70
+ def resources(namespace)
71
+ namespace.resources.select do |resource|
72
+ resource.respond_to?(:configurable_columns?) && resource.configurable_columns?
73
+ end
74
+ end
75
+
76
+ def resource_for(namespace, resource_key)
77
+ resources(namespace).find { |resource| resource.column_registry.resource_key == resource_key.to_s }
78
+ end
79
+
80
+ # Name of a route helper for the "Table columns" page in the given namespace:
81
+ #
82
+ # route_name(admin_namespace) # => 'admin_table_columns_path'
83
+ # route_name(admin_namespace, :update) # => 'admin_table_columns_update_path'
84
+ #
85
+ # The namespace prefix is looked up rather than hardcoded, so the page works
86
+ # in any ActiveAdmin namespace, including the root one.
87
+ def route_name(namespace, action = nil)
88
+ [namespace.route_prefix, 'table_columns', action, 'path'].compact.join('_')
89
+ end
90
+ end
91
+ end
92
+
93
+ require_relative 'activeadmin_configurable_columns/column'
94
+ require_relative 'activeadmin_configurable_columns/registry'
95
+ require_relative 'activeadmin_configurable_columns/dsl'
96
+ require_relative 'activeadmin_configurable_columns/resource_extension'
97
+ require_relative 'activeadmin_configurable_columns/table_renderer'
98
+ require_relative 'activeadmin_configurable_columns/engine'
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activeadmin_configurable_columns
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Kyryl Oliinyk
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activeadmin
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 4.0.0.beta15
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '4.1'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: 4.0.0.beta15
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '4.1'
32
+ - !ruby/object:Gem::Dependency
33
+ name: activerecord
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '7.1'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '7.1'
46
+ - !ruby/object:Gem::Dependency
47
+ name: railties
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '7.1'
53
+ type: :runtime
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '7.1'
60
+ description: Lets every admin choose which columns of an ActiveAdmin index table they
61
+ see. Adds a `configurable_columns` DSL to declare the available columns, renders
62
+ a "Columns" picker in the index sidebar and on a central "Table columns" page, and
63
+ stores each admin's choice in the database.
64
+ email:
65
+ - kyryloliinyk@gmail.com
66
+ executables: []
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - CHANGELOG.md
71
+ - LICENSE.txt
72
+ - README.md
73
+ - app/models/activeadmin_configurable_columns/preference.rb
74
+ - app/views/activeadmin_configurable_columns/_form.html.erb
75
+ - app/views/activeadmin_configurable_columns/_page.html.erb
76
+ - app/views/activeadmin_configurable_columns/_sidebar.html.erb
77
+ - config/locales/en.yml
78
+ - db/migrate/20260930000001_create_activeadmin_configurable_columns_preferences.rb
79
+ - lib/activeadmin_configurable_columns.rb
80
+ - lib/activeadmin_configurable_columns/admin/table_columns.rb
81
+ - lib/activeadmin_configurable_columns/column.rb
82
+ - lib/activeadmin_configurable_columns/dsl.rb
83
+ - lib/activeadmin_configurable_columns/engine.rb
84
+ - lib/activeadmin_configurable_columns/registry.rb
85
+ - lib/activeadmin_configurable_columns/resource_extension.rb
86
+ - lib/activeadmin_configurable_columns/table_renderer.rb
87
+ - lib/activeadmin_configurable_columns/version.rb
88
+ homepage: https://github.com/kyryloliinyk/activeadmin_configurable_columns
89
+ licenses:
90
+ - MIT
91
+ metadata:
92
+ source_code_uri: https://github.com/kyryloliinyk/activeadmin_configurable_columns
93
+ changelog_uri: https://github.com/kyryloliinyk/activeadmin_configurable_columns/blob/main/CHANGELOG.md
94
+ bug_tracker_uri: https://github.com/kyryloliinyk/activeadmin_configurable_columns/issues
95
+ rubygems_mfa_required: 'true'
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '3.1'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubygems_version: 4.0.19
111
+ specification_version: 4
112
+ summary: Per-admin configurable index table columns for ActiveAdmin 4.
113
+ test_files: []