notey 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a4f1b1236c0e43b64f5624a251fdc64f4ce94cd3e12f000efb4619799d6c7c4
4
- data.tar.gz: 8bc6db30919be535ab9b1a6064f8ba58be63791b0cb091fe63a6dca47e57df76
3
+ metadata.gz: 52c58921ef29966128ff879a465b658aecc03be72d9854b20e35fcf374bdf227
4
+ data.tar.gz: f33f53d08767a17f9409255954c1561a9d334e58c8d4e141410a3f1e3af8d429
5
5
  SHA512:
6
- metadata.gz: 3e29900e2827d55ab8efd6bebe99a5a73fdd4d49b709dbeeafeefc73aeddbf31fba242105b0dd7e547f15852855e5a3e45dd3bf4df51fc6365dc156da936a094
7
- data.tar.gz: cb4ae5408587d29aab57d7e87f94a5fbfcd3097eb93a2e5a0e9335816f704560c42acde42f6c1bd3b970ab043cb9163249a8d4672b59e04a26d4a9244ae414bf
6
+ metadata.gz: 679238d4b8f289a4e653a11a86002544d1eabbba16d381e691bbbfc2d8a813c4c86d4a988d2d008f7d88d637bbfd8ab24b9f8b844bae12270d04f63150dbb6b0
7
+ data.tar.gz: 4a0fe1f0a1f63ce23242147a896c8f45a6acad9bee3118b668a553aa9af68bf565e358d0ecc821511c5fa55b66c60dec7005bc99dd11bc43a9af2d9220608508
data/README.md CHANGED
@@ -198,6 +198,26 @@ Each run enqueues one job per person, so one failing send does not stop the
198
198
  rest. A window is sent once even if the run overlaps itself; a send that fails
199
199
  releases the window so it can be sent again.
200
200
 
201
+ ## Deleting old delivery records
202
+
203
+ Notey writes one record per notification per channel that leaves the
204
+ application, and nothing deletes them on its own. Say how long to keep them:
205
+
206
+ ```ruby
207
+ # config/initializers/notey.rb
208
+ Notey.attempt_retention = 90.days
209
+ ```
210
+
211
+ Then run the deletion on a schedule, the way you run the digest windows:
212
+
213
+ ```ruby
214
+ Notey::DeleteOldAttempts.new.call
215
+ ```
216
+
217
+ Everything older than the period goes and everything inside it stays, so
218
+ running it twice leaves the same records as running it once. With no period set
219
+ it refuses to run rather than deleting nothing quietly.
220
+
201
221
  ## Destinations
202
222
 
203
223
  A channel like email reaches a person at an address the app already holds. A
@@ -2,6 +2,10 @@
2
2
 
3
3
  module Notey
4
4
  class Email < Noticed::DeliveryMethod
5
+ def self.reachable?(recipient)
6
+ recipient.email.present?
7
+ end
8
+
5
9
  def deliver
6
10
  NotificationMailer.with(notification: notification, recipient: recipient).notification.deliver_now
7
11
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Notey
4
+ class DeleteOldAttempts
5
+ UNSET = "notey deletes delivery records older than attempt_retention, which is not set"
6
+
7
+ def call
8
+ raise MissingRetention, UNSET if retention.nil?
9
+
10
+ Attempt.where(created_at: ...retention.ago).delete_all
11
+ end
12
+
13
+ private
14
+
15
+ def retention
16
+ Notey.attempt_retention
17
+ end
18
+ end
19
+ end
@@ -27,7 +27,8 @@ module Notey
27
27
  config = ActiveSupport::OrderedOptions.new
28
28
  config[:class] = channel.delivery_method if channel.delivery_method
29
29
  config[:notey_channel] = channel.name
30
- config[:if] = Notey.sends(notey_notification_type, on: channel.name, addressed: channel.addressed?)
30
+ config[:if] = Notey.sends(notey_notification_type, on: channel.name, addressed: channel.addressed?,
31
+ via: Notey.known_delivery_method(channel))
31
32
  config
32
33
  end
33
34
  private_class_method :delivery_config
data/lib/notey/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Notey
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/notey.rb CHANGED
@@ -19,9 +19,11 @@ module Notey
19
19
  class MissingSender < StandardError; end
