| name | rails-admin-scaffold |
| description | Generate a full-featured CRUD admin panel for Rails 6.1+ applications with auto-detection of CSS frameworks, pagination gems, and smart field mapping |
| disable-model-invocation | true |
Admin Scaffold Generator
Generate a complete admin panel with CRUD operations, filtering, pagination, dashboard, exports, and bulk actions for any Rails 6.1+ application.
Overview
This skill creates a fully functional admin interface by:
- Auto-detecting your project's CSS framework, pagination gem, and test framework
- Analyzing all models for fields, associations, enums, and attachments
- Asking targeted questions to customize the output
- Generating controllers, views, routes, and optional tests
- Providing clear next steps for verification
Phase 1: Detection
Before asking any questions, automatically detect the project configuration.
1.1 Detect Existing Admin Dashboards
Scan for existing admin implementations and identify which paths/namespaces they use.
Read config/routes.rb and extract:
Store results as:
existing_admins = [
{ type: "rails_admin", path: "/admin" },
{ type: "activeadmin", path: "/backend" },
{ type: "custom", path: "/management" }
]
taken_namespaces = ["admin", "backend", "management"]
Common namespace options to check:
admin, backend, dashboard, management, panel, console, staff
1.2 Detect CSS Framework
Read these files to determine CSS framework:
Detection logic:
- Read
tailwind.config.js or config/tailwind.config.js → Tailwind
- Read
Gemfile for gem 'bootstrap' or gem 'cssbundling-rails' + package.json for bootstrap → Bootstrap
- Read
Gemfile or package.json for bulma → Bulma
Store result as: css_framework = "tailwind" | "bootstrap" | "bulma"
1.3 Detect Pagination Gem
Read Gemfile:
gem 'pagy'
gem 'kaminari'
gem 'will_paginate'
1.4 Detect Test Framework
1.5 Detect Stimulus
1.6 Detect File Uploader
Check Gemfile and model files for file upload solution:
gem 'activestorage'
gem 'carrierwave'
gem 'shrine'
gem 'paperclip'
gem 'dragonfly'
has_one_attached
has_many_attached
mount_uploader
include ImageUploader
has_attached_file
dragonfly_accessor
Preview syntax by uploader:
<%# Active Storage %>
<%% if record.avatar.attached? %>
<%%= image_tag record.avatar.variant(resize_to_limit: [100, 100]) %>
<%% end %>
<%# CarrierWave %>
<%% if record.avatar.present? %>
<%%= image_tag record.avatar.thumb.url %>
<%% end %>
<%# Shrine %>
<%% if record.avatar_data.present? %>
<%%= image_tag record.avatar_url(:thumb) %>
<%% end %>
<%# Paperclip (legacy) %>
<%% if record.avatar.present? %>
<%%= image_tag record.avatar.url(:thumb) %>
<%% end %>
<%# Dragonfly %>
<%% if record.avatar_stored? %>
<%%= image_tag record.avatar.thumb('100x100#').url %>
<%% end %>
Form input syntax by uploader:
<%# Active Storage %>
<%%= form.file_field :avatar, accept: "image/*", class: "{CSS: file-input}" %>
<%% if record.avatar.attached? %>
<div class="{CSS: file-preview}">
<%%= image_tag record.avatar.variant(resize_to_limit: [200, 200]) %>
<%%= form.check_box :remove_avatar, label: "Remove" %>
</div>
<%% end %>
<%# CarrierWave %>
<%%= form.file_field :avatar, class: "{CSS: file-input}" %>
<%% if record.avatar.present? %>
<div class="{CSS: file-preview}">
<%%= image_tag record.avatar.thumb.url %>
<%%= form.check_box :remove_avatar %>
</div>
<%% end %>
<%# Shrine %>
<%%= form.hidden_field :avatar, value: record.cached_avatar_data %>
<%%= form.file_field :avatar, class: "{CSS: file-input}" %>
<%# Paperclip %>
<%%= form.file_field :avatar, class: "{CSS: file-input}" %>
<%# Dragonfly %>
<%%= form.file_field :avatar, class: "{CSS: file-input}" %>
<%%= form.hidden_field :retained_avatar %>
1.7 Discover Models
ls app/models/*.rb
Parse each model file to extract:
- Model name (from filename and class definition)
- Skip abstract models, concerns, ApplicationRecord
Store as: models = ["User", "Post", "Comment", ...]
1.7 Detect Existing Admin Panels
Check for existing admin implementations to understand current patterns:
gem 'activeadmin'
gem 'rails_admin'
gem 'administrate'
If existing admin gem found:
-
ActiveAdmin - Check app/admin/*.rb for:
- Custom form blocks
- Custom show blocks
- Filters configuration
- Batch actions
- Sidebar sections
-
RailsAdmin - Check config/initializers/rails_admin.rb for:
- Custom actions
- Field configurations
- Navigation labels
-
Administrate - Check app/dashboards/*_dashboard.rb for:
- COLLECTION_ATTRIBUTES
- SHOW_PAGE_ATTRIBUTES
- FORM_ATTRIBUTES
If custom admin exists (app/controllers/admin/ without gems):
Analyze existing controllers and views:
ls app/controllers/admin/*_controller.rb
ls -la app/views/admin/*/
For each existing admin resource, extract:
- Custom actions (beyond standard CRUD)
- Custom filters or scopes
- Special display logic in show views
- Custom form fields or nested forms
- Bulk actions
- Export functionality
Store as:
existing_admin_features = {
"User" => {
custom_actions: ["impersonate", "ban", "export_csv"],
custom_filters: ["by_role", "by_status", "date_range"],
nested_forms: ["profile", "addresses"],
show_sections: ["activity_log", "permissions"],
bulk_actions: ["activate", "deactivate"]
},
}
Phase 2: Model Analysis
For each discovered model, extract detailed information.
2.1 Schema Information
Look for Annotate gem comments at top of model:
If no annotation, read db/schema.rb for the table definition.
2.2 Extract Field Types
Map each column to form input and display type using reference/field-mappings.md.
2.3 Associations
Look for:
belongs_to :author, class_name: 'User'
has_many :comments
has_one :profile
has_and_belongs_to_many :tags
2.4 Enums
enum status: { draft: 0, published: 1, archived: 2 }
enum :role, { admin: 0, user: 1 }, prefix: true
2.5 Active Storage Attachments
has_one_attached :avatar
has_many_attached :images
2.6 Ransack Methods (if existing)
def self.ransackable_attributes(auth_object = nil)
["email", "name", "created_at"]
end
def self.ransackable_associations(auth_object = nil)
["posts", "comments"]
end
2.7 Soft Delete Detection
acts_as_paranoid
include Discard::Model
Store soft_delete info: soft_delete = "paranoia" | "discard" | nil
Phase 3: Interactive Questions
Use the AskUserQuestion tool with these questions:
Question 1: Namespace
Build the question dynamically based on detected existing admins:
If existing admin dashboards were detected:
First, inform the user what was found:
Detected existing admin dashboard(s):
- Rails Admin at /admin
- Custom admin at /backend
Then ask with only available (non-conflicting) options:
{
"question": "Which namespace should the new admin panel use?",
"header": "Namespace",
"options": [
{"label": "dashboard (Recommended)", "description": "Use /dashboard path"},
{"label": "management", "description": "Use /management path"},
{"label": "panel", "description": "Use /panel path"}
],
If no existing admin dashboards detected:
{
"question": "What namespace should the admin panel use?",
"header": "Namespace",
"options": [
{"label": "admin (Recommended)", "description": "Standard /admin path"},
{"label": "backend", "description": "Alternative /backend path"},
{"label": "dashboard", "description": "Use /dashboard path"},
{"label": "management", "description": "Use /management path"}
],
"multiSelect": false
Available namespace pool (in priority order):
admin, backend, dashboard, management, panel, console, staff
If user selects "Other", use their custom namespace.
Question 2: Models to Exclude
Before asking, analyze models and categorize them:
technical_models = [
"ApplicationRecord",
]
join_models = models.select do |m|
columns = m.column_names - %w[id created_at updated_at]
columns.all? { |c| c.ends_with?('_id') }
end
empty_models = models.select { |m| m.count == 0 }
nested_models = models.select do |m|
end
primary_models = models - technical_models - join_models - empty_models - nested_models
Build dynamic options based on detection:
{
"question": "Which models should be EXCLUDED from the admin panel?",
"header": "Exclude",
"options": [
{
"label": "Smart exclude (Recommended)",
"description": "Include {N} primary models, exclude {M} technical/join/empty models"
},
{
"label": "Include all {total} models",
"description": "Generate admin for every model including join tables"
},
{
"label": "Select specific models",
"description": "I'll review and choose which to exclude"
}
],
"multiSelect": false
}
If "Smart exclude" selected, show summary:
Including {N} primary models:
User, Post, Comment, Order, Product, ...
Excluding {M} models:
Technical: ApplicationRecord
Join tables: PostTag, UserRole
Empty tables: LiquidTemplate, FeatureFlag
Nested (manage via parent): CandidateEmail, CandidatePhone, UserAddress
You can manage nested models through their parent's edit form with nested attributes.
If "Select specific models", show multiSelect with categories:
{
"question": "Select models to EXCLUDE from admin:",
"header": "Exclude",
"options": [
{"label": "ApplicationRecord", "description": "technical ✓"},
{"label": "PostTag", "description": "join table ✓"},
{"label": "LiquidTemplate", "description": "empty (0 records) ✓"},
{"label": "CandidatePhone", "description": "nested → Candidate ✓"},
{"label": "User"
Question 3: Model Configuration Mode
{
"question": "How do you want to configure fields and views for each model?",
"header": "Config",
"options": [
{"label": "Smart defaults (Recommended)", "description": "Auto-detect fields, hide sensitive data, full CRUD for all"},
{"label": "Configure each model", "description": "I'll specify fields and views for each model individually"}
],
"multiSelect": false
}
If "Smart defaults":
- All included models get full CRUD (index, show, new, edit, delete)
- Show all fields except sensitive ones (passwords, tokens, secrets)
- Auto-generate filters for searchable fields
- Skip to Question 4 (Authentication)
If "Configure each model":
For each included model, ask the following questions:
3a. Model View Type
{
"question": "What views should {Model} have?",
"header": "{Model}",
"options": [
{"label": "Full CRUD", "description": "Index, show, new, edit, delete"},
{"label": "Read-only", "description": "Index and show only, no editing"},
{"label": "Skip", "description": "Don't generate admin for this model"}
],
"multiSelect": false
}
3b. Index/Table Columns
{
"question": "Which fields should appear in the {Model} table (index view)?",
"header": "Table",
"options": [
{"label": "id", "description": "bigint"},
{"label": "name", "description": "string ✓"},
{"label": "email", "description": "string ✓"},
{"label": "status", "description": "enum ✓"},
{"label"
3c. Show Page Fields
{
"question": "Which fields should appear on the {Model} show page?",
"header": "Show",
"options": [
{"label": "id", "description": "bigint ✓"},
{"label": "name", "description": "string ✓"},
{"label": "email", "description": "string ✓"},
{"label": "bio", "description": "text ✓"},
{"label": "created_at"
3d. Form Fields (only if Full CRUD selected)
{
"question": "Which fields should be editable in the {Model} form?",
"header": "Form",
"options": [
{"label": "name", "description": "string ✓"},
{"label": "email", "description": "string ✓"},
{"label": "bio", "description": "text ✓"},
{"label": "status", "description": "enum ✓"},
{"label":
Sensitive fields (always excluded from options):
SENSITIVE_FIELDS = %w[
encrypted_password password_digest password
reset_password_token confirmation_token unlock_token
api_token access_token refresh_token auth_token secret_token
otp_secret encrypted_otp_secret
secret_key api_key private_key
]
Note: If existing admin panel was detected (Section 1.7), show detected custom features and ask if they should be replicated.
Question 4: Authentication
{
"question": "How should admin authentication work?",
"header": "Auth",
"options": [
{"label": "Devise AdminUser (Recommended)", "description": "Create separate AdminUser model with Devise"},
{"label": "Existing User model", "description": "Use current User model with admin role/flag"},
{"label": "Skip authentication", "description": "No auth - secure with other means"},
{"label": "HTTP Basic Auth", "description": "Simple username/password protection"}
],
"multiSelect":
Question 5: Internationalization
{
"question": "Do you want internationalization (i18n) support?",
"header": "i18n",
"options": [
{"label": "No i18n", "description": "English-only, hardcoded strings"},
{"label": "Yes - English", "description": "i18n with English locale file"},
{"label": "Yes - Russian", "description": "i18n with Russian locale file"},
{"label": "Yes - Other language", "description": "I'll specify the language"}
],
"multiSelect":
Question 6: Tests
{
"question": "Should tests be generated for the admin controllers?",
"header": "Tests",
"options": [
{"label": "Yes (Recommended)", "description": "Generate controller tests"},
{"label": "No", "description": "Skip test generation"}
],
"multiSelect": false
}
Question 7: Export
{
"question": "What export functionality do you need?",
"header": "Export",
"options": [
{"label": "CSV with field selection (Recommended)", "description": "Export to CSV, admin chooses which fields"},
{"label": "CSV + Excel with field selection", "description": "Both formats, admin chooses fields"},
{"label": "Simple CSV (all fields)", "description": "Quick export without field picker"},
{"label": "No export", "description": "Skip export functionality"}
],
"multiSelect":
Phase 4: Generation
Generate files in this specific order. Use templates from templates/ directory.
4.1 Add Gems (if needed)
If pagination gem not found, add to Gemfile:
gem 'pagy', '~> 6.0'
gem 'ransack', '~> 4.0'
If Excel export requested:
gem 'caxlsx', '~> 4.0'
gem 'caxlsx_rails', '~> 0.6'
4.2 Create Concerns
Create app/controllers/concerns/{namespace}/:
date_range_filterable.rb - See templates/concerns/date_range_filterable.rb
exportable.rb - Advanced export concern with field selection:
module {Namespace}
module Exportable
extend ActiveSupport::Concern
def export
@model_class = controller_name.classify.constantize
@exportable_fields = exportable_fields_for(@model_class)
if request.get?
render "#{namespace}/shared/export_modal", locals: {
fields: @exportable_fields,
record_count: filtered_scope.count,
selected_ids: params[:ids]
}
else
selected_fields = params[:export_fields] || @exportable_fields.keys
records = export_scope(params[:ids])
respond_to do |format|
format.csv { send_csv(records, selected_fields) }
format.xlsx { send_xlsx(records, selected_fields) }
end
end
end
private
def exportable_fields_for(model_class)
model_class.column_names.each_with_object({}) do |col, hash|
next if col.in?()
hash[col] = col.humanize
()
scope = filtered_scope
scope = scope.where( selected_ids) selected_ids.present?
scope
model_class = controller_name.classify.constantize
model_class.ransack(params[]).result
()
csv_data = .generate( ) ||
csv << fields.map { || f.humanize }
records.find_each ||
csv << fields.map { || format_field(record, f) }
send_data csv_data,
()
value = record.send(field)
value
, value.strftime()
value.strftime()
value.to_s
bulk_actions.rb - See templates/features/bulk_actions.rb
4.3 Create Base Controller
Use templates/controllers/base_controller.rb as template.
Create app/controllers/{namespace}/base_controller.rb:
module {Namespace}
class BaseController < ApplicationController
include Pagy::Backend
include {Namespace}::DateRangeFilterable
include {Namespace}::Exportable
include {Namespace}::BulkActions
before_action :authenticate_{auth_method}!
layout "{namespace}"
private
def pagy_get_vars(collection, vars)
vars[:items] ||= 25
vars[:count] ||= collection.count(:all)
vars
end
end
end
4.4 Create Layout and Shared Partials
Create app/views/layouts/{namespace}.html.erb using templates/shared/layout.html.erb.
Create app/views/{namespace}/shared/ partials:
_sidebar.html.erb - Navigation with all resources
_flash.html.erb - Flash message display
_pagination.html.erb - Pagination controls
_table_header.html.erb - Sortable column headers
_bulk_actions.html.erb - Bulk action dropdown
_export_modal.html.erb - Export dialog with field selection (if export enabled)
Export Modal (_export_modal.html.erb):
<%# Export modal with field selection %>
<div id="export-modal" class="{CSS: modal}" data-controller="export-modal">
<div class="{CSS: modal-content}">
<div class="{CSS: modal-header}">
<h3>Export <%= controller_name.humanize %></h3>
<button type="button" data-action="export-modal#close">×</button>
</div>
<%= form_tag export_path(format: :csv), method: :post, data: { export_modal_target: "form" } do %>
<%# Pass current filters %>
<% params[:q]&.each do |key, value| %>
<%= hidden_field_tag "q[#{key}]", value %>
<% end %>
<%# Pass selected IDs if bulk export %>
<% if local_assigns[:selected_ids].present? %>
<% selected_ids.each do |id| %>
<%= hidden_field_tag "ids[]", id %>
<% end %>
<% end %>
<div class="{CSS: modal-body}">
<p class="{CSS: text-muted}">
Exporting <strong><%= record_count %></strong> records
<% if selected_ids.present? %>
(<%= selected_ids.size %> selected)
<% else %>
(filtered)
<% end %>
</p>
<div class="{CSS: form-group}">
<label class="{CSS: label}">Select fields to export:</label>
<div class="{CSS: checkbox-group}">
<label class="{CSS: checkbox}">
<%= check_box_tag "select_all", "1", true, data: { action: "export-modal#toggleAll" } %>
<strong>Select All</strong>
</label>
</div>
<div class="{CSS: checkbox-grid}" data-export-modal-target="fields">
<% fields.each do |field_name, field_label| %>
<label class="{CSS: checkbox}">
<%= check_box_tag "export_fields[]", field_name, true, data: { export_modal_target: "field" } %>
<%= field_label %>
</label>
<% end %>
</div>
</div>
</div>
<div class="{CSS: modal-footer}">
<button type="button" class="{CSS: btn-secondary}" data-action="export-modal#close">
Cancel
</button>
<button type="submit" name="format" value="csv" class="{CSS: btn-primary}">
Export CSV
</button>
<%# If Excel enabled %>
<button type="submit" name="format" value="xlsx" class="{CSS: btn-primary}">
Export Excel
</button>
</div>
<% end %>
</div>
</div>
Export Modal Stimulus Controller (if has_stimulus):
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["form", "fields", "field"]
open(event) {
event.preventDefault()
const selectedIds = this.getSelectedIds()
if (selectedIds.length > 0) {
this.addSelectedIds(selectedIds)
}
this.element.classList.remove("hidden")
}
close() {
this.element.classList.add("hidden")
}
toggleAll(event) {
const checked = event.target.checked
this.fieldTargets.forEach(field => field.checked = checked)
}
() {
checkboxes = .()
.(checkboxes).( cb.)
}
() {
..().( el.())
ids.( {
input = .()
input. =
input. =
input. = id
..(input)
})
}
}
IMPORTANT: Use CSS classes from css/{css_framework}.md for all styling.
4.5 Create Dashboard
Create app/controllers/{namespace}/dashboard_controller.rb:
module {Namespace}
class DashboardController < BaseController
def index
@stats = {
users: User.count,
posts: Post.count,
}
@recent_activity = [
]
end
end
end
Create app/views/{namespace}/dashboard/index.html.erb with:
- Stat cards for each model (count, recent count)
- Recent activity feed
- Quick links to each resource
4.6 Create Resource Controllers
For each included model, create app/controllers/{namespace}/{model_plural}_controller.rb:
Use templates/controllers/resource_controller.rb as base.
Based on CRUD configuration (Question 3):
If model has full CRUD:
index with ransack filtering and pagy pagination
show with association preloading
new/create with strong parameters
edit/update with strong parameters
destroy (or soft delete if detected)
export action for CSV/Excel
bulk_destroy action
restore action (if soft delete)
If model is read-only:
index with ransack filtering and pagy pagination
show with association preloading
export action for CSV/Excel
- NO new/create/edit/update/destroy actions
If existing admin features detected (Section 1.7):
Replicate custom functionality from existing admin:
member_action :impersonate, method: :post do
end
def impersonate
@user = User.find(params[:id])
end
Include detected features:
- Custom actions → add as controller methods
- Custom filters → add to ransack configuration
- Nested forms → include in strong parameters and form
- Custom show sections → add to show view
- Bulk actions → add to bulk_actions concern
4.7 Create Resource Views
For each included model, create views in app/views/{namespace}/{model_plural}/:
Based on CRUD configuration (Question 3):
All models (read-only and CRUD):
index.html.erb - Use templates/views/index.html.erb:
- Filter form with ransack
- Data table with sortable columns
- Pagination
- If CRUD: "New" button, bulk action checkboxes, edit/delete links
- If read-only: only "View" links, no bulk actions
Export buttons (if export enabled):
<div class="{CSS: btn-group}">
<%# Export filtered records %>
<%= link_to "Export", "#",
class: "{CSS: btn-secondary}",
data: { action: "export-modal#open" } %>
<%# Export selected records (shown when records selected) %>
<span data-bulk-select-target="bulkActions" class="hidden">
<%= link_to "Export Selected", "#",
class: "{CSS: btn-secondary}",
data: { action: "export-modal#open" } %>
</span>
</div>
<%# Include export modal %>
<%= render "{namespace}/shared/export_modal",
fields: exportable_fields_for(@model_class),
record_count: @query.result.count,
selected_ids: nil %>
show.html.erb - Use templates/views/show.html.erb:
- Field display based on type
- Associated records lists
- Pretty JSON for jsonb fields
- Image previews for attachments
- If CRUD: Edit, Delete, Back buttons
- If read-only: only Back button
_table.html.erb - Use templates/views/_table.html.erb
_filters.html.erb - Use templates/views/_filters.html.erb
Only for models with full CRUD:
new.html.erb - Use templates/views/new.html.erb
edit.html.erb - Use templates/views/edit.html.erb
_form.html.erb - Use templates/views/_form.html.erb:
- Form fields based on column types (see field-mappings.md)
- Association selects
- Enum dropdowns
- File upload fields with previews
- JSON textarea for jsonb
If existing admin features detected:
Replicate custom view sections from existing admin:
- Custom show page sections → add to show.html.erb
- Custom index columns → add to _table.html.erb
- Custom form fields → add to _form.html.erb
- Nested resource forms → add accepts_nested_attributes and fields_for
4.8 Add Ransackable Methods to Models
For each model without existing ransackable methods, add:
def self.ransackable_attributes(auth_object = nil)
["name", "email", "status", "created_at"]
end
def self.ransackable_associations(auth_object = nil)
["author", "comments"]
end
4.9 Create Routes
Add to config/routes.rb:
Based on CRUD configuration (Question 3):
namespace :{namespace} do
root to: "dashboard#index"
resources :users do
collection do
get :export
post :export
delete :bulk_destroy
end
member do
patch :restore
post :impersonate
post :ban
end
end
resources :audit_logs, only: [:index, :show] do
collection do
get :export
post :export
end
end
end
Export route explanation:
GET /admin/users/export - Opens modal (if JS disabled, shows export page)
POST /admin/users/export - Performs export with selected fields, respects current filters
POST /admin/users/export?ids[]=1&ids[]=2 - Exports only selected records
4.10 Create Stimulus Controllers (if has_stimulus)
Create app/javascript/controllers/{namespace}/:
bulk_select_controller.js:
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["checkbox", "selectAll", "bulkActions", "selectedCount"]
connect() {
this.updateUI()
}
toggleAll() {
const checked = this.selectAllTarget.checked
this.checkboxTargets.forEach(cb => cb.checked = checked)
this.updateUI()
}
toggle() {
this.updateUI()
}
updateUI() {
const checked = this.checkboxTargets.filter(cb => cb.checked)
const count = checked.length
this.bulkActionsTarget.classList.toggle("hidden", count === )
.. = count
.. = count === ..
.. = count > && count < ..
}
() {
.
.( cb.)
.( cb.)
}
}
confirm_controller.js:
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static values = { message: String }
confirm(event) {
if (!window.confirm(this.messageValue || "Are you sure?")) {
event.preventDefault()
}
}
}
4.11 Create Tests (if requested)
Use templates from templates/specs/:
RSpec - Create spec/controllers/{namespace}/ or spec/requests/{namespace}/
Minitest - Create test/controllers/{namespace}/
4.12 Create i18n Files (if requested)
Create config/locales/{namespace}.{locale}.yml:
{locale}:
{namespace}:
shared:
actions: "Actions"
edit: "Edit"
delete: "Delete"
back: "Back"
save: "Save"
cancel: "Cancel"
search: "Search"
filter: "Filter"
export: "Export"
export_csv: "Export CSV"
export_excel: "Export Excel"
confirm_delete: "Are you sure you want to delete this record?"
bulk_delete: "Delete selected"
no_records: "No records found"
dashboard:
title: "Dashboard"
total_records: "Total records"
recent_activity: "Recent activity"
users:
title: "Users"
new: "New User"
edit: "Edit User"
Phase 5: Verification
After generation, output these instructions with SPECIFIC details based on user choices:
Output Template
## ✅ Admin Panel Generated Successfully!
### 🔗 Admin Panel URL
**URL:** http://localhost:3000/{namespace}
(Replace `localhost:3000` with your actual host if different)
---
### 🔐 Authentication
{IF auth_choice == "Devise AdminUser"}
**Method:** Devise with AdminUser model
1. Run migrations:
```bash
rails db:migrate
-
Create your first admin user:
rails console
AdminUser.create!(email: 'admin@example.com', password: 'password123')
-
Login at: http://localhost:3000/{namespace}/login
{ELSE IF auth_choice == "Existing User model"}
Method: Using existing User model with admin check
Make sure your User model has an admin? method or admin boolean field.
Users with admin? == true can access the admin panel.
Login with any admin user credentials at: http://localhost:3000/{namespace}
{ELSE IF auth_choice == "HTTP Basic Auth"}
Method: HTTP Basic Authentication
Credentials are set in app/controllers/{namespace}/base_controller.rb:
- Username: admin
- Password: (check the controller file)
You can change credentials in the authenticate method.
{ELSE IF auth_choice == "Skip authentication"}
⚠️ WARNING: No authentication configured!
Your admin panel is currently publicly accessible.
This is fine for development, but NEVER deploy to production without authentication.
Recommended: Add Devise authentication:
-
Add to Gemfile:
gem 'devise'
-
Run:
bundle install
rails generate devise:install
rails generate devise AdminUser
rails db:migrate
-
Create admin user:
AdminUser.create!(email: 'admin@example.com', password: 'password123')
-
Add to app/controllers/{namespace}/base_controller.rb:
before_action :authenticate_admin_user!
{END IF}
📁 Files Created
app/controllers/{namespace}/ - Admin controllers
app/views/{namespace}/ - Admin views
app/views/layouts/{namespace}.html.erb - Admin layout
config/routes.rb - Updated with admin routes
{if tests}- {spec|test}/controllers/{namespace}/ - Controller tests{/if}
{if i18n}- config/locales/{namespace}.{locale}.yml - Translations{/if}
🚀 Quick Start
-
Install dependencies:
bundle install
-
Start server:
rails server
-
Open in browser:
http://localhost:3000/{namespace}
🎨 Customization
- Navigation: Edit
app/views/{namespace}/shared/_sidebar.html.erb
- Global settings: Modify
app/controllers/{namespace}/base_controller.rb
- Styles: Adjust the layout file for your design preferences
---
## CSS Framework Reference
When generating views, use the appropriate CSS classes based on detected framework.
### Tailwind (Default)
See `css/tailwind.md` for class mappings.
### Bootstrap
See `css/bootstrap.md` for class mappings.
### Bulma
See `css/bulma.md` for class mappings.
---
## Field Type Mappings
See `reference/field-mappings.md` for complete mapping of:
- Database column types → Form input types
- Database column types → Table display formats
- Special handling for JSON, attachments, enums
---
## Patterns Reference
See `reference/patterns.md` for:
- Ransack query patterns
- Pagy/Kaminari/WillPaginate integration
- Soft delete patterns
- Export patterns
- Bulk action patterns