rails_templatable 0.1.1
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +205 -0
- data/Rakefile +6 -0
- data/app/assets/stylesheets/rails_templatable/application.css +15 -0
- data/app/controllers/rails_templatable/application_controller.rb +4 -0
- data/app/helpers/rails_templatable/application_helper.rb +4 -0
- data/app/jobs/rails_templatable/application_job.rb +4 -0
- data/app/mailers/rails_templatable/application_mailer.rb +6 -0
- data/app/models/concerns/rails_templatable/has_templates.rb +16 -0
- data/app/models/rails_templatable/application_record.rb +5 -0
- data/app/models/rails_templatable/template.rb +25 -0
- data/app/models/rails_templatable/template_assignment.rb +12 -0
- data/app/views/layouts/rails_templatable/application.html.erb +17 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20250207000000_create_rails_templatable_tables.rb +36 -0
- data/lib/rails_templatable/engine.rb +5 -0
- data/lib/rails_templatable/version.rb +3 -0
- data/lib/rails_templatable.rb +6 -0
- data/lib/tasks/rails_templatable_tasks.rake +4 -0
- data/llms.txt +433 -0
- metadata +88 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 9a6b1d092af3d36bb5050dc292d447db84040459eff960a6fb4f7e6d012ef07c
|
|
4
|
+
data.tar.gz: 0115da00cf3adc178efaccdacf79c1942894886931543f81d5620aa5467ecf2f
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d2ec972556b40651aecfaba05b9a23ec0321126838fda933adef084baea0003e474d1b1f0e3dccd7d23c20ac836fb1ce55052ec32f2689ccf9cd1dc7bad9feb7
|
|
7
|
+
data.tar.gz: af8843f690fa3b1c6c2bef5e982d540c00f7ef44d573ff9976b80c3601bbd5c02b2799c62118a6a74425334d08c383f077909059eca7af1f0f5c96d1f38dff11
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright aric.zheng
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# RailsTemplatable
|
|
2
|
+
|
|
3
|
+
A lightweight Rails Engine that enables any ActiveRecord model to associate with templates for content management. Built with a polymorphic many-to-many relationship, allowing any model to have multiple templates.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎯 **Polymorphic Associations** - Attach templates to any ActiveRecord model
|
|
8
|
+
- 📝 **Multiple Content Formats** - Support for HTML, Markdown, and plain text
|
|
9
|
+
- 🔗 **Many-to-Many Relationship** - Models can have multiple templates, templates can be used by multiple models
|
|
10
|
+
- 🏷️ **Flexible Categories** - User-defined template categories (no hardcoded types)
|
|
11
|
+
- 🔒 **Database Constraints** - Unique constraints at database level
|
|
12
|
+
- 🚀 **Easy Integration** - Simple concern-based inclusion
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
Add this line to your application's Gemfile:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
gem "rails_templatable"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
And then execute:
|
|
23
|
+
```bash
|
|
24
|
+
$ bundle install
|
|
25
|
+
$ bin/rails railties:install:migrations FROM=rails_templatable
|
|
26
|
+
$ bin/rails db:migrate
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### 1. Enable templates in your models
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
class Post < ApplicationRecord
|
|
35
|
+
include RailsTemplatable::HasTemplates
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class WorkLog < ApplicationRecord
|
|
39
|
+
include RailsTemplatable::HasTemplates
|
|
40
|
+
end
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. Create templates
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
# HTML template
|
|
47
|
+
email_template = RailsTemplatable::Template.create!(
|
|
48
|
+
category: "email_template",
|
|
49
|
+
content: "<h1>Welcome!</h1><p>Hello {{name}}</p>",
|
|
50
|
+
content_format: :html
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Markdown template
|
|
54
|
+
doc_template = RailsTemplatable::Template.create!(
|
|
55
|
+
category: "documentation",
|
|
56
|
+
content: "# Documentation\n\nThis is a **markdown** template.",
|
|
57
|
+
content_format: :markdown
|
|
58
|
+
)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 3. Associate templates with models
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
post = Post.create(title: "My Post", content: "Content here")
|
|
65
|
+
|
|
66
|
+
# Add templates
|
|
67
|
+
post.templates << email_template
|
|
68
|
+
post.templates << doc_template
|
|
69
|
+
|
|
70
|
+
# Query templates
|
|
71
|
+
post.templates.where(category: "email_template")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Content Formats
|
|
75
|
+
|
|
76
|
+
The engine supports three content formats out of the box:
|
|
77
|
+
|
|
78
|
+
- `html` (0) - HTML content
|
|
79
|
+
- `markdown` (1) - Markdown content
|
|
80
|
+
- `txt` (2) - Plain text content (default)
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
RailsTemplatable::Template.create!(
|
|
84
|
+
category: "notification",
|
|
85
|
+
content: "Simple text notification",
|
|
86
|
+
content_format: :txt
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Database Schema
|
|
91
|
+
|
|
92
|
+
### rails_templatable_templates
|
|
93
|
+
|
|
94
|
+
| Column | Type | Description |
|
|
95
|
+
|--------|------|-------------|
|
|
96
|
+
| `category` | string | Template category (user-defined) |
|
|
97
|
+
| `content` | text | Template content |
|
|
98
|
+
| `content_format` | integer | Content format (0: html, 1: markdown, 2: txt) |
|
|
99
|
+
| `created_at` | datetime | Creation timestamp |
|
|
100
|
+
| `updated_at` | datetime | Update timestamp |
|
|
101
|
+
|
|
102
|
+
### rails_templatable_assignments
|
|
103
|
+
|
|
104
|
+
| Column | Type | Description |
|
|
105
|
+
|--------|------|-------------|
|
|
106
|
+
| `template_id` | integer | Foreign key to template |
|
|
107
|
+
| `templatable_id` | integer | Polymorphic foreign key ID |
|
|
108
|
+
| `templatable_type` | string | Polymorphic foreign key type |
|
|
109
|
+
| `created_at` | datetime | Creation timestamp |
|
|
110
|
+
| `updated_at` | datetime | Update timestamp |
|
|
111
|
+
|
|
112
|
+
## Advanced Usage
|
|
113
|
+
|
|
114
|
+
### Query helper methods
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
# Get all objects using a specific template
|
|
118
|
+
template.assigned_to(Post)
|
|
119
|
+
|
|
120
|
+
# Get all templates used on a specific model class
|
|
121
|
+
RailsTemplatable::Template.for_model(Post)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Checking template associations
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
# Check if a post has email templates
|
|
128
|
+
post.templates.where(category: "email_template").exists?
|
|
129
|
+
|
|
130
|
+
# Get all HTML format templates
|
|
131
|
+
post.templates.html
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Why `category` instead of `type`?
|
|
135
|
+
|
|
136
|
+
We use `category` instead of `type` to avoid conflicts with Rails' Single Table Inheritance (STI) feature, which reserves the `type` column for storing class names.
|
|
137
|
+
|
|
138
|
+
## Development
|
|
139
|
+
|
|
140
|
+
### Running tests in the dummy app
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
cd test/dummy
|
|
144
|
+
ruby test_templatable.rb
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Database schema
|
|
148
|
+
|
|
149
|
+
After running migrations, your schema will include:
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
create_table "rails_templatable_templates" do |t|
|
|
153
|
+
t.string "category", null: false
|
|
154
|
+
t.text "content"
|
|
155
|
+
t.integer "content_format", default: 2, null: false
|
|
156
|
+
t.index ["category"], name: "index_rails_templatable_templates_on_category"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
create_table "rails_templatable_assignments" do |t|
|
|
160
|
+
t.integer "template_id", null: false
|
|
161
|
+
t.string "templatable_type", null: false
|
|
162
|
+
t.integer "templatable_id", null: false
|
|
163
|
+
t.index ["template_id", "templatable_type", "templatable_id"],
|
|
164
|
+
name: "index_templatable_assignments", unique: true
|
|
165
|
+
end
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Architecture
|
|
169
|
+
|
|
170
|
+
This engine follows the same pattern as [rails_badgeable](https://github.com/afeiship/rails_badgeable):
|
|
171
|
+
|
|
172
|
+
- **Isolated namespace** - All models namespaced under `RailsTemplatable`
|
|
173
|
+
- **Polymorphic associations** - Using `templatable` for flexible model associations
|
|
174
|
+
- **Concern-based inclusion** - Simple `include RailsTemplatable::HasTemplates` to enable
|
|
175
|
+
- **Database-level constraints** - Unique indexes prevent duplicate associations
|
|
176
|
+
|
|
177
|
+
## Comparison with rails_badgeable
|
|
178
|
+
|
|
179
|
+
| Feature | rails_badgeable | rails_templatable |
|
|
180
|
+
|---------|-----------------|-------------------|
|
|
181
|
+
| Main Model | Badge | Template |
|
|
182
|
+
| Fields | name, description | category, content, content_format |
|
|
183
|
+
| Enum Support | No | Yes (content_format) |
|
|
184
|
+
| Polymorphic Name | assignable | templatable |
|
|
185
|
+
| Concern | HasBadges | HasTemplates |
|
|
186
|
+
|
|
187
|
+
## Future Enhancements
|
|
188
|
+
|
|
189
|
+
- Template variable interpolation
|
|
190
|
+
- Template versioning
|
|
191
|
+
- Template inheritance
|
|
192
|
+
- Template preview functionality
|
|
193
|
+
- I18n multi-language support
|
|
194
|
+
|
|
195
|
+
## Contributing
|
|
196
|
+
|
|
197
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
202
|
+
|
|
203
|
+
## Credits
|
|
204
|
+
|
|
205
|
+
Built with inspiration from [rails_badgeable](https://github.com/afeiship/rails_badgeable).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
|
3
|
+
* listed below.
|
|
4
|
+
*
|
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
|
7
|
+
*
|
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
|
11
|
+
* It is generally better to create a new file per style scope.
|
|
12
|
+
*
|
|
13
|
+
*= require_tree .
|
|
14
|
+
*= require_self
|
|
15
|
+
*/
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module RailsTemplatable
|
|
2
|
+
module HasTemplates
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
has_many :rails_templatable_assignments,
|
|
7
|
+
as: :templatable,
|
|
8
|
+
class_name: "RailsTemplatable::TemplateAssignment",
|
|
9
|
+
dependent: :destroy
|
|
10
|
+
|
|
11
|
+
has_many :templates,
|
|
12
|
+
through: :rails_templatable_assignments,
|
|
13
|
+
class_name: "RailsTemplatable::Template"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module RailsTemplatable
|
|
2
|
+
class Template < ApplicationRecord
|
|
3
|
+
self.table_name = "rails_templatable_templates"
|
|
4
|
+
|
|
5
|
+
has_many :assignments, class_name: "RailsTemplatable::TemplateAssignment", dependent: :destroy
|
|
6
|
+
|
|
7
|
+
enum :content_format, { html: 0, markdown: 1, txt: 2 }
|
|
8
|
+
|
|
9
|
+
validates :category, presence: true
|
|
10
|
+
validates :content, presence: true
|
|
11
|
+
validates :content_format, presence: true
|
|
12
|
+
|
|
13
|
+
# Returns all associated records of the given class that have this template
|
|
14
|
+
def assigned_to(klass)
|
|
15
|
+
templatable_ids = assignments.where(templatable_type: klass.base_class.name).pluck(:templatable_id)
|
|
16
|
+
klass.where(id: templatable_ids)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Class method: Returns all templates ever used on the given model class
|
|
20
|
+
def self.for_model(klass)
|
|
21
|
+
template_ids = TemplateAssignment.where(templatable_type: klass.base_class.name).distinct.pluck(:template_id)
|
|
22
|
+
where(id: template_ids)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module RailsTemplatable
|
|
2
|
+
class TemplateAssignment < ApplicationRecord
|
|
3
|
+
self.table_name = "rails_templatable_assignments"
|
|
4
|
+
|
|
5
|
+
belongs_to :template, class_name: "RailsTemplatable::Template"
|
|
6
|
+
belongs_to :templatable, polymorphic: true
|
|
7
|
+
|
|
8
|
+
validates :template, presence: true
|
|
9
|
+
validates :templatable, presence: true
|
|
10
|
+
validates :template_id, uniqueness: { scope: [:templatable_type, :templatable_id] }
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Rails templatable</title>
|
|
5
|
+
<%= csrf_meta_tags %>
|
|
6
|
+
<%= csp_meta_tag %>
|
|
7
|
+
|
|
8
|
+
<%= yield :head %>
|
|
9
|
+
|
|
10
|
+
<%= stylesheet_link_tag "rails_templatable/application", media: "all" %>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
|
|
14
|
+
<%= yield %>
|
|
15
|
+
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateRailsTemplatableTables < ActiveRecord::Migration[6.0]
|
|
4
|
+
def change
|
|
5
|
+
# Create templates table
|
|
6
|
+
# To add custom fields to templates, add columns here, e.g.:
|
|
7
|
+
# t.string :name
|
|
8
|
+
# t.text :description
|
|
9
|
+
create_table :rails_templatable_templates do |t|
|
|
10
|
+
t.string :category, null: false
|
|
11
|
+
t.text :content
|
|
12
|
+
t.integer :content_format, null: false, default: 2
|
|
13
|
+
|
|
14
|
+
t.timestamps
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
add_index :rails_templatable_templates, :category
|
|
18
|
+
|
|
19
|
+
# Create join table for polymorphic associations
|
|
20
|
+
# To add custom fields to assignments, add columns here, e.g.:
|
|
21
|
+
# t.integer :priority
|
|
22
|
+
# t.boolean :active, default: true
|
|
23
|
+
create_table :rails_templatable_assignments do |t|
|
|
24
|
+
t.references :template, null: false, foreign_key: { to_table: :rails_templatable_templates }
|
|
25
|
+
t.references :templatable, polymorphic: true, null: false, index: false
|
|
26
|
+
|
|
27
|
+
t.timestamps
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Custom composite index with short name
|
|
31
|
+
add_index :rails_templatable_assignments,
|
|
32
|
+
[:template_id, :templatable_type, :templatable_id],
|
|
33
|
+
unique: true,
|
|
34
|
+
name: "index_templatable_assignments"
|
|
35
|
+
end
|
|
36
|
+
end
|
data/llms.txt
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
# Rails Templatable
|
|
2
|
+
|
|
3
|
+
> Rails Templatable is a lightweight, reusable Rails Engine that enables any ActiveRecord model to associate with templates for content management via polymorphic many-to-many relationships.
|
|
4
|
+
|
|
5
|
+
It provides a clean, namespace-isolated way to add template functionality to your Rails applications without polluting your models or database schema. The engine uses standard Rails conventions and is fully compatible with Rails 6.0+.
|
|
6
|
+
|
|
7
|
+
**Important notes for AI agents:**
|
|
8
|
+
- This is a Rails Engine, not a mountable engine (no routes, controllers, or views)
|
|
9
|
+
- All models are namespaced under `RailsTemplatable` module
|
|
10
|
+
- Table names are explicitly set to avoid conflicts: `rails_templatable_templates` and `rails_templatable_assignments`
|
|
11
|
+
- The engine uses polymorphic associations, allowing templates to be assigned to ANY model
|
|
12
|
+
- Uses `category` instead of `type` to avoid Rails STI (Single Table Inheritance) conflicts
|
|
13
|
+
- Supports three content formats: HTML (0), Markdown (1), and TXT (2)
|
|
14
|
+
- The `content_format` field uses Rails enum with new syntax: `enum :content_format, { html: 0, markdown: 1, txt: 2 }`
|
|
15
|
+
|
|
16
|
+
## Documentation
|
|
17
|
+
|
|
18
|
+
- [GitHub Repository](https://github.com/afeiship/rails_templatable): Source code and issue tracking
|
|
19
|
+
- [README](README.md): Installation guide and basic usage examples
|
|
20
|
+
- [PRD](docs/01-prd.md): Detailed product requirements and design specifications
|
|
21
|
+
- [Usage Guide](docs/02-usage.md): Comprehensive usage examples
|
|
22
|
+
- [Implementation Summary](docs/03-implementation-summary.md): Implementation details and testing results
|
|
23
|
+
|
|
24
|
+
## Key Features
|
|
25
|
+
|
|
26
|
+
- **Polymorphic Associations:** Assign templates to any ActiveRecord model (Post, WorkLog, User, etc.)
|
|
27
|
+
- **Multiple Content Formats:** Support for HTML, Markdown, and plain text templates
|
|
28
|
+
- **Database-level Constraints:** Unique indexes prevent duplicate template assignments
|
|
29
|
+
- **Namespace Isolated:** All models under `RailsTemplatable` namespace to avoid conflicts
|
|
30
|
+
- **Explicit Table Names:** No reliance on Rails auto-inflection
|
|
31
|
+
- **Flexible Categories:** User-defined template categories (no hardcoded types)
|
|
32
|
+
- **Rails 6.0+ Compatible:** Works with modern Rails versions (tested on Rails 8.1.2)
|
|
33
|
+
- **Zero Configuration:** Works out of the box after migration
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
Add to your Gemfile:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
gem 'rails_templatable'
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Run migrations:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
rails railties:install:migrations FROM=rails_templatable
|
|
47
|
+
rails db:migrate
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Basic Usage
|
|
51
|
+
|
|
52
|
+
### Include the Concern
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
class Post < ApplicationRecord
|
|
56
|
+
include RailsTemplatable::HasTemplates
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class WorkLog < ApplicationRecord
|
|
60
|
+
include RailsTemplatable::HasTemplates
|
|
61
|
+
end
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Create and Assign Templates
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
# Create an HTML template
|
|
68
|
+
email_template = RailsTemplatable::Template.create!(
|
|
69
|
+
category: "email_template",
|
|
70
|
+
content: "<h1>Welcome!</h1><p>Hello {{name}}</p>",
|
|
71
|
+
content_format: :html
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Create a Markdown template
|
|
75
|
+
doc_template = RailsTemplatable::Template.create!(
|
|
76
|
+
category: "documentation",
|
|
77
|
+
content: "# Documentation\n\nThis is a **markdown** template.",
|
|
78
|
+
content_format: :markdown
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Assign to a post
|
|
82
|
+
post = Post.create(title: "Hello World")
|
|
83
|
+
post.templates << email_template
|
|
84
|
+
post.templates << doc_template
|
|
85
|
+
|
|
86
|
+
# Check if post has the template
|
|
87
|
+
post.templates.include?(email_template) # => true
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Query Methods
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
# Get all posts with a specific template
|
|
94
|
+
template.assigned_to(Post)
|
|
95
|
+
# => [#<Post id: 1, ...>, #<Post id: 3, ...>]
|
|
96
|
+
|
|
97
|
+
# Get all templates ever used on Post model
|
|
98
|
+
RailsTemplatable::Template.for_model(Post)
|
|
99
|
+
# => [#<RailsTemplatable::Template category: "email_template">, ...]
|
|
100
|
+
|
|
101
|
+
# Filter templates by category
|
|
102
|
+
post.templates.where(category: "email_template")
|
|
103
|
+
|
|
104
|
+
# Filter templates by content format
|
|
105
|
+
post.templates.html # HTML templates only
|
|
106
|
+
post.templates.markdown # Markdown templates only
|
|
107
|
+
post.templates.txt # Plain text templates only
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Remove Templates
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
# Remove a specific template
|
|
114
|
+
post.templates.destroy(email_template)
|
|
115
|
+
|
|
116
|
+
# Remove all templates
|
|
117
|
+
post.templates.clear
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Database Schema
|
|
121
|
+
|
|
122
|
+
### templates table
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
create_table :rails_templatable_templates do |t|
|
|
126
|
+
t.string :category, null: false
|
|
127
|
+
t.text :content
|
|
128
|
+
t.integer :content_format, null: false, default: 2
|
|
129
|
+
t.timestamps
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
add_index :rails_templatable_templates, :category
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Field Details:**
|
|
136
|
+
- `category` (string) - User-defined template category (e.g., "email_template", "documentation")
|
|
137
|
+
- `content` (text) - Template content (static text, no variable replacement)
|
|
138
|
+
- `content_format` (integer) - Content format: 0=HTML, 1=Markdown, 2=TXT (default)
|
|
139
|
+
- Index on `category` for fast lookups
|
|
140
|
+
|
|
141
|
+
### assignments table (join table)
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
create_table :rails_templatable_assignments do |t|
|
|
145
|
+
t.references :template, null: false, foreign_key: { to_table: :rails_templatable_templates }
|
|
146
|
+
t.references :templatable, polymorphic: true, null: false
|
|
147
|
+
t.timestamps
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
add_index :rails_templatable_assignments,
|
|
151
|
+
[:template_id, :templatable_type, :templatable_id],
|
|
152
|
+
unique: true,
|
|
153
|
+
name: "index_templatable_assignments"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Constraint:** Unique composite index prevents duplicate template assignments to the same record.
|
|
157
|
+
|
|
158
|
+
## Content Formats
|
|
159
|
+
|
|
160
|
+
The engine supports three content formats via Rails enum:
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
enum :content_format, {
|
|
164
|
+
html: 0, # HTML content
|
|
165
|
+
markdown: 1, # Markdown content
|
|
166
|
+
txt: 2 # Plain text content (default)
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Usage:**
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
# Create different format templates
|
|
174
|
+
html_template = RailsTemplatable::Template.create!(
|
|
175
|
+
category: "email",
|
|
176
|
+
content: "<h1>Welcome</h1>",
|
|
177
|
+
content_format: :html
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
markdown_template = RailsTemplatable::Template.create!(
|
|
181
|
+
category: "docs",
|
|
182
|
+
content: "# Guide\n\nInstructions here",
|
|
183
|
+
content_format: :markdown
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
txt_template = RailsTemplatable::Template.create!(
|
|
187
|
+
category: "notification",
|
|
188
|
+
content: "Simple text message",
|
|
189
|
+
content_format: :txt
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Query by format
|
|
193
|
+
post.templates.html # => [#<RailsTemplatable::Template content_format: 0>]
|
|
194
|
+
post.templates.markdown # => [#<RailsTemplatable::Template content_format: 1>]
|
|
195
|
+
post.templates.txt # => [#<RailsTemplatable::Template content_format: 2>]
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Available Methods
|
|
199
|
+
|
|
200
|
+
### Template Instance Methods
|
|
201
|
+
|
|
202
|
+
- `template.assigned_to(klass)` - Returns all records of the given class that have this template
|
|
203
|
+
```ruby
|
|
204
|
+
template.assigned_to(Post) # => [post1, post2]
|
|
205
|
+
template.assigned_to(WorkLog) # => [work_log1, work_log3]
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Template Class Methods
|
|
209
|
+
|
|
210
|
+
- `RailsTemplatable::Template.for_model(klass)` - Returns all templates ever used on the given model class
|
|
211
|
+
```ruby
|
|
212
|
+
RailsTemplatable::Template.for_model(Post) # => [template1, template2]
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Model Methods (with HasTemplates concern)
|
|
216
|
+
|
|
217
|
+
- `model.templates` - Returns all templates assigned to this record
|
|
218
|
+
- `model.templates << template` - Assign a template to this record
|
|
219
|
+
- `model.templates.delete(template)` - Remove a specific template
|
|
220
|
+
- `model.templates.clear` - Remove all templates
|
|
221
|
+
- `model.rails_templatable_assignments` - Direct access to join records
|
|
222
|
+
- `model.templates.where(category: "email_template")` - Filter by category
|
|
223
|
+
- `model.templates.html` - Get HTML format templates only
|
|
224
|
+
- `model.templates.markdown` - Get Markdown format templates only
|
|
225
|
+
- `model.templates.txt` - Get plain text templates only
|
|
226
|
+
|
|
227
|
+
## Advanced Usage
|
|
228
|
+
|
|
229
|
+
### Custom Fields
|
|
230
|
+
|
|
231
|
+
You can add custom fields to templates or assignments via migrations:
|
|
232
|
+
|
|
233
|
+
```ruby
|
|
234
|
+
# Add custom fields to templates
|
|
235
|
+
add_column :rails_templatable_templates, :name, :string
|
|
236
|
+
add_column :rails_templatable_templates, :description, :text
|
|
237
|
+
add_column :rails_templatable_templates, :locale, :string, default: 'en'
|
|
238
|
+
|
|
239
|
+
# Add custom fields to assignments
|
|
240
|
+
add_column :rails_templatable_assignments, :priority, :integer, default: 0
|
|
241
|
+
add_column :rails_templatable_assignments, :active, :boolean, default: true
|
|
242
|
+
add_column :rails_templatable_assignments, :assigned_at, :datetime
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Accessing Assignment Data
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
# Get assignment metadata
|
|
249
|
+
assignment = post.rails_templatable_assignments.find_by(template_id: 1)
|
|
250
|
+
assignment.priority # => 1
|
|
251
|
+
assignment.created_at # => assignment time
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Conditional Scopes
|
|
255
|
+
|
|
256
|
+
Add scopes to your models:
|
|
257
|
+
|
|
258
|
+
```ruby
|
|
259
|
+
class Post < ApplicationRecord
|
|
260
|
+
include RailsTemplatable::HasTemplates
|
|
261
|
+
|
|
262
|
+
scope :with_email_template, -> {
|
|
263
|
+
joins(:templates)
|
|
264
|
+
.where(rails_templatable_templates: { category: 'email_template' })
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
scope :with_html_templates, -> {
|
|
268
|
+
joins(:templates)
|
|
269
|
+
.where(rails_templatable_templates: { content_format: 0 })
|
|
270
|
+
}
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Usage
|
|
274
|
+
Post.with_email_template # All posts with email templates
|
|
275
|
+
Post.with_html_templates # All posts with HTML templates
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Checking Template Existence
|
|
279
|
+
|
|
280
|
+
```ruby
|
|
281
|
+
# Check if a post has templates from a specific category
|
|
282
|
+
post.templates.where(category: "email_template").exists? # => true/false
|
|
283
|
+
|
|
284
|
+
# Check if a post has HTML templates
|
|
285
|
+
post.templates.html.any? # => true/false
|
|
286
|
+
|
|
287
|
+
# Count templates by format
|
|
288
|
+
post.templates.html.count # => 2
|
|
289
|
+
post.templates.markdown.count # => 1
|
|
290
|
+
post.templates.txt.count # => 0
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## Testing
|
|
294
|
+
|
|
295
|
+
Run the test script in the dummy app:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
cd test/dummy
|
|
299
|
+
ruby test_templatable.rb
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Expected output:
|
|
303
|
+
```
|
|
304
|
+
🚀 Testing Rails Templatable Engine
|
|
305
|
+
==================================================
|
|
306
|
+
|
|
307
|
+
📝 Creating templates...
|
|
308
|
+
✓ Created HTML email template
|
|
309
|
+
✓ Created Markdown documentation template
|
|
310
|
+
✓ Created text notification template
|
|
311
|
+
|
|
312
|
+
📄 Creating Posts and WorkLogs...
|
|
313
|
+
✓ Created 2 Posts and 2 WorkLogs
|
|
314
|
+
|
|
315
|
+
🔗 Associating templates with models...
|
|
316
|
+
✓ Templates associated successfully
|
|
317
|
+
|
|
318
|
+
🔍 Testing queries...
|
|
319
|
+
Post1 has 2 templates
|
|
320
|
+
Post1 templates: email_template, documentation
|
|
321
|
+
...
|
|
322
|
+
|
|
323
|
+
✅ All tests passed successfully!
|
|
324
|
+
==================================================
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## Project Structure
|
|
328
|
+
|
|
329
|
+
```
|
|
330
|
+
lib/
|
|
331
|
+
├── rails_templatable.rb # Main entry point
|
|
332
|
+
├── rails_templatable/
|
|
333
|
+
│ ├── engine.rb # Engine definition
|
|
334
|
+
│ └── version.rb # Version info
|
|
335
|
+
app/models/
|
|
336
|
+
├── rails_templatable/
|
|
337
|
+
│ ├── application_record.rb # Base AR class
|
|
338
|
+
│ ├── template.rb # Template model
|
|
339
|
+
│ └── template_assignment.rb # Join model
|
|
340
|
+
└── models/concerns/
|
|
341
|
+
└── rails_templatable/
|
|
342
|
+
└── has_templates.rb # Concern for host models
|
|
343
|
+
db/migrate/
|
|
344
|
+
└── 20250207000000_create_rails_templatable_tables.rb # Migration
|
|
345
|
+
test/dummy/
|
|
346
|
+
├── app/models/
|
|
347
|
+
│ ├── post.rb # Example: Post with HasTemplates
|
|
348
|
+
│ └── work_log.rb # Example: WorkLog with HasTemplates
|
|
349
|
+
├── db/schema.rb # Database schema
|
|
350
|
+
└── test_templatable.rb # Integration test script
|
|
351
|
+
docs/
|
|
352
|
+
├── 01-prd.md # Product Requirements Document
|
|
353
|
+
├── 02-usage.md # Usage Guide
|
|
354
|
+
└── 03-implementation-summary.md # Implementation Summary
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Comparison with rails_badgeable
|
|
358
|
+
|
|
359
|
+
| Feature | rails_badgeable | rails_templatable |
|
|
360
|
+
|---------|-----------------|-------------------|
|
|
361
|
+
| Main Model | Badge | Template |
|
|
362
|
+
| Fields | name, description | category, content, content_format |
|
|
363
|
+
| Enum Support | No | Yes (content_format: html/markdown/txt) |
|
|
364
|
+
| Polymorphic Name | assignable | templatable |
|
|
365
|
+
| Concern | HasBadges | HasTemplates |
|
|
366
|
+
| STI Conflict Handling | N/A | Uses `category` to avoid `type` conflict |
|
|
367
|
+
|
|
368
|
+
## Usage Notes for Agents
|
|
369
|
+
|
|
370
|
+
- **Always use full namespace:** `RailsTemplatable::Template` (no alias generator yet)
|
|
371
|
+
- **Categories are user-defined:** No predefined categories, use any string value
|
|
372
|
+
- **Content formats are enforced:** Use symbols: `:html`, `:markdown`, `:txt`
|
|
373
|
+
- **Polymorphic type stores class name:** Uses `base_class.name` for STI support
|
|
374
|
+
- **Database prevents duplicates:** Unique index on `[template_id, templatable_type, templatable_id]`
|
|
375
|
+
- **Engine is not mountable:** No routes, controllers, or views included
|
|
376
|
+
- **Migrations must be copied:** Use `rails railties:install:migrations FROM=rails_templatable`
|
|
377
|
+
- **Custom fields require migration:** Add columns directly to tables if needed
|
|
378
|
+
- **Join table accessible:** `model.rails_templatable_assignments` for metadata access
|
|
379
|
+
- **`category` field is indexed:** Efficient queries by category
|
|
380
|
+
- **Default content_format is TXT:** New templates default to plain text (2)
|
|
381
|
+
- **Rails 8.x enum syntax:** Must use `enum :content_format, { ... }` not `enum content_format: { ... }`
|
|
382
|
+
|
|
383
|
+
## Architecture Decisions
|
|
384
|
+
|
|
385
|
+
### Why `category` instead of `type`?
|
|
386
|
+
|
|
387
|
+
Rails reserves the `type` column for Single Table Inheritance (STI). To avoid conflicts:
|
|
388
|
+
- We use `category` for template classification
|
|
389
|
+
- Users can define any category string (email_template, documentation, etc.)
|
|
390
|
+
- No hardcoded enum or predefined types
|
|
391
|
+
|
|
392
|
+
### Why explicit table names?
|
|
393
|
+
|
|
394
|
+
To prevent any naming conflicts:
|
|
395
|
+
- `rails_templatable_templates` - Template storage
|
|
396
|
+
- `rails_templatable_assignments` - Polymorphic join table
|
|
397
|
+
- No reliance on Rails auto-inflection
|
|
398
|
+
|
|
399
|
+
### Why enum for content_format?
|
|
400
|
+
|
|
401
|
+
Using Rails enum provides:
|
|
402
|
+
- Type safety (only valid formats)
|
|
403
|
+
- Helper methods (`html`, `markdown`, `txt`)
|
|
404
|
+
- Integer storage for efficiency
|
|
405
|
+
- Easy to extend with new formats
|
|
406
|
+
|
|
407
|
+
## Development
|
|
408
|
+
|
|
409
|
+
- Rails Engine (non-mountable)
|
|
410
|
+
- Ruby 3.0+ required
|
|
411
|
+
- Rails 6.0+ compatible (tested on Rails 8.1.2)
|
|
412
|
+
- MIT License
|
|
413
|
+
- Author: aric.zheng <1290657123@qq.com>
|
|
414
|
+
- Version: 0.1.0
|
|
415
|
+
|
|
416
|
+
## Future Enhancements
|
|
417
|
+
|
|
418
|
+
Planned features for future versions:
|
|
419
|
+
- Template variable interpolation (replace `{{name}}` with actual data)
|
|
420
|
+
- Template versioning system
|
|
421
|
+
- Template inheritance and overriding
|
|
422
|
+
- Template preview functionality
|
|
423
|
+
- I18n multi-language support
|
|
424
|
+
- Short alias generator (like rails_badgeable)
|
|
425
|
+
|
|
426
|
+
## Contributing
|
|
427
|
+
|
|
428
|
+
When contributing to rails_templatable:
|
|
429
|
+
1. Follow the existing architecture patterns
|
|
430
|
+
2. Maintain backward compatibility
|
|
431
|
+
3. Add tests for new features
|
|
432
|
+
4. Update this llms.txt file with API changes
|
|
433
|
+
5. Ensure Rails 6.0+ compatibility
|
metadata
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rails_templatable
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- aric.zheng
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-02-06 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rails
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '6.0'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '9.0'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '6.0'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '9.0'
|
|
33
|
+
description: A lightweight Rails Engine that enables any ActiveRecord model to use
|
|
34
|
+
templates for content generation and management.
|
|
35
|
+
email:
|
|
36
|
+
- 1290657123@qq.com
|
|
37
|
+
executables: []
|
|
38
|
+
extensions: []
|
|
39
|
+
extra_rdoc_files: []
|
|
40
|
+
files:
|
|
41
|
+
- MIT-LICENSE
|
|
42
|
+
- README.md
|
|
43
|
+
- Rakefile
|
|
44
|
+
- app/assets/stylesheets/rails_templatable/application.css
|
|
45
|
+
- app/controllers/rails_templatable/application_controller.rb
|
|
46
|
+
- app/helpers/rails_templatable/application_helper.rb
|
|
47
|
+
- app/jobs/rails_templatable/application_job.rb
|
|
48
|
+
- app/mailers/rails_templatable/application_mailer.rb
|
|
49
|
+
- app/models/concerns/rails_templatable/has_templates.rb
|
|
50
|
+
- app/models/rails_templatable/application_record.rb
|
|
51
|
+
- app/models/rails_templatable/template.rb
|
|
52
|
+
- app/models/rails_templatable/template_assignment.rb
|
|
53
|
+
- app/views/layouts/rails_templatable/application.html.erb
|
|
54
|
+
- config/routes.rb
|
|
55
|
+
- db/migrate/20250207000000_create_rails_templatable_tables.rb
|
|
56
|
+
- lib/rails_templatable.rb
|
|
57
|
+
- lib/rails_templatable/engine.rb
|
|
58
|
+
- lib/rails_templatable/version.rb
|
|
59
|
+
- lib/tasks/rails_templatable_tasks.rake
|
|
60
|
+
- llms.txt
|
|
61
|
+
homepage: https://github.com/afeiship/rails_templatable
|
|
62
|
+
licenses:
|
|
63
|
+
- MIT
|
|
64
|
+
metadata:
|
|
65
|
+
homepage_uri: https://github.com/afeiship/rails_templatable
|
|
66
|
+
changelog_uri: https://github.com/afeiship/rails_templatable/blob/main/CHANGELOG.md
|
|
67
|
+
documentation_uri: https://github.com/afeiship/rails_templatable/blob/main/README.md
|
|
68
|
+
ai_assistant_uri: https://github.com/afeiship/rails_templatable/blob/main/llms.txt
|
|
69
|
+
post_install_message:
|
|
70
|
+
rdoc_options: []
|
|
71
|
+
require_paths:
|
|
72
|
+
- lib
|
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: 3.0.0
|
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
requirements: []
|
|
84
|
+
rubygems_version: 3.5.22
|
|
85
|
+
signing_key:
|
|
86
|
+
specification_version: 4
|
|
87
|
+
summary: A Rails engine for templatable models.
|
|
88
|
+
test_files: []
|