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
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# EmailDigest Customization Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how to customize the email digest system for your specific needs.
|
|
4
|
+
|
|
5
|
+
## Answers to Common Questions
|
|
6
|
+
|
|
7
|
+
### 1. Will it trigger if only 1 digest_item is present?
|
|
8
|
+
|
|
9
|
+
**Yes!** The system triggers if there are **any items** (1 or more). Looking at `digest_scheduler.rb`:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
if items.any? # This means 1 or more items will trigger
|
|
13
|
+
Workers::DigestProcessorWorker.perform_async(...)
|
|
14
|
+
end
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
So if you have even 1 digest item, it will be processed and sent.
|
|
18
|
+
|
|
19
|
+
### 2. How is the email context generated?
|
|
20
|
+
|
|
21
|
+
The email context comes from the `DigestSummarizer` which creates a summary hash. You can customize this per `digest_type` by creating custom summarizer classes in your application.
|
|
22
|
+
|
|
23
|
+
### 3. How are digest_types, users, and organizations combined?
|
|
24
|
+
|
|
25
|
+
The scheduler runs every 15 minutes and:
|
|
26
|
+
1. Finds all `DigestPreference` records where `next_send_at <= now` and `enabled = true`
|
|
27
|
+
2. Each preference is unique per `(user_id, digest_type, organization_id)`
|
|
28
|
+
3. For each preference, it collects pending items for that specific user+digest_type+organization
|
|
29
|
+
4. Processes and sends them together
|
|
30
|
+
|
|
31
|
+
So each email contains items for:
|
|
32
|
+
- **One user**
|
|
33
|
+
- **One digest_type** (e.g., `time_log_notifications`)
|
|
34
|
+
- **One organization**
|
|
35
|
+
|
|
36
|
+
### 4. How to store additional information (course, section, groupTemplate, group)?
|
|
37
|
+
|
|
38
|
+
Use the `metadata` field (JSONB) in `DigestItem`. It can store any additional context you need.
|
|
39
|
+
|
|
40
|
+
## Customization Examples
|
|
41
|
+
|
|
42
|
+
### Example 1: Custom Summarizer with Course/Section Context
|
|
43
|
+
|
|
44
|
+
Create a custom summarizer in your app:
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
# app/services/email_digest/summarizers/time_log_summarizer.rb
|
|
48
|
+
module EmailDigest
|
|
49
|
+
module Summarizers
|
|
50
|
+
class TimeLogSummarizer < EmailDigest::Services::DigestSummarizer
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def enrich_item_hash(item_hash, item)
|
|
54
|
+
metadata = item.metadata || {}
|
|
55
|
+
|
|
56
|
+
# Load additional context from metadata
|
|
57
|
+
item_hash.merge(
|
|
58
|
+
course: load_course(metadata['course_id']),
|
|
59
|
+
section: load_section(metadata['section_id']),
|
|
60
|
+
group_template: load_group_template(metadata['group_template_id']),
|
|
61
|
+
group: load_group(metadata['group_id']),
|
|
62
|
+
time_log: load_time_log(item.source_id) if item.source_id
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def load_course(course_id)
|
|
67
|
+
return nil unless course_id
|
|
68
|
+
# Your logic to load course
|
|
69
|
+
Course.find(course_id) rescue nil
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def load_section(section_id)
|
|
73
|
+
return nil unless section_id
|
|
74
|
+
# Your logic to load section
|
|
75
|
+
Section.find(section_id) rescue nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def load_group_template(group_template_id)
|
|
79
|
+
return nil unless group_template_id
|
|
80
|
+
# Your logic to load group template
|
|
81
|
+
GroupTemplate.find(group_template_id) rescue nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def load_group(group_id)
|
|
85
|
+
return nil unless group_id
|
|
86
|
+
# Your logic to load group
|
|
87
|
+
Group.find(group_id) rescue nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def load_time_log(time_log_id)
|
|
91
|
+
return nil unless time_log_id
|
|
92
|
+
TimeLog.find(time_log_id) rescue nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Override summary generation if needed
|
|
96
|
+
def single_item_summary
|
|
97
|
+
item = items.first
|
|
98
|
+
item_hash = enrich_item_hash(item_to_hash(item), item)
|
|
99
|
+
|
|
100
|
+
# Custom subject with course/section info
|
|
101
|
+
subject = if item_hash[:course] && item_hash[:section]
|
|
102
|
+
"#{item.subject} - #{item_hash[:course].name} (#{item_hash[:section].name})"
|
|
103
|
+
else
|
|
104
|
+
item.subject || "New #{item.notification_type.humanize}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
{
|
|
108
|
+
subject: subject,
|
|
109
|
+
summary: item.body,
|
|
110
|
+
items: [item_hash],
|
|
111
|
+
grouped_items: { item.notification_type => [item_hash] },
|
|
112
|
+
total_count: 1
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Example 2: Register Custom Summarizer
|
|
121
|
+
|
|
122
|
+
In your `config/initializers/email_digest.rb`:
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
EmailDigest.configure do |config|
|
|
126
|
+
# ... other config ...
|
|
127
|
+
|
|
128
|
+
config.register_digest_type(:time_log_notifications, {
|
|
129
|
+
mailer_method: 'time_log_digest_email',
|
|
130
|
+
summarizer: 'EmailDigest::Summarizers::TimeLogSummarizer' # Custom summarizer
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
config.register_digest_type(:mentor_supervisor_notifications, {
|
|
134
|
+
mailer_method: 'mentor_supervisor_digest_email',
|
|
135
|
+
summarizer: 'EmailDigest::Summarizers::MentorSupervisorSummarizer' # Another custom summarizer
|
|
136
|
+
})
|
|
137
|
+
end
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Example 3: Storing Additional Context in DigestItem
|
|
141
|
+
|
|
142
|
+
When creating digest items, include additional context in metadata:
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
# In your application code
|
|
146
|
+
EmailDigest::Models::DigestItem.create_for_user(
|
|
147
|
+
user,
|
|
148
|
+
:time_log_notifications,
|
|
149
|
+
:submission_received,
|
|
150
|
+
subject: "New Time Log Submission",
|
|
151
|
+
body: "A new time log has been submitted for review.",
|
|
152
|
+
priority: 5,
|
|
153
|
+
source_id: time_log.id.to_s,
|
|
154
|
+
source_type: 'TimeLog',
|
|
155
|
+
metadata: {
|
|
156
|
+
course_id: time_log.course.id.to_s,
|
|
157
|
+
course_name: time_log.course.name,
|
|
158
|
+
section_id: time_log.section.id.to_s,
|
|
159
|
+
section_name: time_log.section.name,
|
|
160
|
+
group_template_id: time_log.group_template&.id&.to_s,
|
|
161
|
+
group_template_name: time_log.group_template&.name,
|
|
162
|
+
group_id: time_log.group.id.to_s,
|
|
163
|
+
group_name: time_log.group.name,
|
|
164
|
+
student_name: time_log.student.name,
|
|
165
|
+
# ... any other context you need
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Or using the Digestable concern:
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
class TimeLog < ActiveRecord::Base
|
|
174
|
+
include EmailDigest::Concerns::Digestable
|
|
175
|
+
digestable :time_log_notifications
|
|
176
|
+
|
|
177
|
+
def add_to_digest(user, notification_type, options = {})
|
|
178
|
+
super(user, notification_type, {
|
|
179
|
+
metadata: {
|
|
180
|
+
course_id: course.id.to_s,
|
|
181
|
+
course_name: course.name,
|
|
182
|
+
section_id: section.id.to_s,
|
|
183
|
+
section_name: section.name,
|
|
184
|
+
group_template_id: group_template&.id&.to_s,
|
|
185
|
+
group_id: group.id.to_s,
|
|
186
|
+
group_name: group.name,
|
|
187
|
+
# Merge with any existing metadata
|
|
188
|
+
}.merge(options[:metadata] || {})
|
|
189
|
+
}.merge(options))
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Example 4: Custom Mailer with Rich Context
|
|
195
|
+
|
|
196
|
+
In your mailer, you'll receive the enriched summary:
|
|
197
|
+
|
|
198
|
+
```ruby
|
|
199
|
+
class UserMailer < ActionMailer::Base
|
|
200
|
+
def time_log_digest_email(user, summary, digest_type)
|
|
201
|
+
@user = user
|
|
202
|
+
@summary = summary.with_indifferent_access
|
|
203
|
+
@digest_type = digest_type
|
|
204
|
+
|
|
205
|
+
# Now you have access to enriched items with course/section/group info
|
|
206
|
+
@items = @summary[:items] # Each item has course, section, group_template, group, etc.
|
|
207
|
+
|
|
208
|
+
mail(
|
|
209
|
+
to: @user.email,
|
|
210
|
+
from: 'no-reply@example.com',
|
|
211
|
+
subject: @summary[:subject],
|
|
212
|
+
content_type: 'text/html'
|
|
213
|
+
) do |format|
|
|
214
|
+
format.html { render 'time_log_digest_email' }
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
In your email template:
|
|
221
|
+
|
|
222
|
+
```erb
|
|
223
|
+
<!-- app/views/user_mailer/time_log_digest_email.html.erb -->
|
|
224
|
+
<% @summary[:items].each do |item| %>
|
|
225
|
+
<div>
|
|
226
|
+
<h3><%= item[:subject] %></h3>
|
|
227
|
+
<p><%= item[:body] %></p>
|
|
228
|
+
|
|
229
|
+
<% if item[:course] %>
|
|
230
|
+
<p><strong>Course:</strong> <%= item[:course][:name] %></p>
|
|
231
|
+
<% end %>
|
|
232
|
+
|
|
233
|
+
<% if item[:section] %>
|
|
234
|
+
<p><strong>Section:</strong> <%= item[:section][:name] %></p>
|
|
235
|
+
<% end %>
|
|
236
|
+
|
|
237
|
+
<% if item[:group_template] %>
|
|
238
|
+
<p><strong>Group Template:</strong> <%= item[:group_template][:name] %></p>
|
|
239
|
+
<% end %>
|
|
240
|
+
|
|
241
|
+
<% if item[:group] %>
|
|
242
|
+
<p><strong>Group:</strong> <%= item[:group][:name] %></p>
|
|
243
|
+
<% end %>
|
|
244
|
+
</div>
|
|
245
|
+
<% end %>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## How the 15-Minute Scheduler Works
|
|
249
|
+
|
|
250
|
+
1. **Every 15 minutes**, `DigestSchedulerWorker` runs (configured in `sidekiq_cron_schedule.yml`)
|
|
251
|
+
2. It calls `DigestScheduler.process_all_ready(organization)`
|
|
252
|
+
3. For each `DigestPreference` that's ready (`next_send_at <= now`):
|
|
253
|
+
- Loads the user and organization
|
|
254
|
+
- Uses `DigestCollector` (or custom collector) to gather pending items
|
|
255
|
+
- If items exist (1 or more), enqueues `DigestProcessorWorker`
|
|
256
|
+
- If no items, just updates the schedule
|
|
257
|
+
|
|
258
|
+
## Summary
|
|
259
|
+
|
|
260
|
+
- ✅ **Triggers with 1+ items** - Yes, any items will trigger
|
|
261
|
+
- ✅ **Customizable per digest_type** - Create custom summarizers in your app
|
|
262
|
+
- ✅ **Metadata support** - Store course, section, groupTemplate, group, etc. in metadata
|
|
263
|
+
- ✅ **Rich context in emails** - Custom summarizers can enrich items with additional data
|
|
264
|
+
- ✅ **Application-level control** - All customization happens in your app, not the gem
|
data/FAQ.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# EmailDigest FAQ
|
|
2
|
+
|
|
3
|
+
## Common Questions
|
|
4
|
+
|
|
5
|
+
### Q1: Will it trigger if only 1 digest_item is present?
|
|
6
|
+
|
|
7
|
+
**A: Yes!** The system triggers if there are **any items** (1 or more).
|
|
8
|
+
|
|
9
|
+
Looking at the code in `digest_scheduler.rb`:
|
|
10
|
+
```ruby
|
|
11
|
+
if items.any? # This means 1 or more items will trigger
|
|
12
|
+
Workers::DigestProcessorWorker.perform_async(...)
|
|
13
|
+
end
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
So even a single digest item will be processed and sent.
|
|
17
|
+
|
|
18
|
+
### Q2: What context is being sent to users in mail?
|
|
19
|
+
|
|
20
|
+
**A:** The context comes from the `DigestSummarizer` which creates a summary hash containing:
|
|
21
|
+
- `subject` - Email subject
|
|
22
|
+
- `summary` - Summary text
|
|
23
|
+
- `items` - Array of all items
|
|
24
|
+
- `grouped_items` - Items grouped by notification_type
|
|
25
|
+
- `total_count` - Total number of items
|
|
26
|
+
|
|
27
|
+
Each item in the summary includes:
|
|
28
|
+
- `id`, `notification_type`, `subject`, `body`
|
|
29
|
+
- `metadata` - JSONB field that can contain any additional data
|
|
30
|
+
- `source_id`, `source_type`, `priority`, `created_at`
|
|
31
|
+
|
|
32
|
+
**You can customize this per digest_type** by creating custom summarizer classes in your application (see `CUSTOMIZATION_GUIDE.md`).
|
|
33
|
+
|
|
34
|
+
### Q3: How are different digest_types, users, and organizations combined?
|
|
35
|
+
|
|
36
|
+
**A:** The scheduler runs every 15 minutes and processes them separately:
|
|
37
|
+
|
|
38
|
+
1. Finds all `DigestPreference` records where:
|
|
39
|
+
- `next_send_at <= now`
|
|
40
|
+
- `enabled = true`
|
|
41
|
+
- Optionally filtered by `organization_id`
|
|
42
|
+
|
|
43
|
+
2. Each preference is unique per `(user_id, digest_type, organization_id)`
|
|
44
|
+
|
|
45
|
+
3. For each preference:
|
|
46
|
+
- Collects pending items for that specific user+digest_type+organization
|
|
47
|
+
- Processes them together
|
|
48
|
+
- Sends one email per preference
|
|
49
|
+
|
|
50
|
+
**So each email contains items for:**
|
|
51
|
+
- One user
|
|
52
|
+
- One digest_type (e.g., `time_log_notifications`)
|
|
53
|
+
- One organization
|
|
54
|
+
|
|
55
|
+
Items are NOT combined across different digest_types or users.
|
|
56
|
+
|
|
57
|
+
### Q4: How to store additional information (course, section, groupTemplate, group) in digest_item?
|
|
58
|
+
|
|
59
|
+
**A:** Use the `metadata` field (JSONB) in `DigestItem`. It can store any additional context:
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
EmailDigest::Models::DigestItem.create_for_user(
|
|
63
|
+
user,
|
|
64
|
+
:time_log_notifications,
|
|
65
|
+
:submission_received,
|
|
66
|
+
subject: "New Time Log Submission",
|
|
67
|
+
body: "A new time log has been submitted.",
|
|
68
|
+
metadata: {
|
|
69
|
+
course_id: course.id.to_s,
|
|
70
|
+
course_name: course.name,
|
|
71
|
+
section_id: section.id.to_s,
|
|
72
|
+
section_name: section.name,
|
|
73
|
+
group_template_id: group_template.id.to_s,
|
|
74
|
+
group_id: group.id.to_s,
|
|
75
|
+
group_name: group.name,
|
|
76
|
+
# ... any other data you need
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Then in your custom summarizer, you can access and enrich this data:
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
def enrich_item_hash(item_hash, item)
|
|
85
|
+
metadata = item.metadata || {}
|
|
86
|
+
item_hash.merge(
|
|
87
|
+
course: load_course(metadata['course_id']),
|
|
88
|
+
section: load_section(metadata['section_id']),
|
|
89
|
+
group_template: load_group_template(metadata['group_template_id']),
|
|
90
|
+
group: load_group(metadata['group_id'])
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Q5: How to customize email content per digest_type?
|
|
96
|
+
|
|
97
|
+
**A:** Create custom summarizer classes in your application:
|
|
98
|
+
|
|
99
|
+
1. Create a custom summarizer:
|
|
100
|
+
```ruby
|
|
101
|
+
# app/services/email_digest/summarizers/time_log_summarizer.rb
|
|
102
|
+
module EmailDigest
|
|
103
|
+
module Summarizers
|
|
104
|
+
class TimeLogSummarizer < EmailDigest::Services::DigestSummarizer
|
|
105
|
+
# Override methods to customize behavior
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
2. Register it in your initializer:
|
|
112
|
+
```ruby
|
|
113
|
+
EmailDigest.configure do |config|
|
|
114
|
+
config.register_digest_type(:time_log_notifications, {
|
|
115
|
+
mailer_method: 'time_log_digest_email',
|
|
116
|
+
summarizer: 'EmailDigest::Summarizers::TimeLogSummarizer'
|
|
117
|
+
})
|
|
118
|
+
end
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
See `CUSTOMIZATION_GUIDE.md` for detailed examples.
|
|
122
|
+
|
|
123
|
+
### Q6: What runs at 15-minute intervals?
|
|
124
|
+
|
|
125
|
+
**A:** The `DigestSchedulerWorker` runs every 15 minutes (configured in `sidekiq_cron_schedule.yml`). It:
|
|
126
|
+
|
|
127
|
+
1. Finds all preferences ready to send
|
|
128
|
+
2. For each preference:
|
|
129
|
+
- Collects pending items
|
|
130
|
+
- If items exist, processes them
|
|
131
|
+
- Updates the schedule for next send
|
|
132
|
+
|
|
133
|
+
This ensures digests are sent according to each user's schedule (daily, weekly, etc.).
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 EmailDigest Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/QUICK_START.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Quick Start - EmailDigest Gem Repository
|
|
2
|
+
|
|
3
|
+
## 🚀 Create Your Gem Repository in 5 Minutes
|
|
4
|
+
|
|
5
|
+
### 1. Create Directory & Copy Files
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd ~/workspace
|
|
9
|
+
mkdir email_digest
|
|
10
|
+
cd email_digest
|
|
11
|
+
cp -r /Users/rosharma/workspace/sll-la/eportfolio/gem_package/* .
|
|
12
|
+
git init
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### 2. Update Gemspec
|
|
16
|
+
|
|
17
|
+
Edit `email_digest.gemspec` - update author, email, and GitHub URLs.
|
|
18
|
+
|
|
19
|
+
### 3. Push to GitHub
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
git add .
|
|
23
|
+
git commit -m "Initial commit"
|
|
24
|
+
git remote add origin https://github.com/yourusername/email_digest.git
|
|
25
|
+
git push -u origin main
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 4. Publish to RubyGems
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Create account at https://rubygems.org
|
|
32
|
+
gem signin
|
|
33
|
+
gem build email_digest.gemspec
|
|
34
|
+
gem push email_digest-1.0.0.gem
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 5. Use in Your App
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
# In Gemfile
|
|
41
|
+
gem 'email_digest'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
bundle install
|
|
46
|
+
rails generate email_digest:install
|
|
47
|
+
rails db:migrate
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 📚 Full Documentation
|
|
51
|
+
|
|
52
|
+
- `COMPLETE_SETUP_INSTRUCTIONS.md` - Detailed setup guide
|
|
53
|
+
- `RUBYGEMS_PUBLISHING.md` - Publishing guide
|
|
54
|
+
- `README.md` - Usage documentation
|
|
55
|
+
|
|
56
|
+
## ✅ Done!
|
|
57
|
+
|
|
58
|
+
Your gem is now:
|
|
59
|
+
- ✅ Separate from your main app
|
|
60
|
+
- ✅ Available via Gemfile
|
|
61
|
+
- ✅ Published to RubyGems (after Step 4)
|
|
62
|
+
- ✅ Ready for others to use
|