20
20
  class UnsendableChannel < StandardError; end
21
21
  class UnsendableAttempt < StandardError; end
22
+ class MissingRetention < StandardError; end
22
23
 
23
24
  class << self
24
25
  attr_writer :notification_url, :mailer_sender
26
+ attr_accessor :attempt_retention
25
27
  end
26
28
 
27
29
  def self.mailer_sender
@@ -95,6 +97,12 @@ module Notey
95
97
  "the channel #{channel.name} needs the option #{required.first}, which notey does not supply"
96
98
  end
97
99
 
100
+ def self.known_delivery_method(channel)
101
+ delivery_method_for(channel)
102
+ rescue UnsendableChannel
103
+ nil
104
+ end
105
+
98
106
  def self.delivery_method_for(channel)
99
107
  name = channel.delivery_method || "Noticed::DeliveryMethods::#{channel.name.camelize}"
100
108
  name.to_s.constantize
@@ -108,11 +116,12 @@ module Notey
108
116
  raise MissingSender, "notey sends digests by email and no mailer_sender is set"
109
117
  end
110
118
 
111
- def self.sends(notification_type, on:, addressed: false)
119
+ def self.sends(notification_type, on:, addressed: false, via: nil)
112
120
  lambda do
113
121
  decision = Channels.decision_for(recipient, notification_type, account_id: event.account_id)
114
122
 
115
123
  next false unless decision.window == "immediate" && decision.channels.include?(on.to_s)
124
+ next false if via.respond_to?(:reachable?) && !via.reachable?(recipient)
116
125
  next true unless addressed
117
126
 
118
127
  Destinations.for(event.account_id, on, member: recipient).present?
@@ -2,7 +2,7 @@
2
2
  name: notey-develop
3
3
  description: Use PROACTIVELY for defining a notification type, sending one to the recipients your own code resolved, sending one when a domain event fires, addressing a channel at a person, scheduling daily and weekly digest runs, reading a person's inbox, and reading their channels and window — MUST BE USED instead of hand-rolling preference checks, per-person delivery addresses, or a digest loop.
4
4
  tools: Read, Write, Edit, Grep
5
- scope: notifications — application-wide channel registrations, notification types that name no channel, per-person per-account channel preferences, digest windows, an in-app inbox, per-account destinations, and a record of what was sent on each channel
5
+ scope: notifications — application-wide channel registrations, notification types that name no channel, per-person per-account channel preferences, digest windows, an in-app inbox, per-account destinations, and a record of what was sent on each channel that the host keeps for a period it sets
6
6
  ---
7
7
 
8
8
  This local follows the steps below exactly and invents no others. Where a step
@@ -40,6 +40,9 @@ This local assumes the gem is already hooked into the app; if it is not, that is
40
40
  - `Notey::DigestRun` — sends one window's digests, with `call` for everyone on
41
41
  that window and `deliver_to_member(member, account_id)` for one person in one
42
42
  account.
43
+ - `Notey::DeleteOldAttempts` — deletes every record of what was sent that is
44
+ older than `Notey.attempt_retention`, and refuses to run when no retention is
45
+ set.
43
46
  - `Notey::Inbox.for(member, account_id:)` — returns that person's notifications
44
47
  for that account, as a relation.
45
48
  - `wants?(type, on:, account_id:)` — on the recipient model, true when that
@@ -133,7 +136,21 @@ This local assumes the gem is already hooked into the app; if it is not, that is
133
136
  in this app — cron, a scheduler gem, or a platform scheduler — and at what
134
137
  hour each window should go out.
135
138
 
136
- 7. Read a person's notifications through the inbox rather than querying Noticed
139
+ 7. Schedule the deletion of old delivery records alongside the windows:
140
+
141
+ ```ruby
142
+ Notey::DeleteOldAttempts.new.call
143
+ ```
144
+
145
+ notey writes one record per notification per channel that leaves the
146
+ application and deletes none of them on its own, so a host that never runs
147
+ this keeps every row forever. It deletes everything older than
148
+ `Notey.attempt_retention` and refuses to run when that is not set, which
149
+ `notey-install` is the step that sets. Running it twice leaves the same
150
+ records as running it once, so a schedule that overlaps itself is safe. Ask
151
+ the developer how often it should run.
152
+
153
+ 8. Read a person's notifications through the inbox rather than querying Noticed
137
154
  directly:
