rhales 0.3.0 → 0.5.3
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/.github/renovate.json5 +52 -0
- data/.github/workflows/ci.yml +123 -0
- data/.github/workflows/claude-code-review.yml +69 -0
- data/.github/workflows/claude.yml +49 -0
- data/.github/workflows/code-smells.yml +146 -0
- data/.github/workflows/ruby-lint.yml +78 -0
- data/.github/workflows/yardoc.yml +126 -0
- data/.gitignore +55 -0
- data/.pr_agent.toml +63 -0
- data/.pre-commit-config.yaml +89 -0
- data/.prettierignore +8 -0
- data/.prettierrc +38 -0
- data/.reek.yml +98 -0
- data/.rubocop.yml +428 -0
- data/.serena/.gitignore +3 -0
- data/.yardopts +56 -0
- data/CHANGELOG.md +44 -0
- data/CLAUDE.md +1 -2
- data/Gemfile +29 -0
- data/Gemfile.lock +189 -0
- data/README.md +706 -589
- data/Rakefile +46 -0
- data/debug_context.rb +25 -0
- data/demo/rhales-roda-demo/.gitignore +7 -0
- data/demo/rhales-roda-demo/Gemfile +32 -0
- data/demo/rhales-roda-demo/Gemfile.lock +151 -0
- data/demo/rhales-roda-demo/MAIL.md +405 -0
- data/demo/rhales-roda-demo/README.md +376 -0
- data/demo/rhales-roda-demo/RODA-TEMPLATE-ENGINES.md +192 -0
- data/demo/rhales-roda-demo/Rakefile +49 -0
- data/demo/rhales-roda-demo/app.rb +325 -0
- data/demo/rhales-roda-demo/bin/rackup +26 -0
- data/demo/rhales-roda-demo/config.ru +13 -0
- data/demo/rhales-roda-demo/db/migrate/001_create_rodauth_tables.rb +266 -0
- data/demo/rhales-roda-demo/db/migrate/002_create_rodauth_password_tables.rb +79 -0
- data/demo/rhales-roda-demo/db/migrate/003_add_admin_account.rb +68 -0
- data/demo/rhales-roda-demo/templates/change_login.rue +31 -0
- data/demo/rhales-roda-demo/templates/change_password.rue +36 -0
- data/demo/rhales-roda-demo/templates/close_account.rue +31 -0
- data/demo/rhales-roda-demo/templates/create_account.rue +40 -0
- data/demo/rhales-roda-demo/templates/dashboard.rue +150 -0
- data/demo/rhales-roda-demo/templates/home.rue +78 -0
- data/demo/rhales-roda-demo/templates/layouts/main.rue +168 -0
- data/demo/rhales-roda-demo/templates/login.rue +65 -0
- data/demo/rhales-roda-demo/templates/logout.rue +25 -0
- data/demo/rhales-roda-demo/templates/reset_password.rue +26 -0
- data/demo/rhales-roda-demo/templates/verify_account.rue +27 -0
- data/demo/rhales-roda-demo/test_full_output.rb +27 -0
- data/demo/rhales-roda-demo/test_simple.rb +24 -0
- data/docs/.gitignore +9 -0
- data/docs/architecture/data-flow.md +499 -0
- data/examples/dashboard-with-charts.rue +271 -0
- data/examples/form-with-validation.rue +180 -0
- data/examples/simple-page.rue +61 -0
- data/examples/vue.rue +136 -0
- data/generate-json-schemas.ts +158 -0
- data/json_schemer_migration_summary.md +172 -0
- data/lib/rhales/adapters/base_auth.rb +2 -0
- data/lib/rhales/adapters/base_request.rb +2 -0
- data/lib/rhales/adapters/base_session.rb +2 -0
- data/lib/rhales/adapters.rb +7 -0
- data/lib/rhales/configuration.rb +161 -1
- data/lib/rhales/core/context.rb +354 -0
- data/lib/rhales/{rue_document.rb → core/rue_document.rb} +59 -43
- data/lib/rhales/{template_engine.rb → core/template_engine.rb} +80 -33
- data/lib/rhales/core/view.rb +529 -0
- data/lib/rhales/{view_composition.rb → core/view_composition.rb} +81 -9
- data/lib/rhales/core.rb +9 -0
- data/lib/rhales/errors/hydration_collision_error.rb +2 -0
- data/lib/rhales/errors.rb +2 -0
- data/lib/rhales/hydration/earliest_injection_detector.rb +153 -0
- data/lib/rhales/hydration/hydration_data_aggregator.rb +487 -0
- data/lib/rhales/hydration/hydration_endpoint.rb +215 -0
- data/lib/rhales/hydration/hydration_injector.rb +175 -0
- data/lib/rhales/{hydration_registry.rb → hydration/hydration_registry.rb} +2 -0
- data/lib/rhales/hydration/hydrator.rb +102 -0
- data/lib/rhales/hydration/link_based_injection_detector.rb +195 -0
- data/lib/rhales/hydration/mount_point_detector.rb +109 -0
- data/lib/rhales/hydration/safe_injection_validator.rb +103 -0
- data/lib/rhales/hydration.rb +13 -0
- data/lib/rhales/{refinements → integrations/refinements}/require_refinements.rb +7 -13
- data/lib/rhales/{tilt.rb → integrations/tilt.rb} +26 -18
- data/lib/rhales/integrations.rb +6 -0
- data/lib/rhales/middleware/json_responder.rb +191 -0
- data/lib/rhales/middleware/schema_validator.rb +300 -0
- data/lib/rhales/middleware.rb +6 -0
- data/lib/rhales/parsers/handlebars_parser.rb +2 -0
- data/lib/rhales/parsers/rue_format_parser.rb +55 -36
- data/lib/rhales/parsers.rb +9 -0
- data/lib/rhales/{csp.rb → security/csp.rb} +27 -3
- data/lib/rhales/utils/json_serializer.rb +114 -0
- data/lib/rhales/utils/logging_helpers.rb +75 -0
- data/lib/rhales/utils/schema_extractor.rb +132 -0
- data/lib/rhales/utils/schema_generator.rb +194 -0
- data/lib/rhales/utils.rb +40 -0
- data/lib/rhales/version.rb +5 -1
- data/lib/rhales.rb +47 -19
- data/lib/tasks/rhales_schema.rake +197 -0
- data/package.json +10 -0
- data/pnpm-lock.yaml +345 -0
- data/pnpm-workspace.yaml +2 -0
- data/proofs/error_handling.rb +79 -0
- data/proofs/expanded_object_inheritance.rb +82 -0
- data/proofs/partial_context_scoping_fix.rb +168 -0
- data/proofs/ui_context_partial_inheritance.rb +236 -0
- data/rhales.gemspec +14 -6
- data/schema_vs_data_comparison.md +254 -0
- data/test_direct_access.rb +36 -0
- metadata +142 -18
- data/CLAUDE.locale.txt +0 -7
- data/lib/rhales/context.rb +0 -240
- data/lib/rhales/hydration_data_aggregator.rb +0 -220
- data/lib/rhales/hydrator.rb +0 -141
- data/lib/rhales/parsers/handlebars-grammar-review.txt +0 -39
- data/lib/rhales/view.rb +0 -412
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# Rhales Roda Demo
|
|
2
|
+
|
|
3
|
+
This demo application showcases the power of Rhales RSFC (Ruby Single File Components) templates integrated with a Roda web application using Rodauth for authentication.
|
|
4
|
+
|
|
5
|
+
## Features Demonstrated
|
|
6
|
+
|
|
7
|
+
- **RSFC Templates**: Single file components with `<data>`, `<template>`, `<schema>`, and `<logic>` sections
|
|
8
|
+
- **Schema Validation**: Runtime validation of hydration data against Zod schemas
|
|
9
|
+
- **Client-Side Hydration**: Secure data injection with CSP nonce support
|
|
10
|
+
- **Authentication Integration**: Rodauth with custom Rhales adapters
|
|
11
|
+
- **Dynamic Content**: Posts management with CRUD operations
|
|
12
|
+
- **Handlebars Syntax**: Conditionals, iteration, and partials
|
|
13
|
+
- **API Integration**: Client-side fetching with hydrated configuration
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
Before starting, ensure you have:
|
|
18
|
+
- Ruby 3.0 or higher
|
|
19
|
+
- Bundler gem installed (`gem install bundler`)
|
|
20
|
+
- SQLite3 installed on your system
|
|
21
|
+
- Node.js and pnpm (for schema generation)
|
|
22
|
+
|
|
23
|
+
## Step-by-Step Setup Instructions
|
|
24
|
+
|
|
25
|
+
### Step 1: Navigate to the demo directory
|
|
26
|
+
|
|
27
|
+
From the root of the rhales gem repository:
|
|
28
|
+
```bash
|
|
29
|
+
cd demo/rhales-roda-demo
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Step 2: Install Ruby dependencies
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
bundle install
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
If you encounter errors:
|
|
39
|
+
```bash
|
|
40
|
+
# Update bundler
|
|
41
|
+
gem install bundler
|
|
42
|
+
|
|
43
|
+
# Try again
|
|
44
|
+
bundle install
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Step 3: Install Node.js dependencies (for schema generation)
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pnpm install
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
If you don't have pnpm installed:
|
|
54
|
+
```bash
|
|
55
|
+
npm install -g pnpm
|
|
56
|
+
pnpm install
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Step 4: Generate JSON Schemas from templates
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
bundle exec rake rhales:schema:generate
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This will:
|
|
66
|
+
- Parse all `.rue` templates with `<schema>` sections
|
|
67
|
+
- Execute Zod schema code to generate JSON Schemas
|
|
68
|
+
- Save generated schemas to `public/schemas/`
|
|
69
|
+
|
|
70
|
+
Expected output:
|
|
71
|
+
```
|
|
72
|
+
Schema Generation
|
|
73
|
+
============================================================
|
|
74
|
+
Templates: ./templates
|
|
75
|
+
Output: ./public/schemas
|
|
76
|
+
Zod: (using pnpm exec tsx)
|
|
77
|
+
|
|
78
|
+
Found 2 schema section(s):
|
|
79
|
+
- login (js-zod)
|
|
80
|
+
- dashboard (js-zod)
|
|
81
|
+
|
|
82
|
+
Generating JSON Schemas...
|
|
83
|
+
✓ Generated schema for: login
|
|
84
|
+
✓ Generated schema for: dashboard
|
|
85
|
+
|
|
86
|
+
Results:
|
|
87
|
+
------------------------------------------------------------
|
|
88
|
+
✓ Successfully generated 2 schema(s)
|
|
89
|
+
✓ Output directory: /path/to/demo/rhales-roda-demo/public/schemas
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
You can verify the generated schemas:
|
|
93
|
+
```bash
|
|
94
|
+
ls -la public/schemas/
|
|
95
|
+
cat public/schemas/login.json | jq .
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Step 5: Start the web server
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
bundle exec rackup
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Expected output:
|
|
105
|
+
```
|
|
106
|
+
Puma starting in single mode...
|
|
107
|
+
* Puma version: 6.4.2 (ruby 3.0.0-p0) ("The Eagle of Durango")
|
|
108
|
+
* Min threads: 0
|
|
109
|
+
* Max threads: 5
|
|
110
|
+
* Environment: development
|
|
111
|
+
* PID: 12345
|
|
112
|
+
* Listening on http://127.0.0.1:9292
|
|
113
|
+
* Listening on http://[::1]:9292
|
|
114
|
+
Use Ctrl-C to stop
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Step 6: Open your browser
|
|
118
|
+
|
|
119
|
+
Visit: http://localhost:9292
|
|
120
|
+
|
|
121
|
+
You should see the Rhales Demo homepage with feature cards.
|
|
122
|
+
|
|
123
|
+
## Testing the Demo
|
|
124
|
+
|
|
125
|
+
### Quick Test with Demo Account
|
|
126
|
+
|
|
127
|
+
1. Click **"Login"** in the top navigation
|
|
128
|
+
2. Enter credentials:
|
|
129
|
+
- Email: `demo@example.com`
|
|
130
|
+
- Password: `demo123`
|
|
131
|
+
3. Click **"Login"** button
|
|
132
|
+
4. You should see a dashboard with:
|
|
133
|
+
- Welcome message with the user's name
|
|
134
|
+
- Stats showing Total Posts, Published, and Drafts
|
|
135
|
+
- List of sample posts
|
|
136
|
+
|
|
137
|
+
### Create Your Own Account
|
|
138
|
+
|
|
139
|
+
1. From the homepage, click **"Register"**
|
|
140
|
+
2. Fill out the form:
|
|
141
|
+
- Full Name: `Test User` (or your name)
|
|
142
|
+
- Email: `test@example.com` (any email works)
|
|
143
|
+
- Password: `testpass` (min 6 characters)
|
|
144
|
+
- Confirm Password: `testpass`
|
|
145
|
+
3. Click **"Create Account"**
|
|
146
|
+
4. You'll be automatically logged in and redirected to an empty dashboard
|
|
147
|
+
|
|
148
|
+
### Test Post Management
|
|
149
|
+
|
|
150
|
+
1. Click **"New Post"** button on the dashboard
|
|
151
|
+
2. Fill out the form:
|
|
152
|
+
- Title: `My First Post`
|
|
153
|
+
- Content: `This is a test post using Rhales RSFC templates!`
|
|
154
|
+
- Status: `Published`
|
|
155
|
+
3. Click **"Create Post"**
|
|
156
|
+
4. You should see:
|
|
157
|
+
- Success message: "Post created successfully!"
|
|
158
|
+
- Your post in the dashboard list
|
|
159
|
+
5. Try editing the post:
|
|
160
|
+
- Click **"Edit"** on your post
|
|
161
|
+
- Change the title or content
|
|
162
|
+
- Click **"Update Post"**
|
|
163
|
+
6. Try deleting:
|
|
164
|
+
- Click **"Delete"**
|
|
165
|
+
- Confirm the deletion
|
|
166
|
+
- Post should disappear with success message
|
|
167
|
+
|
|
168
|
+
### Test Client-Side Hydration
|
|
169
|
+
|
|
170
|
+
1. Click **"Profile"** in the navigation
|
|
171
|
+
2. You'll see your account information
|
|
172
|
+
3. Click **"Fetch Stats"** button
|
|
173
|
+
4. Watch as it makes an API call and displays JSON response
|
|
174
|
+
5. Open browser console (F12) to see hydration logs
|
|
175
|
+
|
|
176
|
+
### Logout
|
|
177
|
+
|
|
178
|
+
Click **"Logout"** in the navigation to end your session.
|
|
179
|
+
|
|
180
|
+
## Development Mode
|
|
181
|
+
|
|
182
|
+
For automatic server restarts when files change:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
bundle exec rerun rackup
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Now when you edit any `.rb` or `.rue` file, the server will restart automatically.
|
|
189
|
+
|
|
190
|
+
## Schema Generation and Validation
|
|
191
|
+
|
|
192
|
+
### Generating Schemas
|
|
193
|
+
|
|
194
|
+
Rhales uses Zod schemas defined in `.rue` templates to generate JSON Schemas for runtime validation:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
# Generate all schemas
|
|
198
|
+
bundle exec rake rhales:schema:generate
|
|
199
|
+
|
|
200
|
+
# Custom paths
|
|
201
|
+
bundle exec rake rhales:schema:generate \
|
|
202
|
+
TEMPLATES_DIR=./templates \
|
|
203
|
+
OUTPUT_DIR=./public/schemas
|
|
204
|
+
|
|
205
|
+
# View statistics
|
|
206
|
+
bundle exec rake rhales:schema:stats
|
|
207
|
+
|
|
208
|
+
# Validate existing schemas
|
|
209
|
+
bundle exec rake rhales:schema:validate
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Schema Sections in Templates
|
|
213
|
+
|
|
214
|
+
Templates with `<schema>` sections define the shape of data they expect:
|
|
215
|
+
|
|
216
|
+
```html
|
|
217
|
+
<schema lang="js-zod" window="appData">
|
|
218
|
+
const schema = z.object({
|
|
219
|
+
user: z.object({
|
|
220
|
+
name: z.string(),
|
|
221
|
+
email: z.string().email(),
|
|
222
|
+
}),
|
|
223
|
+
posts: z.array(z.object({
|
|
224
|
+
id: z.number(),
|
|
225
|
+
title: z.string(),
|
|
226
|
+
})),
|
|
227
|
+
});
|
|
228
|
+
</schema>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Runtime Validation
|
|
232
|
+
|
|
233
|
+
When schema validation is enabled (see `app.rb`), the middleware will:
|
|
234
|
+
- Extract hydration data from HTML responses
|
|
235
|
+
- Validate against the generated JSON Schema
|
|
236
|
+
- In development: Fail loudly with detailed error messages
|
|
237
|
+
- In production: Log warnings but continue serving
|
|
238
|
+
|
|
239
|
+
### Directory Structure
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
demo/rhales-roda-demo/
|
|
243
|
+
├── templates/ # Source .rue files
|
|
244
|
+
│ ├── login.rue # With <schema> section
|
|
245
|
+
│ └── dashboard.rue # With <schema> section
|
|
246
|
+
├── public/
|
|
247
|
+
│ └── schemas/ # Generated JSON Schemas (gitignored)
|
|
248
|
+
│ ├── login.json
|
|
249
|
+
│ └── dashboard.json
|
|
250
|
+
└── app.rb # Schema validation middleware config
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Understanding the Code
|
|
254
|
+
|
|
255
|
+
### Key Files to Explore
|
|
256
|
+
|
|
257
|
+
1. **app.rb** - Main application file
|
|
258
|
+
- Shows Rhales configuration
|
|
259
|
+
- Custom auth/session adapters
|
|
260
|
+
- Route definitions
|
|
261
|
+
- `rhales_render` helper method
|
|
262
|
+
|
|
263
|
+
2. **templates/layouts/main.rue** - Layout template
|
|
264
|
+
- Shows conditional navigation
|
|
265
|
+
- CSP nonce usage in styles
|
|
266
|
+
- Flash message handling
|
|
267
|
+
|
|
268
|
+
3. **templates/home.rue** - Homepage
|
|
269
|
+
- Data hydration with `window` attribute
|
|
270
|
+
- Feature iteration
|
|
271
|
+
- Client-side JavaScript integration
|
|
272
|
+
|
|
273
|
+
4. **templates/dashboard/index.rue** - Dashboard
|
|
274
|
+
- Uses partials (`{{> post_item}}`)
|
|
275
|
+
- Conditional rendering
|
|
276
|
+
- Stats display
|
|
277
|
+
|
|
278
|
+
## Troubleshooting
|
|
279
|
+
|
|
280
|
+
### Port 9292 already in use
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
# Use a different port
|
|
284
|
+
bundle exec rackup -p 9293
|
|
285
|
+
|
|
286
|
+
# Or find and kill the process using port 9292
|
|
287
|
+
lsof -i :9292
|
|
288
|
+
kill -9 <PID>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Bundle install fails
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
# Check Ruby version
|
|
295
|
+
ruby -v # Should be 3.0 or higher
|
|
296
|
+
|
|
297
|
+
# Update RubyGems
|
|
298
|
+
gem update --system
|
|
299
|
+
|
|
300
|
+
# Try installing problematic gems individually
|
|
301
|
+
gem install sqlite3 -v '2.0.0'
|
|
302
|
+
gem install rack-session -v '2.0.0'
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Rack::Session errors
|
|
306
|
+
|
|
307
|
+
If you see "uninitialized constant Rack::Session" or "invalid secret" errors:
|
|
308
|
+
```bash
|
|
309
|
+
bundle install # Make sure rack-session and rackup gems are installed
|
|
310
|
+
bundle binstub rack # Fix rackup binstub conflicts if needed
|
|
311
|
+
bundle exec rackup # Try again
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### SQLite3 LoadError
|
|
315
|
+
|
|
316
|
+
Install SQLite3 for your system:
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
# macOS
|
|
320
|
+
brew install sqlite3
|
|
321
|
+
|
|
322
|
+
# Ubuntu/Debian
|
|
323
|
+
sudo apt-get install sqlite3 libsqlite3-dev
|
|
324
|
+
|
|
325
|
+
# Fedora/RHEL
|
|
326
|
+
sudo dnf install sqlite sqlite-devel
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Then reinstall the gem:
|
|
330
|
+
```bash
|
|
331
|
+
gem uninstall sqlite3
|
|
332
|
+
bundle install
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Template not found
|
|
336
|
+
|
|
337
|
+
Verify template files exist:
|
|
338
|
+
```bash
|
|
339
|
+
find templates -name "*.rue" | sort
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Should show:
|
|
343
|
+
```
|
|
344
|
+
templates/auth/login.rue
|
|
345
|
+
templates/auth/register.rue
|
|
346
|
+
templates/dashboard/index.rue
|
|
347
|
+
templates/dashboard/post_form.rue
|
|
348
|
+
templates/dashboard/profile.rue
|
|
349
|
+
templates/home.rue
|
|
350
|
+
templates/layouts/main.rue
|
|
351
|
+
templates/partials/post_item.rue
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### No CSS styling
|
|
355
|
+
|
|
356
|
+
The demo includes inline CSS in the layout. If styles aren't loading:
|
|
357
|
+
1. Check browser console for CSP errors
|
|
358
|
+
2. Verify the `{{runtime.nonce}}` is being replaced in templates
|
|
359
|
+
3. Try hard refresh (Ctrl+Shift+R or Cmd+Shift+R)
|
|
360
|
+
|
|
361
|
+
## Next Steps
|
|
362
|
+
|
|
363
|
+
1. Explore the `.rue` template files to understand RSFC structure
|
|
364
|
+
2. Modify templates and see live changes
|
|
365
|
+
3. Add new routes and templates
|
|
366
|
+
4. Integrate Rhales into your own Ruby applications
|
|
367
|
+
|
|
368
|
+
## Architecture Overview
|
|
369
|
+
|
|
370
|
+
- **Roda**: Lightweight Ruby web framework
|
|
371
|
+
- **Rodauth**: Full-featured authentication
|
|
372
|
+
- **Sequel**: Database toolkit with models
|
|
373
|
+
- **SQLite**: Zero-config database
|
|
374
|
+
- **Rhales**: RSFC template engine with hydration
|
|
375
|
+
|
|
376
|
+
This demo shows how Rhales integrates seamlessly with existing Ruby tools while adding powerful component-based templating.
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Custom Template Engines in Roda
|
|
2
|
+
|
|
3
|
+
Roda's render plugin uses Tilt to support multiple template engines. You can use any template engine that Tilt supports, or integrate custom template engines in several ways.
|
|
4
|
+
|
|
5
|
+
## Using Template Engines Already Supported by Tilt
|
|
6
|
+
|
|
7
|
+
Tilt supports 25+ template engines out of the box. To use a different engine, specify it in the `:engine` option:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
plugin :render, engine: 'haml'
|
|
11
|
+
plugin :render, engine: 'slim'
|
|
12
|
+
plugin :render, engine: 'liquid'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
You can also specify the engine per template:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
render('template', engine: 'haml')
|
|
19
|
+
view('template', engine: 'slim')
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Adding New Template Engines to Tilt
|
|
23
|
+
|
|
24
|
+
As of 2025, Tilt is actively maintained by Jeremy Evans but no longer accepts new community template engine integrations in the main gem. If you want to add support for a template engine not currently supported by Tilt, you should:
|
|
25
|
+
|
|
26
|
+
1. Create a separate gem that provides Tilt integration for your template engine
|
|
27
|
+
2. Have the template engine itself ship with Tilt integration
|
|
28
|
+
|
|
29
|
+
To register a new template engine with Tilt:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
# In your gem or application
|
|
33
|
+
require 'tilt'
|
|
34
|
+
|
|
35
|
+
class MyTemplateEngine < Tilt::Template
|
|
36
|
+
def prepare
|
|
37
|
+
# Parse/compile template during template creation
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def evaluate(scope, locals, &block)
|
|
41
|
+
# Render template with given scope and locals
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Register with Tilt
|
|
46
|
+
Tilt.register MyTemplateEngine, 'myengine'
|
|
47
|
+
|
|
48
|
+
# Or for multiple extensions
|
|
49
|
+
Tilt.register MyTemplateEngine, 'myengine', 'mytempl'
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Then use it in Roda:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
plugin :render, engine: 'myengine'
|
|
56
|
+
render('template') # renders template.myengine
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Layouts
|
|
60
|
+
|
|
61
|
+
In Roda, you can specify layouts and create defaults through the render plugin configuration:
|
|
62
|
+
|
|
63
|
+
Specifying a Layout File
|
|
64
|
+
```ruby
|
|
65
|
+
# Set a specific layout file
|
|
66
|
+
plugin :render, layout: 'my_layout'
|
|
67
|
+
|
|
68
|
+
# Or specify per-render
|
|
69
|
+
render('template', layout: 'specific_layout')
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Creating Default Layout
|
|
73
|
+
|
|
74
|
+
Roda automatically looks for layout.erb (or .engine extension) in your views directory. To create a default layout:
|
|
75
|
+
|
|
76
|
+
```html
|
|
77
|
+
# views/layout.erb
|
|
78
|
+
<!DOCTYPE html>
|
|
79
|
+
<html>
|
|
80
|
+
<head>
|
|
81
|
+
<title>My App</title>
|
|
82
|
+
</head>
|
|
83
|
+
<body>
|
|
84
|
+
<%= yield %>
|
|
85
|
+
</body>
|
|
86
|
+
</html>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Layout Configuration Options
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
plugin :render,
|
|
93
|
+
layout: 'layout', # Default layout file
|
|
94
|
+
layout_opts: {engine: 'erb'}, # Layout-specific options
|
|
95
|
+
views: 'views' # Views directory
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The layout file receives the rendered template content via yield and can access the same instance variables and locals as your templates.
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
## Using Custom Template Classes Directly
|
|
102
|
+
|
|
103
|
+
For maximum control, you can bypass Tilt entirely and provide your own template class using the `:template_class` option:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
class MyCustomTemplate
|
|
107
|
+
def initialize(file, line, options={}, &block)
|
|
108
|
+
@template_string = block ? block.call : File.read(file)
|
|
109
|
+
# Custom initialization
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def render(scope, locals={}, &block)
|
|
113
|
+
# Custom rendering logic
|
|
114
|
+
# Return rendered string
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Use globally
|
|
119
|
+
plugin :render, template_class: MyCustomTemplate
|
|
120
|
+
|
|
121
|
+
# Or per-template
|
|
122
|
+
render('template', template_class: MyCustomTemplate)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Engine-Specific Options
|
|
126
|
+
|
|
127
|
+
Configure options for specific template engines using `:engine_opts`:
|
|
128
|
+
|
|
129
|
+
```ruby
|
|
130
|
+
plugin :render,
|
|
131
|
+
engine_opts: {
|
|
132
|
+
'erb' => {default_encoding: 'UTF-8'},
|
|
133
|
+
'haml' => {format: :html5, escape_html: true},
|
|
134
|
+
'slim' => {pretty: true}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Example: Adding Mustache Support
|
|
139
|
+
|
|
140
|
+
Here's a complete example of adding Mustache template support:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
require 'mustache'
|
|
144
|
+
require 'tilt'
|
|
145
|
+
|
|
146
|
+
class TiltMustacheTemplate < Tilt::Template
|
|
147
|
+
def prepare
|
|
148
|
+
@engine = Mustache.new
|
|
149
|
+
@engine.template = data
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def evaluate(scope, locals, &block)
|
|
153
|
+
@engine.render(locals)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
Tilt.register TiltMustacheTemplate, 'mustache'
|
|
158
|
+
|
|
159
|
+
# In your Roda app
|
|
160
|
+
plugin :render, engine: 'mustache'
|
|
161
|
+
|
|
162
|
+
route do |r|
|
|
163
|
+
r.get do
|
|
164
|
+
view('template', locals: {name: 'World'}) # renders template.mustache
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Tilt Maintenance Status
|
|
170
|
+
|
|
171
|
+
**Current Status**: Tilt is actively maintained by Jeremy Evans (also the maintainer of Roda) as of 2025.
|
|
172
|
+
|
|
173
|
+
**Recent Activity**:
|
|
174
|
+
- Version 2.6.0 released January 13, 2025
|
|
175
|
+
- Version 2.5.0 released December 20, 2024
|
|
176
|
+
- 0 open issues and pull requests (excellent maintenance)
|
|
177
|
+
|
|
178
|
+
**Policy on New Engines**: The Tilt team no longer accepts new community-maintained template integrations into the main gem. New template engines should implement their own Tilt compatibility in separate gems.
|
|
179
|
+
|
|
180
|
+
**Supported Engines**: Tilt currently supports 25+ template engines including ERB, Erubi, Haml, Slim, Markdown engines (CommonMarker, Kramdown, Redcarpet), Sass/SCSS, CoffeeScript, TypeScript, Liquid, Builder, Nokogiri, and many more.
|
|
181
|
+
|
|
182
|
+
## Best Practices
|
|
183
|
+
|
|
184
|
+
1. **Use existing engines**: Check if Tilt already supports your desired template engine before creating a custom integration.
|
|
185
|
+
|
|
186
|
+
2. **Separate gems**: If you need a new template engine, create a separate gem with Tilt integration rather than modifying Tilt directly.
|
|
187
|
+
|
|
188
|
+
3. **Performance**: Use the `:template_class` option for maximum performance if you don't need Tilt's generic interface.
|
|
189
|
+
|
|
190
|
+
4. **Caching**: Custom template classes should support Roda's caching mechanisms for best performance.
|
|
191
|
+
|
|
192
|
+
5. **Error handling**: Ensure your custom template engines provide proper error messages with filename and line number information.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Rakefile for rhales-roda-demo
|
|
2
|
+
|
|
3
|
+
# Load Rhales rake tasks for schema generation
|
|
4
|
+
# Note: In a real project, these would be loaded from the gem
|
|
5
|
+
# For this demo, we load from the local gem directory
|
|
6
|
+
begin
|
|
7
|
+
# Try loading from local gem development directory
|
|
8
|
+
rake_tasks = File.expand_path('../../lib/tasks/rhales_schema.rake', __dir__)
|
|
9
|
+
load rake_tasks if File.exist?(rake_tasks)
|
|
10
|
+
rescue LoadError
|
|
11
|
+
# Fallback: load from installed gem
|
|
12
|
+
spec = Gem::Specification.find_by_name('rhales')
|
|
13
|
+
load "#{spec.gem_dir}/lib/tasks/rhales_schema.rake"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
desc 'Run CI integration test (replicates GitHub Actions workflow)'
|
|
17
|
+
task :ci_test do
|
|
18
|
+
puts "Installing demo dependencies..."
|
|
19
|
+
system('bundle install') || (puts "Failed to install dependencies" && exit(1))
|
|
20
|
+
|
|
21
|
+
puts "Starting demo server..."
|
|
22
|
+
server_pid = spawn('bundle exec rackup -p 9393')
|
|
23
|
+
puts "Server PID: #{server_pid}"
|
|
24
|
+
|
|
25
|
+
begin
|
|
26
|
+
# Wait for server to start
|
|
27
|
+
sleep 1
|
|
28
|
+
|
|
29
|
+
# Test the server (replicates: curl -f http://localhost:9393/ || exit 1)
|
|
30
|
+
puts "Testing demo server..."
|
|
31
|
+
success = system('curl -f http://localhost:9393/')
|
|
32
|
+
puts "Curl exit status: #{$?.exitstatus}"
|
|
33
|
+
|
|
34
|
+
if success
|
|
35
|
+
puts "✓ Demo integration test passed"
|
|
36
|
+
else
|
|
37
|
+
puts "✗ Demo integration test failed"
|
|
38
|
+
exit 1
|
|
39
|
+
end
|
|
40
|
+
ensure
|
|
41
|
+
# Clean up (replicates: pkill -f rackup || true)
|
|
42
|
+
if server_pid
|
|
43
|
+
Process.kill('TERM', server_pid) rescue nil
|
|
44
|
+
Process.wait(server_pid) rescue nil
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
task default: :ci_test
|