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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 63e8195762299f95bdc217efcf1a81dbd8be46ec029ea90b8b4db430af37eabd
4
- data.tar.gz: 94532fa3c3cd42c9086b35385773c0d2f8483dad96690b0d78523512736aa86e
3
+ metadata.gz: 2524889a76072d9a0c8a32bcbcf644a47b53ef388eeb5937ab2d643f9f7943e9
4
+ data.tar.gz: 27050e567ac00cbf4a1d784a2495601b68fe8bfb481c14d3d822d99a33d0d086
5
5
  SHA512:
6
- metadata.gz: d365c0b22c19489e7d61d46746bec34d58834c167066f02eb71cec287e855a7e820e1eb535e066c2f871cc948338bbbd5151efac7838ca037e2455b36c4dbe07
7
- data.tar.gz: d7c9d72e65bbac0b683b6f5b69b888547f7ebc5245c35b6b9f582042ad8ef524b775acaed2aa47d17dd4a2be689315c02e41570913edebf7c213b5d0dc728f6f
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 :sender # ...
95
- has_many :sent_dispatches # ...
96
- has_many :sent_lists # ...
97
- has_many :sent_messages # ...
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 :subscriber # ...
105
- has_many :dispatches # ...
106
- has_many :subscriptions # ...
107
- has_many :subscribed_lists # ...
108
- has_many :unsubscribed_lists # ...
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.subscriptions # returns a `Missive::Subscription` collection
175
+ user.missive_subscriptions # returns a `Missive::Subscription` collection
129
176
 
130
177
  # List the (un)subscribed lists
131
- user.subscribed_lists # returns a `Missive::List` collection
132
- user.unsubscribed_lists # returns a `Missive::List` collection
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.subscriber.subscriptions.create!(list:)
182
+ user.missive_subscriber.subscriptions.create!(list:)
136
183
 
137
184
  # Unsubscribe from the list
138
- user.subscriptions.find_by(list:).suppress!(reason: :manual_suppression)
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.sent_lists << list
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
- included do
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
- self.sender = Missive::Sender.find_or_initialize_by(email:)
13
- sender.assign_attributes(attributes)
14
- sender.save!
15
- sender
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
- included do
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
- self.subscriber = Missive::Subscriber.find_or_initialize_by(email:)
17
- subscriber.assign_attributes(attributes)
18
- subscriber.save!
19
- subscriber
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
@@ -1,3 +1,3 @@
1
1
  module Missive
2
- VERSION = "0.0.2".freeze
2
+ VERSION = "0.0.4".freeze
3
3
  end
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.2
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: 3.7.2
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: []