138
155
 
139
156
  ```ruby
@@ -143,7 +160,7 @@ This local assumes the gem is already hooked into the app; if it is not, that is
143
160
  It returns a relation, so chain `.unread`, `.order` and `.limit` onto it. With
144
161
  no account it returns an empty relation, never another account's rows.
145
162
 
146
- 8. Read a person's preferences from the host's own code when the app needs to
163
+ 9. Read a person's preferences from the host's own code when the app needs to
147
164
  branch on them:
148
165
 
149
166
  ```ruby
@@ -156,7 +173,7 @@ This local assumes the gem is already hooked into the app; if it is not, that is
156
173
  three read a person's stored row for that type, and fall back to email and
157
174
  in-app when they have never stored one.
158
175
 
159
- 9. In tests that register their own channels or notification types, call
176
+ 10. In tests that register their own channels or notification types, call
160
177
  `Notey.reset!` in teardown. Both are held for the life of the process, so a
161
178
  test that skips this leaves its channels and types in place for every test
162
179
  after it.
@@ -185,7 +202,7 @@ This local assumes the gem is already hooked into the app; if it is not, that is
185
202
  no row saying it went out.
186
203
  - **Notification types, subscribers and digest schedules all belong in the
187
204
  host's code**, never in the gem.
188
- - **Out of scope.** Registering the channels the application has, making a model
189
- a recipient, setting the current person and account, storing an account's
190
- addresses, and the pages a person picks their own channels on all belong to
191
- `notey-install`.
205
+ - **Out of scope.** Registering the channels the application has, setting how
206
+ long delivery records are kept, making a model a recipient, setting the
207
+ current person and account, storing an account's addresses, and the pages a
208
+ person picks their own channels on all belong to `notey-install`.
@@ -2,7 +2,7 @@
2
2
  name: notey-info
3
3
  description: Use to learn what notey offers — notification types and the channels they allow, per-person per-account delivery preferences, digest windows, an in-app inbox, and per-account destinations.
4
4
  tools: Read
5
- scope: notifications — application-wide channel registrations, notification types that name no channel, per-person per-account channel preferences, digest windows, an in-app inbox, per-account destinations, and a record of what was sent on each channel
5
+ scope: notifications — application-wide channel registrations, notification types that name no channel, per-person per-account channel preferences, digest windows, an in-app inbox, per-account destinations, and a record of what was sent on each channel that the host keeps for a period it sets
6
6
  ---
7
7
 
8
8
  This local explains what notey is and which of its other two locals you need. It
@@ -80,4 +80,5 @@ has already been sent, you are building, and `notey-develop` owns it.
80
80
  - **Attempt** — one row per notification per outbound channel, claimed before the
81
81
  send and marked after, holding whether it was sent and what a failure said. It
82
82
  is what answers why one person was not reached, and it is what stops the same
83
- channel sending twice for the same notification.
83
+ channel sending twice for the same notification. The host says how long these
84
+ are kept and runs the deletion that enforces it.
@@ -2,7 +2,7 @@
2
2
  name: notey-install
3
3
  description: Use to hook notey into a project — installing its tables, mounting its engine, registering the channels the application can send on, making a model a recipient, setting the current person and account, putting the account on Noticed's rows, supplying a notification url, and reaching the preferences, inbox and destinations pages.
4
4
  tools: Bash, Read, Edit
5
- scope: notifications — application-wide channel registrations, notification types that name no channel, per-person per-account channel preferences, digest windows, an in-app inbox, per-account destinations, and a record of what was sent on each channel
5
+ scope: notifications — application-wide channel registrations, notification types that name no channel, per-person per-account channel preferences, digest windows, an in-app inbox, per-account destinations, and a record of what was sent on each channel that the host keeps for a period it sets
6
6
  ---
7
7
 
8
8
  This local follows the steps below exactly and invents no others. Where a step
@@ -33,6 +33,9 @@ different notifications in each.
33
33
  through it.
34
34
  - `Notey.notification_url=` — takes a lambda returning the host's own url for
