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 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,6 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ require "bundler/gem_tasks"
@@ -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,4 @@
1
+ module RailsTemplatable
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module RailsTemplatable
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module RailsTemplatable
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module RailsTemplatable
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -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,5 @@
1
+ module RailsTemplatable
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ 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,2 @@
1
+ RailsTemplatable::Engine.routes.draw do
2
+ end
@@ -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
@@ -0,0 +1,5 @@
1
+ module RailsTemplatable
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace RailsTemplatable
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module RailsTemplatable
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,6 @@
1
+ require "rails_templatable/version"
2
+ require "rails_templatable/engine"
3
+
4
+ module RailsTemplatable
5
+ # Your code goes here...
6
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :rails_templatable do
3
+ # # Task goes here
4
+ # 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: []