ruby_cms 0.1.9 → 0.2.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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +440 -58
- data/app/controllers/ruby_cms/admin/base_controller.rb +33 -12
- data/app/controllers/ruby_cms/admin/content_block_versions_controller.rb +62 -0
- data/app/controllers/ruby_cms/admin/dashboard_controller.rb +40 -0
- data/app/helpers/ruby_cms/admin/dashboard_helper.rb +20 -0
- data/app/javascript/controllers/ruby_cms/content_block_history_controller.js +91 -0
- data/app/javascript/controllers/ruby_cms/index.js +4 -0
- data/app/javascript/controllers/ruby_cms/visual_editor_controller.js +33 -29
- data/app/models/concerns/content_block/versionable.rb +80 -0
- data/app/models/content_block.rb +1 -0
- data/app/models/content_block_version.rb +34 -0
- data/app/views/admin/content_block_versions/index.html.erb +52 -0
- data/app/views/admin/content_block_versions/show.html.erb +37 -0
- data/app/views/ruby_cms/admin/content_block_versions/index.html.erb +52 -0
- data/app/views/ruby_cms/admin/content_block_versions/show.html.erb +37 -0
- data/app/views/ruby_cms/admin/content_blocks/show.html.erb +12 -0
- data/app/views/ruby_cms/admin/dashboard/blocks/_analytics_overview.html.erb +53 -0
- data/app/views/ruby_cms/admin/dashboard/blocks/_content_blocks_stats.html.erb +17 -0
- data/app/views/ruby_cms/admin/dashboard/blocks/_permissions_stats.html.erb +17 -0
- data/app/views/ruby_cms/admin/dashboard/blocks/_quick_actions.html.erb +62 -0
- data/app/views/ruby_cms/admin/dashboard/blocks/_recent_errors.html.erb +39 -0
- data/app/views/ruby_cms/admin/dashboard/blocks/_users_stats.html.erb +17 -0
- data/app/views/ruby_cms/admin/dashboard/blocks/_visitor_errors_stats.html.erb +24 -0
- data/app/views/ruby_cms/admin/dashboard/index.html.erb +22 -180
- data/config/routes.rb +8 -0
- data/db/migrate/20260328000001_create_content_block_versions.rb +22 -0
- data/lib/generators/ruby_cms/admin_page_generator.rb +126 -0
- data/lib/generators/ruby_cms/templates/admin_page/controller.rb.tt +8 -0
- data/lib/generators/ruby_cms/templates/admin_page/index.html.erb.tt +11 -0
- data/lib/ruby_cms/dashboard_blocks.rb +91 -0
- data/lib/ruby_cms/engine/admin_permissions.rb +69 -0
- data/lib/ruby_cms/engine/content_blocks_tasks.rb +66 -0
- data/lib/ruby_cms/engine/css.rb +14 -0
- data/lib/ruby_cms/engine/dashboard_registration.rb +66 -0
- data/lib/ruby_cms/engine/navigation_registration.rb +80 -0
- data/lib/ruby_cms/engine.rb +23 -278
- data/lib/ruby_cms/icons.rb +118 -0
- data/lib/ruby_cms/version.rb +1 -1
- data/lib/ruby_cms.rb +36 -10
- metadata +28 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2a8d714a6a977cef96bd1eb99a4efd56e66da9ea9aebf81f61e982731bed9bda
|
|
4
|
+
data.tar.gz: 2543ebcfcef9083b33a69f7346ba921c087691aa46d6e1f1b7490d57bf9edfc8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8625f68d914febc4c00a551f6d48ac0dfa0305a79248ce6faabc95e1641bc8794fef469f4480bfdb3869966011e59977e6be9cc9fdf048e7f1a7d95c01eb7372
|
|
7
|
+
data.tar.gz: 81354dff69046ca0ea0d94dceccd4c682070b768ef6bbf640dc19d2478d7dfe8b3b78e8351fba8c19814781a489a22e2175352ebfd38bda01d1bf50b0cf4e22a
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
|
@@ -1,141 +1,523 @@
|
|
|
1
1
|
# RubyCMS
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Reusable Rails engine for a CMS-style admin: content blocks, visual editor, versioning, pluggable dashboard, permissions, analytics, and visitor error tracking.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Vision: your app owns product features (pages, models, business logic); RubyCMS manages content workflows and admin screens.
|
|
5
|
+
Your app owns product features (pages, models, business logic); RubyCMS manages content workflows and admin screens.
|
|
8
6
|
|
|
9
7
|
## Features
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
- **Content blocks** -- rich text, plain text, images, links, and lists with locale support
|
|
10
|
+
- **Content block versioning** -- automatic version history with rollback and side-by-side diffs
|
|
11
|
+
- **Visual editor** -- inline editing on live page previews
|
|
12
|
+
- **Pluggable dashboard** -- registry-based blocks with permission gating and host-app extensibility
|
|
13
|
+
- **Permissions** -- key-based access control with templates and per-user assignment
|
|
14
|
+
- **Admin page generator** -- one command to scaffold a new admin page with nav, permission, and route
|
|
15
|
+
- **Named icon registry** -- 20+ Heroicons by name, no SVG copy-pasting
|
|
16
|
+
- **Settings** -- DB-backed admin settings with categories and UI
|
|
17
|
+
- **Analytics** -- Ahoy-powered visit/event tracking with dashboard drill-downs
|
|
18
|
+
- **Visitor error tracking** -- automatic 404/500 capture with admin overview
|
|
16
19
|
|
|
17
20
|
## Quick Start
|
|
18
21
|
|
|
19
22
|
```bash
|
|
23
|
+
# Add to Gemfile
|
|
24
|
+
gem "ruby_cms"
|
|
25
|
+
|
|
26
|
+
# Install
|
|
20
27
|
rails g ruby_cms:install
|
|
28
|
+
rails db:migrate
|
|
29
|
+
rails ruby_cms:seed_permissions
|
|
30
|
+
rails ruby_cms:setup_admin
|
|
21
31
|
```
|
|
22
32
|
|
|
23
|
-
The generator sets up
|
|
24
|
-
|
|
25
|
-
* `config/initializers/ruby_cms.rb`
|
|
26
|
-
* mounts the engine (`/admin/...` on the host app)
|
|
27
|
-
* migrations + RubyCMS tables
|
|
28
|
-
* seed permissions + initial admin setup
|
|
33
|
+
The install generator sets up the initializer, mounts the engine at `/admin`, runs migrations, configures Tailwind, Stimulus, Action Text, and Ahoy.
|
|
29
34
|
|
|
30
|
-
|
|
35
|
+
Visit `/admin` and sign in with the admin user you configured.
|
|
31
36
|
|
|
32
|
-
##
|
|
37
|
+
## Content Blocks
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
Use the `content_block` helper in any view:
|
|
35
40
|
|
|
36
41
|
```erb
|
|
37
42
|
<%= content_block("hero_title", default: "Welcome") %>
|
|
38
43
|
<%= content_block("footer", cache: true) %>
|
|
39
44
|
```
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
### Content Types
|
|
47
|
+
|
|
48
|
+
| Type | Description |
|
|
49
|
+
|------|-------------|
|
|
50
|
+
| `text` | Plain text string |
|
|
51
|
+
| `rich_text` | Action Text rich content (HTML) |
|
|
52
|
+
| `image` | Attached image via Active Storage |
|
|
53
|
+
| `link` | URL string |
|
|
54
|
+
| `list` | JSON array or newline-separated items |
|
|
55
|
+
|
|
56
|
+
### Placeholders and Attributes
|
|
43
57
|
|
|
44
|
-
`content_block` wraps output for the visual editor
|
|
45
|
-
Use `wrap: false` (or `content_block_text`):
|
|
58
|
+
`content_block` wraps output for the visual editor. For HTML attributes, use `wrap: false`:
|
|
46
59
|
|
|
47
60
|
```erb
|
|
48
61
|
<%= text_field_tag :name, nil,
|
|
49
|
-
placeholder: content_block("contact.
|
|
50
|
-
|
|
51
|
-
<%= text_area_tag :message, nil,
|
|
52
|
-
placeholder: content_block_text("contact.message_placeholder", fallback: "Your message...") %>
|
|
62
|
+
placeholder: content_block("contact.placeholder", wrap: false, fallback: "Your name") %>
|
|
53
63
|
```
|
|
54
64
|
|
|
55
|
-
|
|
65
|
+
Or use `content_block_text` which never wraps:
|
|
56
66
|
|
|
57
|
-
|
|
58
|
-
<
|
|
67
|
+
```erb
|
|
68
|
+
<meta name="description" content="<%= content_block_text("meta_desc", fallback: "Default") %>">
|
|
69
|
+
```
|
|
59
70
|
|
|
60
|
-
|
|
71
|
+
### List Items
|
|
61
72
|
|
|
62
73
|
```erb
|
|
63
|
-
<% content_block_list_items("
|
|
74
|
+
<% content_block_list_items("badges", fallback: ["Ruby", "Rails"]).each do |badge| %>
|
|
64
75
|
<%= tag.span badge, class: "badge" %>
|
|
65
76
|
<% end %>
|
|
66
77
|
```
|
|
67
78
|
|
|
68
|
-
|
|
79
|
+
### Multi-locale Support
|
|
69
80
|
|
|
70
|
-
|
|
81
|
+
Content blocks have a `locale` field. The CMS groups blocks by key prefix across locales for easy management.
|
|
71
82
|
|
|
72
|
-
|
|
83
|
+
## Content Block Versioning
|
|
84
|
+
|
|
85
|
+
Versions are created automatically on every meaningful change:
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
block = ContentBlock.create!(key: "hero", title: "Welcome", content: "Hello",
|
|
89
|
+
content_type: "text", locale: "en")
|
|
90
|
+
block.versions.count # => 1 (event: "create")
|
|
91
|
+
|
|
92
|
+
block.update!(title: "Welcome!")
|
|
93
|
+
block.versions.count # => 2 (event: "update")
|
|
94
|
+
|
|
95
|
+
block.update!(published: false)
|
|
96
|
+
block.versions.last.event # => "unpublish"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Events
|
|
100
|
+
|
|
101
|
+
| Event | When |
|
|
102
|
+
|-------|------|
|
|
103
|
+
| `create` | Block is first created |
|
|
104
|
+
| `update` | Title, content, or content_type changes |
|
|
105
|
+
| `publish` | `published` changes to `true` |
|
|
106
|
+
| `unpublish` | `published` changes to `false` |
|
|
107
|
+
| `rollback` | `rollback_to_version!` is called |
|
|
108
|
+
| `visual_editor` | Edit via the visual editor |
|
|
109
|
+
|
|
110
|
+
### Rollback
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
old_version = block.versions.first
|
|
114
|
+
block.rollback_to_version!(old_version, user: current_user)
|
|
115
|
+
# Creates a new version with event: "rollback"
|
|
116
|
+
# Restores title, content, content_type, published, and rich_content
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Admin UI
|
|
120
|
+
|
|
121
|
+
- **Version history** link on each content block show page
|
|
122
|
+
- **Timeline view** with colored event badges
|
|
123
|
+
- **Side-by-side diff** (old vs new, red/green)
|
|
124
|
+
- **Rollback button** with confirmation
|
|
125
|
+
|
|
126
|
+
### Routes
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
GET /admin/content_blocks/:id/versions # index (HTML + JSON)
|
|
130
|
+
GET /admin/content_blocks/:id/versions/:vid # show with diff
|
|
131
|
+
POST /admin/content_blocks/:id/versions/:vid/rollback
|
|
132
|
+
```
|
|
73
133
|
|
|
74
134
|
## Visual Editor
|
|
75
135
|
|
|
76
|
-
Configure preview templates
|
|
136
|
+
Configure preview templates in `config/initializers/ruby_cms.rb`:
|
|
77
137
|
|
|
78
138
|
```ruby
|
|
79
|
-
|
|
80
|
-
c.
|
|
139
|
+
RubyCms.configure do |c|
|
|
140
|
+
c.preview_templates = { "home" => "pages/home", "about" => "pages/about" }
|
|
141
|
+
c.preview_data = ->(page_key, view) { { products: Product.limit(5) } }
|
|
142
|
+
end
|
|
81
143
|
```
|
|
82
144
|
|
|
83
|
-
|
|
145
|
+
Open **Admin > Visual editor**, pick a page, and click content blocks to edit them inline.
|
|
84
146
|
|
|
85
|
-
##
|
|
147
|
+
## Dashboard
|
|
86
148
|
|
|
87
|
-
|
|
149
|
+
The dashboard uses a registry-based block system. Each block is a partial with optional permission gating and data injection.
|
|
88
150
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
151
|
+
### Default Blocks
|
|
152
|
+
|
|
153
|
+
| Block | Section | Permission |
|
|
154
|
+
|-------|---------|------------|
|
|
155
|
+
| Content blocks stats | stats | `manage_content_blocks` |
|
|
156
|
+
| Users stats | stats | `manage_permissions` |
|
|
157
|
+
| Permissions stats | stats | `manage_permissions` |
|
|
158
|
+
| Visitor errors stats | stats | `manage_visitor_errors` |
|
|
159
|
+
| Quick actions | main | -- |
|
|
160
|
+
| Recent errors | main | `manage_visitor_errors` |
|
|
161
|
+
| Analytics overview | main | `manage_analytics` |
|
|
162
|
+
|
|
163
|
+
### Adding Custom Blocks
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
# config/initializers/ruby_cms.rb (or ruby_cms_pages.rb)
|
|
167
|
+
Rails.application.config.to_prepare do
|
|
168
|
+
RubyCms.dashboard_register(
|
|
169
|
+
key: :orders_stats,
|
|
170
|
+
label: "Orders",
|
|
171
|
+
section: :stats, # :stats (top row) or :main (bottom grid)
|
|
172
|
+
order: 5,
|
|
173
|
+
partial: "admin/dashboard/orders_stats",
|
|
174
|
+
permission: :manage_orders,
|
|
175
|
+
span: :single, # :single or :double (grid width)
|
|
176
|
+
data: ->(controller) {
|
|
177
|
+
{ count: Order.count, today: Order.where("created_at > ?", Date.today).count }
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
end
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Then create the partial `app/views/admin/dashboard/_orders_stats.html.erb`. The `block` local contains the registration data and any computed `data`.
|
|
184
|
+
|
|
185
|
+
### Overriding Default Blocks
|
|
186
|
+
|
|
187
|
+
Re-register with the same key to override:
|
|
188
|
+
|
|
189
|
+
```ruby
|
|
190
|
+
RubyCms.dashboard_register(
|
|
191
|
+
key: :quick_actions,
|
|
192
|
+
label: "Quick actions",
|
|
193
|
+
section: :main,
|
|
194
|
+
partial: "admin/dashboard/my_quick_actions" # your own partial
|
|
195
|
+
)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Navigation and Permissions
|
|
199
|
+
|
|
200
|
+
### register_page (recommended)
|
|
201
|
+
|
|
202
|
+
One call to register a nav item and its permission:
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
RubyCms.register_page(
|
|
206
|
+
key: :backups,
|
|
207
|
+
label: "Backups",
|
|
208
|
+
path: :admin_backups_path, # Symbol: auto-wrapped via main_app
|
|
209
|
+
icon: :archive_box, # Named icon from RubyCms::Icons
|
|
210
|
+
section: :main, # :main (sidebar top) or :settings (sidebar bottom)
|
|
211
|
+
permission: :manage_backups, # Auto-registered as permission key
|
|
212
|
+
order: 10
|
|
213
|
+
)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### nav_register (low-level)
|
|
217
|
+
|
|
218
|
+
For more control (custom visibility gates, non-standard paths):
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
RubyCms.nav_register(
|
|
222
|
+
key: :reports,
|
|
223
|
+
label: "Reports",
|
|
224
|
+
path: ->(view) { view.main_app.reports_path },
|
|
225
|
+
icon: :chart_bar,
|
|
226
|
+
section: "main",
|
|
227
|
+
permission: :manage_reports,
|
|
228
|
+
if: ->(view) { view.current_user_cms&.admin? }
|
|
229
|
+
)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Path Options
|
|
233
|
+
|
|
234
|
+
| Format | Example | Behavior |
|
|
235
|
+
|--------|---------|----------|
|
|
236
|
+
| Symbol | `:admin_backups_path` | Auto-wrapped: `view.main_app.send(:admin_backups_path)` |
|
|
237
|
+
| Lambda | `-> (v) { v.some_path }` | Called with view context |
|
|
238
|
+
| String | `"/admin/backups"` | Used as-is |
|
|
239
|
+
|
|
240
|
+
### Named Icons
|
|
241
|
+
|
|
242
|
+
Use symbol keys from `RubyCms::Icons`:
|
|
243
|
+
|
|
244
|
+
```ruby
|
|
245
|
+
RubyCms::Icons.available
|
|
246
|
+
# => [:home, :pencil_square, :document_duplicate, :chart_bar, :shield_check,
|
|
247
|
+
# :exclamation_triangle, :user_group, :cog_6_tooth, :archive_box, :folder,
|
|
248
|
+
# :bell, :clock, :tag, :cube, :envelope, :wrench, :globe, :photograph,
|
|
249
|
+
# :list_bullet, :plus_circle, :trash, :eye, :lock_closed, :currency_dollar]
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Raw SVG strings are also accepted for custom icons.
|
|
253
|
+
|
|
254
|
+
### Permission Keys
|
|
255
|
+
|
|
256
|
+
Default keys: `manage_admin`, `manage_permissions`, `manage_content_blocks`, `manage_visitor_errors`, `manage_analytics`.
|
|
257
|
+
|
|
258
|
+
Register additional keys:
|
|
259
|
+
|
|
260
|
+
```ruby
|
|
261
|
+
RubyCms.register_permission_keys(:manage_orders, :manage_reports)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Permission Templates
|
|
265
|
+
|
|
266
|
+
Group permission keys into reusable templates:
|
|
267
|
+
|
|
268
|
+
```ruby
|
|
269
|
+
RubyCms.register_permission_template(:editor,
|
|
270
|
+
label: "Editor",
|
|
271
|
+
keys: %w[manage_admin manage_content_blocks],
|
|
272
|
+
description: "Can manage content but not users"
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# Apply to a user:
|
|
276
|
+
RubyCms::Permission.apply_template!(user, :editor)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### cms_page Macro
|
|
280
|
+
|
|
281
|
+
In controllers that inherit from `RubyCms::Admin::BaseController`, use `cms_page` to link to a registered page. The permission is looked up from the nav registry automatically:
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
class Admin::BackupsController < RubyCms::Admin::BaseController
|
|
285
|
+
cms_page :backups # permission from register_page(:backups, permission: :manage_backups)
|
|
286
|
+
|
|
287
|
+
def index
|
|
288
|
+
@backups = Backup.recent
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## Admin Page Generator
|
|
294
|
+
|
|
295
|
+
Scaffold a complete admin page with one command:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
rails g ruby_cms:admin_page backups
|
|
93
299
|
```
|
|
94
300
|
|
|
301
|
+
This generates:
|
|
302
|
+
|
|
303
|
+
| File | Description |
|
|
304
|
+
|------|-------------|
|
|
305
|
+
| `app/controllers/admin/backups_controller.rb` | Controller with `cms_page :backups` |
|
|
306
|
+
| `app/views/admin/backups/index.html.erb` | View template |
|
|
307
|
+
| `config/routes.rb` | Route injection (`namespace :admin`) |
|
|
308
|
+
| `config/initializers/ruby_cms_pages.rb` | `register_page` call |
|
|
309
|
+
|
|
310
|
+
### Options
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
rails g ruby_cms:admin_page backups \
|
|
314
|
+
--permission=manage_backups \
|
|
315
|
+
--icon=archive_box \
|
|
316
|
+
--section=settings \
|
|
317
|
+
--order=15
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
| Option | Default | Description |
|
|
321
|
+
|--------|---------|-------------|
|
|
322
|
+
| `--permission` | `manage_<name>` | Permission key |
|
|
323
|
+
| `--icon` | `folder` | Icon from `RubyCms::Icons` |
|
|
324
|
+
| `--section` | `main` | `main` or `settings` |
|
|
325
|
+
| `--order` | `10` | Sort order in nav |
|
|
326
|
+
|
|
327
|
+
After generating, run `rails ruby_cms:seed_permissions` to create the permission row in the database.
|
|
328
|
+
|
|
329
|
+
## Settings
|
|
330
|
+
|
|
331
|
+
RubyCMS uses DB-backed settings with a registry for defaults and types:
|
|
332
|
+
|
|
333
|
+
```ruby
|
|
334
|
+
# Read
|
|
335
|
+
RubyCms::Settings.get(:analytics_default_period, default: "week")
|
|
336
|
+
|
|
337
|
+
# Write (admin UI or programmatic)
|
|
338
|
+
RubyCms::Settings.set(:analytics_default_period, "month")
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Registering Custom Settings
|
|
342
|
+
|
|
343
|
+
```ruby
|
|
344
|
+
RubyCms::SettingsRegistry.register(
|
|
345
|
+
key: :my_custom_setting,
|
|
346
|
+
type: :string,
|
|
347
|
+
default: "hello",
|
|
348
|
+
category: :general,
|
|
349
|
+
description: "A custom setting"
|
|
350
|
+
)
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Settings are managed in **Admin > Settings** with tabs per category.
|
|
354
|
+
|
|
95
355
|
## Visitor Error Tracking
|
|
96
356
|
|
|
97
|
-
Public
|
|
357
|
+
Public exceptions are automatically captured when `RubyCms::VisitorErrorCapture` is included in your `ApplicationController` (added by the install generator).
|
|
98
358
|
|
|
99
|
-
|
|
359
|
+
View captured errors in **Admin > Visitor errors** with status codes, paths, timestamps, and resolution tracking.
|
|
100
360
|
|
|
101
|
-
##
|
|
361
|
+
## Analytics (Ahoy)
|
|
362
|
+
|
|
363
|
+
RubyCMS integrates with Ahoy for server-side page view and event tracking.
|
|
102
364
|
|
|
103
365
|
Include `RubyCms::PageTracking` in public controllers:
|
|
104
366
|
|
|
105
367
|
```ruby
|
|
106
368
|
class PagesController < ApplicationController
|
|
107
369
|
include RubyCms::PageTracking
|
|
370
|
+
end
|
|
371
|
+
```
|
|
108
372
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
373
|
+
View analytics in **Admin > Analytics** with:
|
|
374
|
+
- Visit and event counts
|
|
375
|
+
- Popular pages
|
|
376
|
+
- Top visitors
|
|
377
|
+
- Configurable date ranges and periods
|
|
378
|
+
|
|
379
|
+
### Customization Hooks
|
|
380
|
+
|
|
381
|
+
```ruby
|
|
382
|
+
RubyCms.configure do |c|
|
|
383
|
+
c.analytics_visit_scope = ->(scope) { scope.where.not(ip: ["127.0.0.1"]) }
|
|
384
|
+
c.analytics_event_scope = ->(scope) { scope }
|
|
385
|
+
c.analytics_extra_cards = lambda { |start_date:, end_date:, period:, visits_scope:, events_scope:|
|
|
386
|
+
[{ title: "Custom KPI", value: visits_scope.count }]
|
|
387
|
+
}
|
|
112
388
|
end
|
|
113
389
|
```
|
|
114
390
|
|
|
115
391
|
## Seeding Content Blocks from YAML
|
|
116
392
|
|
|
117
|
-
|
|
393
|
+
Set the translation namespace:
|
|
118
394
|
|
|
119
395
|
```ruby
|
|
120
|
-
|
|
396
|
+
RubyCms.configure do |c|
|
|
397
|
+
c.content_blocks_translation_namespace = "content_blocks"
|
|
398
|
+
end
|
|
121
399
|
```
|
|
122
400
|
|
|
123
|
-
|
|
401
|
+
Create locale files:
|
|
124
402
|
|
|
125
403
|
```yaml
|
|
404
|
+
# config/locales/en.yml
|
|
126
405
|
en:
|
|
127
406
|
content_blocks:
|
|
128
407
|
hero_title: "Welcome to my site"
|
|
408
|
+
footer_text: "Copyright 2026"
|
|
129
409
|
```
|
|
130
410
|
|
|
131
411
|
Import:
|
|
132
412
|
|
|
133
413
|
```bash
|
|
134
|
-
rails ruby_cms:content_blocks:
|
|
414
|
+
rails ruby_cms:content_blocks:import
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Export DB content blocks back to YAML:
|
|
418
|
+
|
|
419
|
+
```bash
|
|
420
|
+
rails ruby_cms:content_blocks:export
|
|
135
421
|
```
|
|
136
422
|
|
|
137
|
-
##
|
|
423
|
+
## Configuration
|
|
424
|
+
|
|
425
|
+
All configuration happens in `config/initializers/ruby_cms.rb`:
|
|
426
|
+
|
|
427
|
+
```ruby
|
|
428
|
+
RubyCms.configure do |c|
|
|
429
|
+
# Base controller (must provide current_user + authentication)
|
|
430
|
+
c.admin_base_controller = "ApplicationController"
|
|
431
|
+
|
|
432
|
+
# Admin layout
|
|
433
|
+
c.admin_layout = "admin/admin"
|
|
434
|
+
|
|
435
|
+
# User model class name
|
|
436
|
+
c.user_class_name = "User"
|
|
437
|
+
|
|
438
|
+
# Allow user.admin? bypass when no Permission records exist
|
|
439
|
+
c.bootstrap_admin_with_role = true
|
|
440
|
+
|
|
441
|
+
# Redirect path for unauthenticated/unauthorized users
|
|
442
|
+
c.unauthorized_redirect_path = "/"
|
|
443
|
+
|
|
444
|
+
# Visual editor
|
|
445
|
+
c.preview_templates = { "home" => "pages/home" }
|
|
446
|
+
c.preview_data = ->(page_key, view) { {} }
|
|
447
|
+
|
|
448
|
+
# Content blocks
|
|
449
|
+
c.content_blocks_translation_namespace = "content_blocks"
|
|
450
|
+
c.image_content_types = %w[image/png image/jpeg image/gif image/webp]
|
|
451
|
+
c.image_max_size = 5 * 1024 * 1024
|
|
452
|
+
end
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
## Rake Tasks
|
|
456
|
+
|
|
457
|
+
| Task | Description |
|
|
458
|
+
|------|-------------|
|
|
459
|
+
| `ruby_cms:seed_permissions` | Create default permission rows + settings |
|
|
460
|
+
| `ruby_cms:setup_admin` | Interactive first admin user setup |
|
|
461
|
+
| `ruby_cms:grant_manage_admin` | Grant all permissions to a user by email |
|
|
462
|
+
| `ruby_cms:content_blocks:export` | Export DB content blocks to YAML |
|
|
463
|
+
| `ruby_cms:content_blocks:import` | Import content blocks from YAML |
|
|
464
|
+
| `ruby_cms:content_blocks:sync` | Export + optional import |
|
|
465
|
+
| `ruby_cms:import_initializer_settings` | Import initializer values into DB settings |
|
|
466
|
+
| `ruby_cms:css:compile` | Compile admin CSS to host app |
|
|
467
|
+
| `ruby_cms:css:compile_gem` | Compile admin CSS within the gem |
|
|
468
|
+
|
|
469
|
+
## Development
|
|
470
|
+
|
|
471
|
+
### Setup
|
|
472
|
+
|
|
473
|
+
```bash
|
|
474
|
+
git clone https://github.com/jobhammer00/ruby_cms.git
|
|
475
|
+
cd ruby_cms
|
|
476
|
+
bundle install
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Running Tests
|
|
480
|
+
|
|
481
|
+
```bash
|
|
482
|
+
bundle exec rspec
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Tests use an in-memory SQLite database via `spec/support/dummy_app.rb`. No separate dummy Rails app directory is needed.
|
|
486
|
+
|
|
487
|
+
### CSS Compilation
|
|
488
|
+
|
|
489
|
+
```bash
|
|
490
|
+
rails ruby_cms:css:compile_gem
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Architecture
|
|
494
|
+
|
|
495
|
+
```
|
|
496
|
+
lib/ruby_cms.rb # Module: nav_register, register_page, permissions API
|
|
497
|
+
lib/ruby_cms/engine.rb # Rails::Engine: config, initializers, rake tasks, nav registration
|
|
498
|
+
lib/ruby_cms/icons.rb # Named Heroicon SVG registry
|
|
499
|
+
lib/ruby_cms/dashboard_blocks.rb # Dashboard block registry
|
|
500
|
+
lib/ruby_cms/settings.rb # DB-backed settings
|
|
501
|
+
lib/ruby_cms/settings_registry.rb # Settings definitions and defaults
|
|
502
|
+
app/controllers/ruby_cms/admin/ # Admin controllers (base, dashboard, content blocks, etc.)
|
|
503
|
+
app/models/ # ContentBlock, ContentBlockVersion, Permission, etc.
|
|
504
|
+
app/views/ruby_cms/admin/ # Admin views
|
|
505
|
+
app/views/layouts/ruby_cms/ # Admin layout + sidebar
|
|
506
|
+
app/components/ # ViewComponents (AdminPage, etc.)
|
|
507
|
+
app/helpers/ # Content block, settings, dashboard helpers
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### Key Extension Points
|
|
511
|
+
|
|
512
|
+
| What | How |
|
|
513
|
+
|------|-----|
|
|
514
|
+
| New admin page | `rails g ruby_cms:admin_page <name>` |
|
|
515
|
+
| New nav item | `RubyCms.register_page(...)` or `RubyCms.nav_register(...)` |
|
|
516
|
+
| New permission | `RubyCms.register_permission_keys(:key)` |
|
|
517
|
+
| New dashboard block | `RubyCms.dashboard_register(...)` |
|
|
518
|
+
| New setting | `RubyCms::SettingsRegistry.register(...)` |
|
|
519
|
+
| New icon | Pass raw SVG string to `icon:` parameter |
|
|
520
|
+
|
|
521
|
+
## License
|
|
138
522
|
|
|
139
|
-
|
|
140
|
-
* `rails ruby_cms:setup_admin`
|
|
141
|
-
* `rails ruby_cms:content_blocks:seed`
|
|
523
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -20,8 +20,39 @@ module RubyCms
|
|
|
20
20
|
# new_session_path, root_path, etc.
|
|
21
21
|
include RubyCms::Engine.routes.url_helpers
|
|
22
22
|
|
|
23
|
+
# Declare which registered page this controller serves.
|
|
24
|
+
# Looks up the permission from the nav_registry entry and sets a before_action.
|
|
25
|
+
# Usage: cms_page :backups (requires a matching register_page call with key: :backups)
|
|
26
|
+
def self.cms_page(key)
|
|
27
|
+
page_key = key.to_sym
|
|
28
|
+
before_action do
|
|
29
|
+
entry = RubyCms.nav_registry.find {|e| e[:key] == page_key }
|
|
30
|
+
if entry.nil?
|
|
31
|
+
if defined?(Rails.logger)
|
|
32
|
+
Rails.logger.warn("[RubyCMS] cms_page :#{page_key} has no matching register_page entry. " \
|
|
33
|
+
"Only manage_admin is enforced.")
|
|
34
|
+
end
|
|
35
|
+
elsif entry[:permission].present?
|
|
36
|
+
require_permission!(entry[:permission].to_sym)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Public API: dashboard block +data procs and host code may call this on the controller instance.
|
|
42
|
+
def current_user_cms
|
|
43
|
+
@current_user_cms ||= resolve_current_user
|
|
44
|
+
end
|
|
45
|
+
|
|
23
46
|
private
|
|
24
47
|
|
|
48
|
+
def resolve_current_user
|
|
49
|
+
if respond_to?(:current_user, true)
|
|
50
|
+
send(:current_user)
|
|
51
|
+
else
|
|
52
|
+
Rails.application.config.ruby_cms.current_user_resolver&.call(self)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
25
56
|
def require_cms_access
|
|
26
57
|
ensure_authenticated
|
|
27
58
|
require_permission!(:manage_admin)
|
|
@@ -67,18 +98,6 @@ module RubyCms
|
|
|
67
98
|
Rails.application.config.ruby_cms.unauthorized_redirect_path.presence || "/"
|
|
68
99
|
end
|
|
69
100
|
|
|
70
|
-
def current_user_cms
|
|
71
|
-
@current_user_cms ||= resolve_current_user
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def resolve_current_user
|
|
75
|
-
if respond_to?(:current_user, true)
|
|
76
|
-
send(:current_user)
|
|
77
|
-
else
|
|
78
|
-
Rails.application.config.ruby_cms.current_user_resolver&.call(self)
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
101
|
def render_not_found
|
|
83
102
|
render "ruby_cms/errors/not_found",
|
|
84
103
|
status: :not_found,
|
|
@@ -106,6 +125,8 @@ module RubyCms
|
|
|
106
125
|
def model_param_key(model_class, param_name)
|
|
107
126
|
params.key?(param_name) ? param_name : model_class.model_name.param_key.to_sym
|
|
108
127
|
end
|
|
128
|
+
|
|
129
|
+
public :current_user_cms
|
|
109
130
|
end
|
|
110
131
|
end
|
|
111
132
|
end
|