35
35
  one notification, used to link the rows in a digest email.
36
+ - `Notey.attempt_retention=` — takes how long notey keeps the record of what it
37
+ sent on each channel, such as `90.days`. Nothing is deleted until the host
38
+ runs the deletion.
36
39
  - `Notey.check!` — raises when a registered channel names a delivery method that
37
40
  does not exist or needs an option notey does not supply, and when notey has no
38
41
  sender address for its digests. notey runs it itself after initialization when
@@ -162,7 +165,18 @@ moves all three.
162
165
  the engine's copy. Ask the developer whether these pages should carry the
163
166
  app's navigation.
164
167
 
165
- 12. Set up destinations only if a registered channel needs an address. The stored credential is encrypted, so
168
+ 12. Set the retention for delivery records in
169
+ `config/initializers/notey.rb`:
170
+
171
+ ```ruby
172
+ Notey.attempt_retention = 90.days
173
+ ```
174
+
175
+ Ask the developer how long this application should keep the record of what
176
+ was sent on each channel; there is no default. Scheduling the deletion is
177
+ `notey-develop`'s step, and nothing is deleted until it runs.
178
+
179
+ 13. Set up destinations only if a registered channel needs an address. The stored credential is encrypted, so
166
180
  Active Record encryption keys must be configured in the host's credentials
167
181
  (`bin/rails db:encryption:init` generates a set) or saving a destination
168
182
  raises. Ask the developer whether any channel needs this before doing it.
@@ -190,6 +204,10 @@ moves all three.
190
204
  that changes it.
191
205
  - **Re-run `bin/rails notey:install:migrations` after upgrading the gem.** It
192
206
  copies only the migrations the host does not already have.
193
- - **Out of scope.** Scheduling the digest runs, writing a notification type,
207
+ - **Nothing deletes a delivery record until the host runs the deletion.** The
208
+ table grows for every notification on every outbound channel, so a host that
209
+ sets no retention and schedules no deletion keeps every row forever.
210
+ - **Out of scope.** Scheduling the digest runs and the deletion of old delivery
211
+ records, writing a notification type,
194
212
  sending one when a domain event fires, and reading the inbox from the host's
195
213
  own code all belong to `notey-develop`.
@@ -1,4 +1,4 @@
1
- scope: notifications — application-wide channel registrations, notification types that name no channel, per-person per-account channel preferences, digest windows, an in-app inbox, per-account destinations, and a record of what was sent on each channel
1
+ scope: notifications — application-wide channel registrations, notification types that name no channel, per-person per-account channel preferences, digest windows, an in-app inbox, per-account destinations, and a record of what was sent on each channel that the host keeps for a period it sets
2
2
 
3
3
  install:
4
4
  - bin/rails notey:install:migrations
@@ -8,6 +8,7 @@ install:
8
8
  - Notey::Notification
9
9
  - Notey::Current
10
10
  - Notey.notification_url=
11
+ - Notey.attempt_retention=
11
12
  - Notey.check!
12
13
  - /notey/preferences
13
14
  - /notey/notifications
@@ -19,6 +20,7 @@ develop:
19
20
  - notify
20
21
  - Notey::Destinations.for
21
22
  - Notey::DigestRun
23
+ - Notey::DeleteOldAttempts
22
24
  - Notey::Inbox.for
23
25
  - wants?
24
26
  - channels_for
@@ -40,6 +42,7 @@ sources:
40
42
  - app/models/notey/destination.rb
41
43
  - app/models/notey/digest.rb
42
44
  - app/models/notey/attempt.rb
45
+ - app/models/notey/delete_old_attempts.rb
43
46
  - app/delivery_methods/notey/email.rb
44
47
  - app/delivery_methods/notey/in_app.rb
45
48
  - app/jobs/notey/digest_job.rb
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: notey
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - tylercschneider
@@ -88,6 +88,7 @@ files:
88
88
  - app/models/notey/application_record.rb
89
89
  - app/models/notey/attempt.rb
90
90
  - app/models/notey/current.rb
91
+ - app/models/notey/delete_old_attempts.rb
91
92
  - app/models/notey/destination.rb
92
93
  - app/models/notey/digest.rb
93
94
  - app/models/notey/kept.rb