email_digest 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.vscode/settings.json +3 -0
- data/ANSWERS_TO_YOUR_QUESTIONS.md +361 -0
- data/CHANGELOG.md +14 -0
- data/COMPLETE_SETUP_INSTRUCTIONS.md +298 -0
- data/CUSTOMIZATION_GUIDE.md +264 -0
- data/FAQ.md +133 -0
- data/LICENSE.txt +21 -0
- data/QUICK_START.md +62 -0
- data/README.md +253 -0
- data/REPOSITORY_READY.md +146 -0
- data/RUBYGEMS_PUBLISHING.md +272 -0
- data/SETUP_SEPARATE_GEM_REPO.md +131 -0
- data/email_digest.gemspec +52 -0
- data/lib/email_digest/concerns/digestable.rb +57 -0
- data/lib/email_digest/configuration.rb +40 -0
- data/lib/email_digest/generators/email_digest/install/install_generator.rb +50 -0
- data/lib/email_digest/generators/email_digest/install/templates/README +16 -0
- data/lib/email_digest/generators/email_digest/install/templates/create_email_digest_tables.rb +77 -0
- data/lib/email_digest/generators/email_digest/install/templates/email_digest.rb +25 -0
- data/lib/email_digest/models/digest_item.rb +49 -0
- data/lib/email_digest/models/digest_preference.rb +106 -0
- data/lib/email_digest/railtie.rb +9 -0
- data/lib/email_digest/services/digest_collector.rb +38 -0
- data/lib/email_digest/services/digest_scheduler.rb +55 -0
- data/lib/email_digest/services/digest_summarizer.rb +135 -0
- data/lib/email_digest/version.rb +5 -0
- data/lib/email_digest/workers/digest_processor_worker.rb +64 -0
- data/lib/email_digest/workers/digest_scheduler_worker.rb +26 -0
- data/lib/email_digest/workers/digest_sender_worker.rb +48 -0
- data/lib/email_digest.rb +37 -0
- metadata +241 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e5ba6e19b8f7962fc7f96d1623e403bbff8b9e60957a209c50fc873eb4f391ca
|
|
4
|
+
data.tar.gz: e5a6f65646956c66671bdba5c454e250806a520d62912bf21c1bcf38309aeff3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 55f42707c03303f309ed0850f0417237e155a5ce74eb7729775cc4c88bb289c3003d730e36515cdc1195cc9ca238f9c13a64f85f3d9ab1e8565c88959833eb17
|
|
7
|
+
data.tar.gz: c5316893ee2b57debaf88311305a095b769006872c1981f979187d9663935565db056b221af9a32f3d6dbafda58bc0ff2112bb30ee120c220262f37d1b55ac6a
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
# Answers to Your Questions
|
|
2
|
+
|
|
3
|
+
## Q1: Will it trigger if only 1 digest_item is present?
|
|
4
|
+
|
|
5
|
+
**✅ YES!** The system triggers if there are **any items** (1 or more).
|
|
6
|
+
|
|
7
|
+
Looking at `digest_scheduler.rb` line 39:
|
|
8
|
+
```ruby
|
|
9
|
+
if items.any? # This means 1 or more items will trigger
|
|
10
|
+
Workers::DigestProcessorWorker.perform_async(...)
|
|
11
|
+
end
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
**Answer:** Even a single digest item will trigger processing and email sending.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Q2: What context is being sent to users in mail? Can I customize it per digest_type?
|
|
19
|
+
|
|
20
|
+
**Current Context:**
|
|
21
|
+
The `DigestSummarizer` creates a summary hash with:
|
|
22
|
+
- `subject` - Email subject line
|
|
23
|
+
- `summary` - Summary text
|
|
24
|
+
- `items` - Array of all items
|
|
25
|
+
- `grouped_items` - Items grouped by notification_type
|
|
26
|
+
- `total_count` - Total number of items
|
|
27
|
+
|
|
28
|
+
Each item includes:
|
|
29
|
+
- `id`, `notification_type`, `subject`, `body`
|
|
30
|
+
- `metadata` - JSONB field (can store anything)
|
|
31
|
+
- `source_id`, `source_type`, `priority`, `created_at`
|
|
32
|
+
|
|
33
|
+
**✅ YES, you can customize it per digest_type at your code level!**
|
|
34
|
+
|
|
35
|
+
**Solution:** Create custom summarizer classes in your application:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
# app/services/email_digest/summarizers/time_log_summarizer.rb
|
|
39
|
+
module EmailDigest
|
|
40
|
+
module Summarizers
|
|
41
|
+
class TimeLogSummarizer < EmailDigest::Services::DigestSummarizer
|
|
42
|
+
# Override methods to customize the summary
|
|
43
|
+
def single_item_summary
|
|
44
|
+
item = items.first
|
|
45
|
+
item_hash = enrich_item_hash(item_to_hash(item), item)
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
subject: "Custom Subject: #{item.subject}",
|
|
49
|
+
summary: "Custom summary text",
|
|
50
|
+
items: [item_hash],
|
|
51
|
+
grouped_items: { item.notification_type => [item_hash] },
|
|
52
|
+
total_count: 1,
|
|
53
|
+
# Add your custom fields here
|
|
54
|
+
custom_context: {
|
|
55
|
+
course: item_hash[:course],
|
|
56
|
+
section: item_hash[:section]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def enrich_item_hash(item_hash, item)
|
|
62
|
+
metadata = item.metadata || {}
|
|
63
|
+
item_hash.merge(
|
|
64
|
+
course: load_course(metadata['course_id']),
|
|
65
|
+
section: load_section(metadata['section_id']),
|
|
66
|
+
group_template: load_group_template(metadata['group_template_id']),
|
|
67
|
+
group: load_group(metadata['group_id'])
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def load_course(course_id)
|
|
74
|
+
return nil unless course_id
|
|
75
|
+
Course.find(course_id) rescue nil
|
|
76
|
+
end
|
|
77
|
+
# ... other load methods
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Then register it:
|
|
84
|
+
```ruby
|
|
85
|
+
# config/initializers/email_digest.rb
|
|
86
|
+
EmailDigest.configure do |config|
|
|
87
|
+
config.register_digest_type(:time_log_notifications, {
|
|
88
|
+
mailer_method: 'time_log_digest_email',
|
|
89
|
+
summarizer: 'EmailDigest::Summarizers::TimeLogSummarizer' # Your custom class
|
|
90
|
+
})
|
|
91
|
+
end
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Q3: How are different digest_types, users, and organizations combined? What runs at 15 mins?
|
|
97
|
+
|
|
98
|
+
**How it works:**
|
|
99
|
+
|
|
100
|
+
1. **Every 15 minutes**, `DigestSchedulerWorker` runs (configured in `sidekiq_cron_schedule.yml`)
|
|
101
|
+
|
|
102
|
+
2. It calls `DigestScheduler.process_all_ready(organization)` which:
|
|
103
|
+
- Finds all `DigestPreference` records where:
|
|
104
|
+
- `next_send_at <= now`
|
|
105
|
+
- `enabled = true`
|
|
106
|
+
- Optionally filtered by `organization_id`
|
|
107
|
+
|
|
108
|
+
3. **Each preference is unique per `(user_id, digest_type, organization_id)`**
|
|
109
|
+
|
|
110
|
+
4. For each preference:
|
|
111
|
+
- Uses `DigestCollector` to gather pending items for that specific user+digest_type+organization
|
|
112
|
+
- If items exist (1+), processes them together
|
|
113
|
+
- Sends **one email per preference**
|
|
114
|
+
|
|
115
|
+
**Important:** Items are **NOT combined** across:
|
|
116
|
+
- Different digest_types
|
|
117
|
+
- Different users
|
|
118
|
+
- Different organizations
|
|
119
|
+
|
|
120
|
+
**Example:**
|
|
121
|
+
- User A with `time_log_notifications` → Gets one email
|
|
122
|
+
- User A with `mentor_supervisor_notifications` → Gets a separate email
|
|
123
|
+
- User B with `time_log_notifications` → Gets a separate email
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Q4: How to store additional information (course, section, groupTemplate, group) in digest_item?
|
|
128
|
+
|
|
129
|
+
**✅ Use the `metadata` field (JSONB)!**
|
|
130
|
+
|
|
131
|
+
The `metadata` field can store any additional context you need:
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
# When creating digest items
|
|
135
|
+
EmailDigest::Models::DigestItem.create_for_user(
|
|
136
|
+
user,
|
|
137
|
+
:time_log_notifications,
|
|
138
|
+
:submission_received,
|
|
139
|
+
subject: "New Time Log Submission",
|
|
140
|
+
body: "A new time log has been submitted.",
|
|
141
|
+
metadata: {
|
|
142
|
+
course_id: course.id.to_s,
|
|
143
|
+
course_name: course.name,
|
|
144
|
+
section_id: section.id.to_s,
|
|
145
|
+
section_name: section.name,
|
|
146
|
+
group_template_id: group_template.id.to_s,
|
|
147
|
+
group_template_name: group_template.name,
|
|
148
|
+
group_id: group.id.to_s,
|
|
149
|
+
group_name: group.name,
|
|
150
|
+
student_name: student.name,
|
|
151
|
+
# ... any other data you need
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Then in your custom summarizer**, access and enrich this data:
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
def enrich_item_hash(item_hash, item)
|
|
160
|
+
metadata = item.metadata || {}
|
|
161
|
+
item_hash.merge(
|
|
162
|
+
course: {
|
|
163
|
+
id: metadata['course_id'],
|
|
164
|
+
name: metadata['course_name']
|
|
165
|
+
},
|
|
166
|
+
section: {
|
|
167
|
+
id: metadata['section_id'],
|
|
168
|
+
name: metadata['section_name']
|
|
169
|
+
},
|
|
170
|
+
group_template: {
|
|
171
|
+
id: metadata['group_template_id'],
|
|
172
|
+
name: metadata['group_template_name']
|
|
173
|
+
},
|
|
174
|
+
group: {
|
|
175
|
+
id: metadata['group_id'],
|
|
176
|
+
name: metadata['group_name']
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Or load full objects:**
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
def enrich_item_hash(item_hash, item)
|
|
186
|
+
metadata = item.metadata || {}
|
|
187
|
+
item_hash.merge(
|
|
188
|
+
course: load_course(metadata['course_id']),
|
|
189
|
+
section: load_section(metadata['section_id']),
|
|
190
|
+
group_template: load_group_template(metadata['group_template_id']),
|
|
191
|
+
group: load_group(metadata['group_id'])
|
|
192
|
+
)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
private
|
|
196
|
+
|
|
197
|
+
def load_course(course_id)
|
|
198
|
+
return nil unless course_id
|
|
199
|
+
Course.find(course_id) rescue nil
|
|
200
|
+
end
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Complete Example: Time Log Digest with Course/Section Context
|
|
206
|
+
|
|
207
|
+
### 1. Create Digest Item with Metadata
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
# In your application code
|
|
211
|
+
time_log.add_to_digest(
|
|
212
|
+
instructor,
|
|
213
|
+
:submission_received,
|
|
214
|
+
subject: "New Time Log Submission",
|
|
215
|
+
body: "A new time log has been submitted for review.",
|
|
216
|
+
priority: 5,
|
|
217
|
+
metadata: {
|
|
218
|
+
course_id: time_log.course.id.to_s,
|
|
219
|
+
course_name: time_log.course.name,
|
|
220
|
+
section_id: time_log.section.id.to_s,
|
|
221
|
+
section_name: time_log.section.name,
|
|
222
|
+
group_template_id: time_log.group_template&.id&.to_s,
|
|
223
|
+
group_id: time_log.group.id.to_s,
|
|
224
|
+
group_name: time_log.group.name,
|
|
225
|
+
student_id: time_log.student.id.to_s,
|
|
226
|
+
student_name: time_log.student.name
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### 2. Create Custom Summarizer
|
|
232
|
+
|
|
233
|
+
```ruby
|
|
234
|
+
# app/services/email_digest/summarizers/time_log_summarizer.rb
|
|
235
|
+
module EmailDigest
|
|
236
|
+
module Summarizers
|
|
237
|
+
class TimeLogSummarizer < EmailDigest::Services::DigestSummarizer
|
|
238
|
+
def enrich_item_hash(item_hash, item)
|
|
239
|
+
metadata = item.metadata || {}
|
|
240
|
+
|
|
241
|
+
# Load full objects from metadata
|
|
242
|
+
item_hash.merge(
|
|
243
|
+
course: load_course(metadata['course_id']),
|
|
244
|
+
section: load_section(metadata['section_id']),
|
|
245
|
+
group_template: load_group_template(metadata['group_template_id']),
|
|
246
|
+
group: load_group(metadata['group_id']),
|
|
247
|
+
student: load_student(metadata['student_id']),
|
|
248
|
+
# Include metadata for easy access
|
|
249
|
+
course_name: metadata['course_name'],
|
|
250
|
+
section_name: metadata['section_name'],
|
|
251
|
+
group_name: metadata['group_name'],
|
|
252
|
+
student_name: metadata['student_name']
|
|
253
|
+
)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def grouped_summary
|
|
257
|
+
grouped = items.group_by do |item|
|
|
258
|
+
# Group by course and section
|
|
259
|
+
metadata = item.metadata || {}
|
|
260
|
+
"#{metadata['course_name']} - #{metadata['section_name']}"
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
items_hash = items.map { |item| enrich_item_hash(item_to_hash(item), item) }
|
|
264
|
+
grouped_hash = grouped.transform_values do |items|
|
|
265
|
+
items.map { |item| enrich_item_hash(item_to_hash(item), item) }
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
{
|
|
269
|
+
subject: "Time Log Notifications - #{items.size} New Submissions",
|
|
270
|
+
summary: "You have #{items.size} new time log submissions across #{grouped.size} courses.",
|
|
271
|
+
items: items_hash,
|
|
272
|
+
grouped_items: grouped_hash,
|
|
273
|
+
total_count: items.size,
|
|
274
|
+
grouped: true
|
|
275
|
+
}
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
private
|
|
279
|
+
|
|
280
|
+
def load_course(course_id)
|
|
281
|
+
return nil unless course_id
|
|
282
|
+
Course.find(course_id) rescue nil
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def load_section(section_id)
|
|
286
|
+
return nil unless section_id
|
|
287
|
+
Section.find(section_id) rescue nil
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def load_group_template(group_template_id)
|
|
291
|
+
return nil unless group_template_id
|
|
292
|
+
GroupTemplate.find(group_template_id) rescue nil
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def load_group(group_id)
|
|
296
|
+
return nil unless group_id
|
|
297
|
+
Group.find(group_id) rescue nil
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def load_student(student_id)
|
|
301
|
+
return nil unless student_id
|
|
302
|
+
Student.find(student_id) rescue nil
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### 3. Register Custom Summarizer
|
|
310
|
+
|
|
311
|
+
```ruby
|
|
312
|
+
# config/initializers/email_digest.rb
|
|
313
|
+
EmailDigest.configure do |config|
|
|
314
|
+
config.register_digest_type(:time_log_notifications, {
|
|
315
|
+
mailer_method: 'time_log_digest_email',
|
|
316
|
+
summarizer: 'EmailDigest::Summarizers::TimeLogSummarizer'
|
|
317
|
+
})
|
|
318
|
+
end
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### 4. Use in Mailer Template
|
|
322
|
+
|
|
323
|
+
```erb
|
|
324
|
+
<!-- app/views/user_mailer/time_log_digest_email.html.erb -->
|
|
325
|
+
<% @summary[:grouped_items].each do |group_key, items| %>
|
|
326
|
+
<h2><%= group_key %></h2>
|
|
327
|
+
<% items.each do |item| %>
|
|
328
|
+
<div>
|
|
329
|
+
<h3><%= item[:subject] %></h3>
|
|
330
|
+
<p><%= item[:body] %></p>
|
|
331
|
+
|
|
332
|
+
<% if item[:course_name] %>
|
|
333
|
+
<p><strong>Course:</strong> <%= item[:course_name] %></p>
|
|
334
|
+
<% end %>
|
|
335
|
+
|
|
336
|
+
<% if item[:section_name] %>
|
|
337
|
+
<p><strong>Section:</strong> <%= item[:section_name] %></p>
|
|
338
|
+
<% end %>
|
|
339
|
+
|
|
340
|
+
<% if item[:group_name] %>
|
|
341
|
+
<p><strong>Group:</strong> <%= item[:group_name] %></p>
|
|
342
|
+
<% end %>
|
|
343
|
+
|
|
344
|
+
<% if item[:student_name] %>
|
|
345
|
+
<p><strong>Student:</strong> <%= item[:student_name] %></p>
|
|
346
|
+
<% end %>
|
|
347
|
+
</div>
|
|
348
|
+
<% end %>
|
|
349
|
+
<% end %>
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## Summary
|
|
355
|
+
|
|
356
|
+
✅ **Q1:** Yes, triggers with 1+ items
|
|
357
|
+
✅ **Q2:** Customizable per digest_type via custom summarizers in your app
|
|
358
|
+
✅ **Q3:** Each email is for one user + one digest_type + one organization, processed every 15 mins
|
|
359
|
+
✅ **Q4:** Store additional info in `metadata` JSONB field, enrich in custom summarizer
|
|
360
|
+
|
|
361
|
+
See `CUSTOMIZATION_GUIDE.md` for more detailed examples!
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [1.0.0] - 2025-01-27
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Initial release
|
|
9
|
+
- PostgreSQL backend
|
|
10
|
+
- Sidekiq integration
|
|
11
|
+
- Multi-tenant support
|
|
12
|
+
- Flexible scheduling
|
|
13
|
+
- Priority-based processing
|
|
14
|
+
- Smart summarization
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# Complete Setup Instructions for EmailDigest Gem
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This guide will help you create a completely separate gem repository that you can:
|
|
6
|
+
1. Use in your application via Gemfile
|
|
7
|
+
2. Publish to RubyGems for others to use
|
|
8
|
+
3. Maintain independently from your main application
|
|
9
|
+
|
|
10
|
+
## Step-by-Step Instructions
|
|
11
|
+
|
|
12
|
+
### Step 1: Create New Repository Directory
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# Navigate to a location outside your main app
|
|
16
|
+
cd ~/workspace # or ~/projects, or wherever you prefer
|
|
17
|
+
|
|
18
|
+
# Create new directory
|
|
19
|
+
mkdir email_digest
|
|
20
|
+
cd email_digest
|
|
21
|
+
|
|
22
|
+
# Initialize git
|
|
23
|
+
git init
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Step 2: Copy All Gem Files
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Copy everything from gem_package
|
|
30
|
+
cp -r /Users/rosharma/workspace/sll-la/eportfolio/gem_package/* ~/workspace/email_digest/
|
|
31
|
+
|
|
32
|
+
# Verify files are copied
|
|
33
|
+
ls -la
|
|
34
|
+
# You should see: email_digest.gemspec, lib/, README.md, etc.
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Step 3: Update Gemspec Metadata
|
|
38
|
+
|
|
39
|
+
Edit `email_digest.gemspec`:
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
spec.authors = ['Your Name'] # Change this
|
|
43
|
+
spec.email = ['your.email@example.com'] # Change this
|
|
44
|
+
spec.homepage = 'https://github.com/yourusername/email_digest' # Change this
|
|
45
|
+
spec.metadata['source_code_uri'] = 'https://github.com/yourusername/email_digest' # Change this
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Step 4: Create GitHub Repository
|
|
49
|
+
|
|
50
|
+
#### Option A: Via GitHub Web Interface
|
|
51
|
+
|
|
52
|
+
1. Go to https://github.com/new
|
|
53
|
+
2. Repository name: `email_digest`
|
|
54
|
+
3. Description: "A flexible email digest system for Rails"
|
|
55
|
+
4. Choose Public or Private
|
|
56
|
+
5. Don't initialize with README (you already have one)
|
|
57
|
+
6. Click "Create repository"
|
|
58
|
+
|
|
59
|
+
#### Option B: Via GitHub CLI
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
gh repo create email_digest --public --description "A flexible email digest system for Rails"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Step 5: Connect and Push to GitHub
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Add remote
|
|
69
|
+
git remote add origin https://github.com/yourusername/email_digest.git
|
|
70
|
+
|
|
71
|
+
# Add all files
|
|
72
|
+
git add .
|
|
73
|
+
|
|
74
|
+
# Initial commit
|
|
75
|
+
git commit -m "Initial commit: EmailDigest gem v1.0.0"
|
|
76
|
+
|
|
77
|
+
# Push to GitHub
|
|
78
|
+
git branch -M main
|
|
79
|
+
git push -u origin main
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Step 6: Test the Gem Locally
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Build the gem
|
|
86
|
+
gem build email_digest.gemspec
|
|
87
|
+
|
|
88
|
+
# This creates: email_digest-1.0.0.gem
|
|
89
|
+
|
|
90
|
+
# Test installation
|
|
91
|
+
gem install ./email_digest-1.0.0.gem
|
|
92
|
+
|
|
93
|
+
# Or test in your app
|
|
94
|
+
# In your app's Gemfile, add:
|
|
95
|
+
# gem 'email_digest', path: '../email_digest'
|
|
96
|
+
# Then: bundle install
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Step 7: Publish to RubyGems
|
|
100
|
+
|
|
101
|
+
#### 7a. Create RubyGems Account
|
|
102
|
+
|
|
103
|
+
1. Go to https://rubygems.org
|
|
104
|
+
2. Click "Sign up"
|
|
105
|
+
3. Fill in your details
|
|
106
|
+
4. Verify your email
|
|
107
|
+
|
|
108
|
+
#### 7b. Get API Key
|
|
109
|
+
|
|
110
|
+
1. Go to https://rubygems.org/profile/edit
|
|
111
|
+
2. Scroll to "API Access"
|
|
112
|
+
3. Click "Add a new API key"
|
|
113
|
+
4. Give it a name (e.g., "laptop")
|
|
114
|
+
5. Copy the key (you'll use it once)
|
|
115
|
+
|
|
116
|
+
#### 7c. Configure Credentials
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Option 1: Use gem signin
|
|
120
|
+
gem signin
|
|
121
|
+
# Enter your RubyGems username and password
|
|
122
|
+
|
|
123
|
+
# Option 2: Manual setup
|
|
124
|
+
# Create ~/.gem/credentials with:
|
|
125
|
+
# ---
|
|
126
|
+
# :rubygems_api_key: YOUR_API_KEY_HERE
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
#### 7d. Publish
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Make sure you've built the gem
|
|
133
|
+
gem build email_digest.gemspec
|
|
134
|
+
|
|
135
|
+
# Push to RubyGems
|
|
136
|
+
gem push email_digest-1.0.0.gem
|
|
137
|
+
|
|
138
|
+
# You should see:
|
|
139
|
+
# Pushing gem to https://rubygems.org...
|
|
140
|
+
# Successfully registered gem: email_digest
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### 7e. Verify
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Check on RubyGems
|
|
147
|
+
open https://rubygems.org/gems/email_digest
|
|
148
|
+
|
|
149
|
+
# Or via command line
|
|
150
|
+
gem search email_digest
|
|
151
|
+
gem info email_digest
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Step 8: Use in Your Application
|
|
155
|
+
|
|
156
|
+
In your main app's `Gemfile`:
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
# From RubyGems (recommended)
|
|
160
|
+
gem 'email_digest'
|
|
161
|
+
|
|
162
|
+
# Or from GitHub (for development)
|
|
163
|
+
gem 'email_digest', git: 'https://github.com/yourusername/email_digest.git'
|
|
164
|
+
|
|
165
|
+
# Or local path (for local development)
|
|
166
|
+
gem 'email_digest', path: '../email_digest'
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Then in your app:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
bundle install
|
|
173
|
+
rails generate email_digest:install
|
|
174
|
+
rails db:migrate
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## File Structure
|
|
178
|
+
|
|
179
|
+
Your gem repository should have:
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
email_digest/
|
|
183
|
+
├── .gitignore
|
|
184
|
+
├── .github/
|
|
185
|
+
│ └── workflows/
|
|
186
|
+
│ └── ci.yml
|
|
187
|
+
├── email_digest.gemspec
|
|
188
|
+
├── README.md
|
|
189
|
+
├── LICENSE.txt
|
|
190
|
+
├── CHANGELOG.md
|
|
191
|
+
├── RUBYGEMS_PUBLISHING.md
|
|
192
|
+
└── lib/
|
|
193
|
+
├── email_digest.rb
|
|
194
|
+
└── email_digest/
|
|
195
|
+
├── version.rb
|
|
196
|
+
├── configuration.rb
|
|
197
|
+
├── railtie.rb
|
|
198
|
+
├── models/
|
|
199
|
+
│ ├── digest_item.rb
|
|
200
|
+
│ └── digest_preference.rb
|
|
201
|
+
├── services/
|
|
202
|
+
│ ├── digest_collector.rb
|
|
203
|
+
│ ├── digest_summarizer.rb
|
|
204
|
+
│ └── digest_scheduler.rb
|
|
205
|
+
├── workers/
|
|
206
|
+
│ ├── digest_processor_worker.rb
|
|
207
|
+
│ ├── digest_sender_worker.rb
|
|
208
|
+
│ └── digest_scheduler_worker.rb
|
|
209
|
+
├── concerns/
|
|
210
|
+
│ └── digestable.rb
|
|
211
|
+
└── generators/
|
|
212
|
+
└── email_digest/
|
|
213
|
+
└── install/
|
|
214
|
+
├── install_generator.rb
|
|
215
|
+
└── templates/
|
|
216
|
+
├── email_digest.rb
|
|
217
|
+
├── create_email_digest_tables.rb
|
|
218
|
+
└── README
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Updating the Gem
|
|
222
|
+
|
|
223
|
+
When you make changes:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
# 1. Update version in lib/email_digest/version.rb
|
|
227
|
+
# 2. Update CHANGELOG.md
|
|
228
|
+
# 3. Commit changes
|
|
229
|
+
git add .
|
|
230
|
+
git commit -m "Update: [description]"
|
|
231
|
+
git push
|
|
232
|
+
|
|
233
|
+
# 4. Tag the release
|
|
234
|
+
git tag -a v1.0.1 -m "Version 1.0.1"
|
|
235
|
+
git push origin v1.0.1
|
|
236
|
+
|
|
237
|
+
# 5. Build and publish
|
|
238
|
+
gem build email_digest.gemspec
|
|
239
|
+
gem push email_digest-1.0.1.gem
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Quick Reference
|
|
243
|
+
|
|
244
|
+
### Local Development
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
# In your app's Gemfile
|
|
248
|
+
gem 'email_digest', path: '../email_digest'
|
|
249
|
+
bundle install
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### From GitHub
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
# In your app's Gemfile
|
|
256
|
+
gem 'email_digest', git: 'https://github.com/yourusername/email_digest.git'
|
|
257
|
+
bundle install
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### From RubyGems
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
# In your app's Gemfile
|
|
264
|
+
gem 'email_digest'
|
|
265
|
+
bundle install
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Troubleshooting
|
|
269
|
+
|
|
270
|
+
### "Could not find gem"
|
|
271
|
+
|
|
272
|
+
- Make sure the path is correct
|
|
273
|
+
- Run `bundle install` again
|
|
274
|
+
- Check `bundle show email_digest` to verify location
|
|
275
|
+
|
|
276
|
+
### "Generator not found"
|
|
277
|
+
|
|
278
|
+
- Make sure you ran `bundle install`
|
|
279
|
+
- Check that the gem is in your Gemfile
|
|
280
|
+
- Verify the gem path is correct
|
|
281
|
+
|
|
282
|
+
### "Table already exists"
|
|
283
|
+
|
|
284
|
+
- You might have run the migration manually
|
|
285
|
+
- Check your database for `email_digest_items` and `email_digest_preferences` tables
|
|
286
|
+
|
|
287
|
+
## Next Steps
|
|
288
|
+
|
|
289
|
+
1. ✅ Create separate repo (done via this guide)
|
|
290
|
+
2. ✅ Publish to RubyGems (follow Step 7)
|
|
291
|
+
3. ✅ Use in your app (follow Step 8)
|
|
292
|
+
4. ✅ Share with others (they can use `gem 'email_digest'`)
|
|
293
|
+
|
|
294
|
+
## Support
|
|
295
|
+
|
|
296
|
+
- See `RUBYGEMS_PUBLISHING.md` for detailed publishing guide
|
|
297
|
+
- See `README.md` for usage documentation
|
|
298
|
+
- Check GitHub Issues for known problems
|