missive 0.0.2 → 0.0.4
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 +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +62 -15
- data/app/models/concerns/missive/user.rb +22 -0
- data/app/models/concerns/missive/user_as_sender.rb +63 -9
- data/app/models/concerns/missive/user_as_subscriber.rb +68 -13
- data/lib/missive/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2524889a76072d9a0c8a32bcbcf644a47b53ef388eeb5937ab2d643f9f7943e9
|
|
4
|
+
data.tar.gz: 27050e567ac00cbf4a1d784a2495601b68fe8bfb481c14d3d822d99a33d0d086
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6d58ad49167ba5a32acc92dd1e74e69c74ccd83748465f48497eef9a1d212bd092c5938a3fa1f3fa9357f3bdb8e5cc29c82152ac10cca3b89b094b51cd4ac94e
|
|
7
|
+
data.tar.gz: 5292722bfed62d90c92d6d61ec831c46fa495bb29609469da887711557e3e8f60a9ceb3bc4084a4fdf04c3b99cbbcf041cd7945100550a7bfbe20c1481ea4f82
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.0.4] - 2026-02-04
|
|
4
|
+
|
|
5
|
+
- Fix associations collision checks and configuration
|
|
6
|
+
|
|
7
|
+
## [0.0.3] - 2026-02-04
|
|
8
|
+
|
|
9
|
+
- Add associations collision checks and configuration
|
|
10
|
+
|
|
11
|
+
## [0.0.2] - 2025-12-14
|
|
12
|
+
|
|
3
13
|
- Add install generator
|
|
4
14
|
- Add Postmark Bulk API implementation
|
|
5
15
|
|
data/README.md
CHANGED
|
@@ -91,21 +91,21 @@ This is equivalent to:
|
|
|
91
91
|
```rb
|
|
92
92
|
class User < ApplicationRecord
|
|
93
93
|
# Missive::UserAsSender
|
|
94
|
-
has_one :
|
|
95
|
-
has_many :
|
|
96
|
-
has_many :
|
|
97
|
-
has_many :
|
|
94
|
+
has_one :missive_sender # ...
|
|
95
|
+
has_many :missive_sent_dispatches # ...
|
|
96
|
+
has_many :missive_sent_lists # ...
|
|
97
|
+
has_many :missive_sent_messages # ...
|
|
98
98
|
|
|
99
99
|
def init_sender(attributes = {});
|
|
100
100
|
# ...
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
# Missive::UserAsSubscriber
|
|
104
|
-
has_one :
|
|
105
|
-
has_many :
|
|
106
|
-
has_many :
|
|
107
|
-
has_many :
|
|
108
|
-
has_many :
|
|
104
|
+
has_one :missive_subscriber # ...
|
|
105
|
+
has_many :missive_dispatches # ...
|
|
106
|
+
has_many :missive_subscriptions # ...
|
|
107
|
+
has_many :missive_subscribed_lists # ...
|
|
108
|
+
has_many :missive_unsubscribed_lists # ...
|
|
109
109
|
|
|
110
110
|
def init_subscriber(attributes = {})
|
|
111
111
|
# ...
|
|
@@ -113,6 +113,53 @@ class User < ApplicationRecord
|
|
|
113
113
|
end
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
+
#### Customizing association names
|
|
117
|
+
|
|
118
|
+
By default, association names are prefixed with `missive_` to avoid collisions with existing associations. If you want to use shorter names (e.g., `sender` instead of `missive_sender`), you can customize them using the `.with` method:
|
|
119
|
+
|
|
120
|
+
```rb
|
|
121
|
+
class User < ApplicationRecord
|
|
122
|
+
include Missive::User.with(
|
|
123
|
+
sender: {
|
|
124
|
+
sender: :sender,
|
|
125
|
+
sent_dispatches: :sent_dispatches,
|
|
126
|
+
sent_lists: :sent_lists,
|
|
127
|
+
sent_messages: :sent_messages
|
|
128
|
+
},
|
|
129
|
+
subscriber: {
|
|
130
|
+
subscriber: :subscriber,
|
|
131
|
+
dispatches: :dispatches,
|
|
132
|
+
subscriptions: :subscriptions,
|
|
133
|
+
subscribed_lists: :subscribed_lists,
|
|
134
|
+
unsubscribed_lists: :unsubscribed_lists
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Or, if including the concerns separately:
|
|
141
|
+
|
|
142
|
+
```rb
|
|
143
|
+
class User < ApplicationRecord
|
|
144
|
+
include Missive::UserAsSender.with(
|
|
145
|
+
sender: :sender,
|
|
146
|
+
sent_dispatches: :sent_dispatches,
|
|
147
|
+
sent_lists: :sent_lists,
|
|
148
|
+
sent_messages: :sent_messages
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
include Missive::UserAsSubscriber.with(
|
|
152
|
+
subscriber: :subscriber,
|
|
153
|
+
dispatches: :dispatches,
|
|
154
|
+
subscriptions: :subscriptions,
|
|
155
|
+
subscribed_lists: :subscribed_lists,
|
|
156
|
+
unsubscribed_lists: :unsubscribed_lists
|
|
157
|
+
)
|
|
158
|
+
end
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
You only need to customize the associations you want to rename - any unconfigured associations will use their default `missive_` prefixed names.
|
|
162
|
+
|
|
116
163
|
#### Manage subscriptions
|
|
117
164
|
|
|
118
165
|
```rb
|
|
@@ -125,17 +172,17 @@ list = Missive::List.first
|
|
|
125
172
|
user.init_subscriber
|
|
126
173
|
|
|
127
174
|
# List the subscriptions
|
|
128
|
-
user.
|
|
175
|
+
user.missive_subscriptions # returns a `Missive::Subscription` collection
|
|
129
176
|
|
|
130
177
|
# List the (un)subscribed lists
|
|
131
|
-
user.
|
|
132
|
-
user.
|
|
178
|
+
user.missive_subscribed_lists # returns a `Missive::List` collection
|
|
179
|
+
user.missive_unsubscribed_lists # returns a `Missive::List` collection
|
|
133
180
|
|
|
134
181
|
# Subscribe to an existing Missive::List
|
|
135
|
-
user.
|
|
182
|
+
user.missive_subscriber.subscriptions.create!(list:)
|
|
136
183
|
|
|
137
184
|
# Unsubscribe from the list
|
|
138
|
-
user.
|
|
185
|
+
user.missive_subscriptions.find_by(list:).suppress!(reason: :manual_suppression)
|
|
139
186
|
```
|
|
140
187
|
|
|
141
188
|
#### Manage senders
|
|
@@ -151,7 +198,7 @@ list = Missive::List.first
|
|
|
151
198
|
user.init_sender(name: user.full_name)
|
|
152
199
|
|
|
153
200
|
# Make them the default sender for a list
|
|
154
|
-
user.
|
|
201
|
+
user.missive_sent_lists << list
|
|
155
202
|
```
|
|
156
203
|
|
|
157
204
|
#### Manage lists
|
|
@@ -2,6 +2,28 @@ module Missive
|
|
|
2
2
|
module User
|
|
3
3
|
extend ActiveSupport::Concern
|
|
4
4
|
|
|
5
|
+
def self.with(sender: {}, subscriber: {})
|
|
6
|
+
Module.new do
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
define_singleton_method(:inspect) { "Missive::User.with(sender: #{sender.inspect}, subscriber: #{subscriber.inspect})" }
|
|
10
|
+
|
|
11
|
+
included do
|
|
12
|
+
if sender.empty?
|
|
13
|
+
include Missive::UserAsSender
|
|
14
|
+
else
|
|
15
|
+
include Missive::UserAsSender.with(sender)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
if subscriber.empty?
|
|
19
|
+
include Missive::UserAsSubscriber
|
|
20
|
+
else
|
|
21
|
+
include Missive::UserAsSubscriber.with(subscriber)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
5
27
|
included do
|
|
6
28
|
include Missive::UserAsSender
|
|
7
29
|
include Missive::UserAsSubscriber
|
|
@@ -2,18 +2,72 @@ module Missive
|
|
|
2
2
|
module UserAsSender
|
|
3
3
|
extend ActiveSupport::Concern
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
has_one :sender, class_name: "Missive::Sender", dependent: :nullify
|
|
7
|
-
has_many :sent_dispatches, class_name: "Missive::Dispatch", through: :sender, source: :dispatches
|
|
8
|
-
has_many :sent_lists, class_name: "Missive::List", through: :sender, source: :lists
|
|
9
|
-
has_many :sent_messages, class_name: "Missive::Message", through: :sender, source: :messages
|
|
5
|
+
class AssociationAlreadyDefinedError < StandardError; end
|
|
10
6
|
|
|
7
|
+
DEFAULT_ASSOCIATION_NAMES = {
|
|
8
|
+
sender: :missive_sender,
|
|
9
|
+
sent_dispatches: :missive_sent_dispatches,
|
|
10
|
+
sent_lists: :missive_sent_lists,
|
|
11
|
+
sent_messages: :missive_sent_messages
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
def self.with(options = {})
|
|
15
|
+
config = DEFAULT_ASSOCIATION_NAMES.merge(options)
|
|
16
|
+
|
|
17
|
+
Module.new do
|
|
18
|
+
extend ActiveSupport::Concern
|
|
19
|
+
|
|
20
|
+
define_singleton_method(:inspect) { "Missive::UserAsSender.with(#{options.inspect})" }
|
|
21
|
+
|
|
22
|
+
included do |base|
|
|
23
|
+
base.instance_variable_set(:@missive_sender_config, config)
|
|
24
|
+
base.extend(ClassMethods)
|
|
25
|
+
base.include(InstanceMethods)
|
|
26
|
+
base._define_missive_sender_associations
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
module ClassMethods
|
|
32
|
+
def missive_sender_config
|
|
33
|
+
@missive_sender_config ||= DEFAULT_ASSOCIATION_NAMES.dup
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def _define_missive_sender_associations
|
|
37
|
+
config = missive_sender_config
|
|
38
|
+
|
|
39
|
+
_check_missive_association_collision!(config[:sender])
|
|
40
|
+
|
|
41
|
+
has_one config[:sender], class_name: "Missive::Sender", foreign_key: :user_id, dependent: :nullify
|
|
42
|
+
has_many config[:sent_dispatches], class_name: "Missive::Dispatch", through: config[:sender], source: :dispatches
|
|
43
|
+
has_many config[:sent_lists], class_name: "Missive::List", through: config[:sender], source: :lists
|
|
44
|
+
has_many config[:sent_messages], class_name: "Missive::Message", through: config[:sender], source: :messages
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def _check_missive_association_collision!(name)
|
|
48
|
+
return unless reflect_on_association(name)
|
|
49
|
+
|
|
50
|
+
raise AssociationAlreadyDefinedError,
|
|
51
|
+
"Association :#{name} is already defined on #{self.name}. " \
|
|
52
|
+
"Use Missive::UserAsSender.with to specify a different name. " \
|
|
53
|
+
"Example: include Missive::UserAsSender.with(sender: :missive_sender)"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
module InstanceMethods
|
|
11
58
|
def init_sender(attributes = {})
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
59
|
+
assoc = self.class.missive_sender_config[:sender]
|
|
60
|
+
send("#{assoc}=", Missive::Sender.find_or_initialize_by(email:))
|
|
61
|
+
send(assoc).assign_attributes(attributes)
|
|
62
|
+
send(assoc).save!
|
|
63
|
+
send(assoc)
|
|
16
64
|
end
|
|
17
65
|
end
|
|
66
|
+
|
|
67
|
+
included do |base|
|
|
68
|
+
base.extend(ClassMethods)
|
|
69
|
+
base.include(InstanceMethods)
|
|
70
|
+
base._define_missive_sender_associations
|
|
71
|
+
end
|
|
18
72
|
end
|
|
19
73
|
end
|
|
@@ -2,22 +2,77 @@ module Missive
|
|
|
2
2
|
module UserAsSubscriber
|
|
3
3
|
extend ActiveSupport::Concern
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
has_one :subscriber, class_name: "Missive::Subscriber", dependent: :destroy
|
|
7
|
-
has_many :dispatches, class_name: "Missive::Dispatch", through: :subscriber
|
|
8
|
-
has_many :subscriptions, class_name: "Missive::Subscription", through: :subscriber
|
|
9
|
-
has_many :subscribed_lists, class_name: "Missive::List", through: :subscriber, source: :lists
|
|
10
|
-
has_many :unsubscribed_lists, -> { where.not(missive_subscriptions: {suppressed_at: nil}) },
|
|
11
|
-
class_name: "Missive::List",
|
|
12
|
-
through: :subscriber,
|
|
13
|
-
source: :lists
|
|
5
|
+
class AssociationAlreadyDefinedError < StandardError; end
|
|
14
6
|
|
|
7
|
+
DEFAULT_ASSOCIATION_NAMES = {
|
|
8
|
+
subscriber: :missive_subscriber,
|
|
9
|
+
dispatches: :missive_dispatches,
|
|
10
|
+
subscriptions: :missive_subscriptions,
|
|
11
|
+
subscribed_lists: :missive_subscribed_lists,
|
|
12
|
+
unsubscribed_lists: :missive_unsubscribed_lists
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
def self.with(options = {})
|
|
16
|
+
config = DEFAULT_ASSOCIATION_NAMES.merge(options)
|
|
17
|
+
|
|
18
|
+
Module.new do
|
|
19
|
+
extend ActiveSupport::Concern
|
|
20
|
+
|
|
21
|
+
define_singleton_method(:inspect) { "Missive::UserAsSubscriber.with(#{options.inspect})" }
|
|
22
|
+
|
|
23
|
+
included do |base|
|
|
24
|
+
base.instance_variable_set(:@missive_subscriber_config, config)
|
|
25
|
+
base.extend(ClassMethods)
|
|
26
|
+
base.include(InstanceMethods)
|
|
27
|
+
base._define_missive_subscriber_associations
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
module ClassMethods
|
|
33
|
+
def missive_subscriber_config
|
|
34
|
+
@missive_subscriber_config ||= DEFAULT_ASSOCIATION_NAMES.dup
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def _define_missive_subscriber_associations
|
|
38
|
+
config = missive_subscriber_config
|
|
39
|
+
|
|
40
|
+
_check_missive_association_collision!(config[:subscriber])
|
|
41
|
+
|
|
42
|
+
has_one config[:subscriber], class_name: "Missive::Subscriber", foreign_key: :user_id, dependent: :destroy
|
|
43
|
+
has_many config[:dispatches], class_name: "Missive::Dispatch", through: config[:subscriber], source: :dispatches
|
|
44
|
+
has_many config[:subscriptions], class_name: "Missive::Subscription", through: config[:subscriber], source: :subscriptions
|
|
45
|
+
has_many config[:subscribed_lists], class_name: "Missive::List", through: config[:subscriber], source: :lists
|
|
46
|
+
has_many config[:unsubscribed_lists], -> { where.not(missive_subscriptions: {suppressed_at: nil}) },
|
|
47
|
+
class_name: "Missive::List",
|
|
48
|
+
through: config[:subscriber],
|
|
49
|
+
source: :lists
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def _check_missive_association_collision!(name)
|
|
53
|
+
return unless reflect_on_association(name)
|
|
54
|
+
|
|
55
|
+
raise AssociationAlreadyDefinedError,
|
|
56
|
+
"Association :#{name} is already defined on #{self.name}. " \
|
|
57
|
+
"Use Missive::UserAsSubscriber.with to specify a different name. " \
|
|
58
|
+
"Example: include Missive::UserAsSubscriber.with(subscriber: :missive_subscriber)"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
module InstanceMethods
|
|
15
63
|
def init_subscriber(attributes = {})
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
64
|
+
assoc = self.class.missive_subscriber_config[:subscriber]
|
|
65
|
+
send("#{assoc}=", Missive::Subscriber.find_or_initialize_by(email:))
|
|
66
|
+
send(assoc).assign_attributes(attributes)
|
|
67
|
+
send(assoc).save!
|
|
68
|
+
send(assoc)
|
|
20
69
|
end
|
|
21
70
|
end
|
|
71
|
+
|
|
72
|
+
included do |base|
|
|
73
|
+
base.extend(ClassMethods)
|
|
74
|
+
base.include(InstanceMethods)
|
|
75
|
+
base._define_missive_subscriber_associations
|
|
76
|
+
end
|
|
22
77
|
end
|
|
23
78
|
end
|
data/lib/missive/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: missive
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hans Lemuet
|
|
@@ -130,7 +130,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
130
130
|
- !ruby/object:Gem::Version
|
|
131
131
|
version: '0'
|
|
132
132
|
requirements: []
|
|
133
|
-
rubygems_version:
|
|
133
|
+
rubygems_version: 4.0.4
|
|
134
134
|
specification_version: 4
|
|
135
135
|
summary: Toolbox for managing newsletters in Rails, sending them with Postmark.
|
|
136
136
|
test_files: []
|