caffeinate 0.2.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +162 -77
  3. data/app/controllers/caffeinate/campaign_subscriptions_controller.rb +3 -3
  4. data/app/models/caffeinate/application_record.rb +0 -1
  5. data/app/models/caffeinate/campaign.rb +49 -2
  6. data/app/models/caffeinate/campaign_subscription.rb +50 -13
  7. data/app/models/caffeinate/mailing.rb +14 -6
  8. data/app/views/layouts/{caffeinate.html.erb → _caffeinate.html.erb} +0 -0
  9. data/db/migrate/20201124183102_create_caffeinate_campaigns.rb +1 -0
  10. data/db/migrate/20201124183303_create_caffeinate_campaign_subscriptions.rb +6 -3
  11. data/db/migrate/20201124183419_create_caffeinate_mailings.rb +2 -1
  12. data/lib/caffeinate.rb +4 -8
  13. data/lib/caffeinate/action_mailer.rb +4 -4
  14. data/lib/caffeinate/action_mailer/extension.rb +11 -5
  15. data/lib/caffeinate/action_mailer/interceptor.rb +4 -2
  16. data/lib/caffeinate/action_mailer/observer.rb +4 -3
  17. data/lib/caffeinate/active_record/extension.rb +17 -11
  18. data/lib/caffeinate/configuration.rb +11 -2
  19. data/lib/caffeinate/drip.rb +15 -2
  20. data/lib/caffeinate/drip_evaluator.rb +3 -0
  21. data/lib/caffeinate/dripper/base.rb +12 -5
  22. data/lib/caffeinate/dripper/batching.rb +22 -0
  23. data/lib/caffeinate/dripper/callbacks.rb +89 -6
  24. data/lib/caffeinate/dripper/campaign.rb +20 -8
  25. data/lib/caffeinate/dripper/defaults.rb +4 -2
  26. data/lib/caffeinate/dripper/delivery.rb +8 -8
  27. data/lib/caffeinate/dripper/drip.rb +3 -42
  28. data/lib/caffeinate/dripper/drip_collection.rb +62 -0
  29. data/lib/caffeinate/dripper/inferences.rb +7 -2
  30. data/lib/caffeinate/dripper/perform.rb +14 -7
  31. data/lib/caffeinate/dripper/periodical.rb +26 -0
  32. data/lib/caffeinate/dripper/subscriber.rb +14 -2
  33. data/lib/caffeinate/dripper_collection.rb +17 -0
  34. data/lib/caffeinate/engine.rb +6 -4
  35. data/lib/caffeinate/helpers.rb +3 -0
  36. data/lib/caffeinate/mail_ext.rb +12 -0
  37. data/lib/caffeinate/url_helpers.rb +3 -0
  38. data/lib/caffeinate/version.rb +1 -1
  39. data/lib/generators/caffeinate/install_generator.rb +5 -1
  40. data/lib/generators/caffeinate/templates/caffeinate.rb +21 -1
  41. metadata +22 -4
  42. data/lib/caffeinate/action_mailer/helpers.rb +0 -12
@@ -17,12 +17,12 @@
17
17
  module Caffeinate
18
18
  # Records of the mails sent and to be sent for a given `::Caffeinate::CampaignSubscriber`
19
19
  class Mailing < ApplicationRecord
20
- CURRENT_THREAD_KEY = :current_caffeinate_mailing.freeze
21
-
22
20
  self.table_name = 'caffeinate_mailings'
23
21
 
24
22
  belongs_to :caffeinate_campaign_subscription, class_name: 'Caffeinate::CampaignSubscription'
23
+ alias_attribute :subscription, :caffeinate_campaign_subscription
25
24
  has_one :caffeinate_campaign, through: :caffeinate_campaign_subscription
25
+ alias_attribute :campaign, :caffeinate_campaign
26
26
 
27
27
  scope :upcoming, -> { unsent.unskipped.where('send_at < ?', ::Caffeinate.config.time_now).order('send_at asc') }
28
28
  scope :unsent, -> { unskipped.where(sent_at: nil) }
@@ -30,6 +30,13 @@ module Caffeinate
30
30
  scope :skipped, -> { where.not(skipped_at: nil) }
31
31
  scope :unskipped, -> { where(skipped_at: nil) }
32
32
 
33
+ def initialize_dup(args)
34
+ super
35
+ self.send_at = nil
36
+ self.sent_at = nil
37
+ self.skipped_at = nil
38
+ end
39
+
33
40
  # Checks if the Mailing is not skipped and not sent
34
41
  def pending?
35
42
  unskipped? && unsent?
@@ -59,13 +66,12 @@ module Caffeinate
59
66
  def skip!
60
67
  update!(skipped_at: Caffeinate.config.time_now)
61
68
 
62
- caffeinate_campaign.to_dripper.run_callbacks(:on_skip, caffeinate_campaign_subscription, self)
69
+ caffeinate_campaign.to_dripper.run_callbacks(:on_skip, self)
63
70
  end
64
71
 
65
72
  # The associated drip
66
- # @todo This can be optimized with a better cache
67
73
  def drip
68
- @drip ||= caffeinate_campaign.to_dripper.drip_collection[mailer_action]
74
+ @drip ||= caffeinate_campaign.to_dripper.drip_collection.for(mailer_action)
69
75
  end
70
76
 
71
77
  # The associated Subscriber from `::Caffeinate::CampaignSubscription`
@@ -80,7 +86,7 @@ module Caffeinate
80
86
 
81
87
  # Assigns attributes to the Mailing from the Drip
82
88
  def from_drip(drip)
83
- self.send_at = drip.send_at
89
+ self.send_at = drip.send_at(self)
84
90
  self.mailer_class = drip.options[:mailer_class]
85
91
  self.mailer_action = drip.action
86
92
  self
@@ -93,6 +99,8 @@ module Caffeinate
93
99
  else
94
100
  deliver!
95
101
  end
102
+
103
+ caffeinate_campaign_subscription.touch
96
104
  end
97
105
 
98
106
  # Delivers the Mailing in the foreground
@@ -6,6 +6,7 @@ class CreateCaffeinateCampaigns < ActiveRecord::Migration[6.0]
6
6
  create_table :caffeinate_campaigns do |t|
7
7
  t.string :name, null: false
8
8
  t.string :slug, null: false
9
+ t.boolean :active, default: true, null: false
9
10
 
10
11
  t.timestamps
11
12
  end
@@ -7,16 +7,19 @@ class CreateCaffeinateCampaignSubscriptions < ActiveRecord::Migration[6.0]
7
7
  create_table :caffeinate_campaign_subscriptions do |t|
8
8
  t.references :caffeinate_campaign, null: false, index: { name: :caffeineate_campaign_subscriptions_on_campaign }, foreign_key: true
9
9
  t.string :subscriber_type, null: false
10
- t.string :subscriber_id, null: false
10
+ t.integer :subscriber_id, null: false
11
11
  t.string :user_type
12
- t.string :user_id
12
+ t.integer :user_id
13
13
  t.string :token, null: false
14
14
  t.datetime :ended_at
15
+ t.string :ended_reason
16
+ t.datetime :resubscribed_at
15
17
  t.datetime :unsubscribed_at
18
+ t.string :unsubscribe_reason
16
19
 
17
20
  t.timestamps
18
21
  end
19
22
  add_index :caffeinate_campaign_subscriptions, :token, unique: true
20
- add_index :caffeinate_campaign_subscriptions, %i[subscriber_id subscriber_type user_id user_type], name: :index_caffeinate_campaign_subscriptions
23
+ add_index :caffeinate_campaign_subscriptions, %i[caffeinate_campaign_id subscriber_id subscriber_type user_id user_type ended_at resubscribed_at unsubscribed_at], name: :index_caffeinate_campaign_subscriptions
21
24
  end
22
25
  end
@@ -14,6 +14,7 @@ class CreateCaffeinateMailings < ActiveRecord::Migration[6.0]
14
14
 
15
15
  t.timestamps
16
16
  end
17
- add_index :caffeinate_mailings, %i[campaign_subscription_id mailer_class mailer_action sent_at send_at skipped_at], name: :index_caffeinate_mailings
17
+
18
+ add_index :caffeinate_mailings, %i[caffeinate_campaign_subscription_id send_at sent_at skipped_at], name: :index_caffeinate_mailings
18
19
  end
19
20
  end
@@ -1,22 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rails/all'
4
+ require 'caffeinate/mail_ext'
4
5
  require 'caffeinate/engine'
5
6
  require 'caffeinate/drip'
6
7
  require 'caffeinate/url_helpers'
7
8
  require 'caffeinate/configuration'
8
9
  require 'caffeinate/dripper/base'
9
10
  require 'caffeinate/deliver_async'
11
+ require 'caffeinate/dripper_collection'
10
12
 
11
13
  module Caffeinate
12
- # Caches the campaign to the campaign class
13
- def self.dripper_to_campaign_class
14
- @dripper_to_campaign_class ||= {}
15
- end
16
-
17
- # Convenience method for `dripper_to_campaign_class`
18
- def self.register_dripper(name, klass)
19
- dripper_to_campaign_class[name.to_sym] = klass
14
+ def self.dripper_collection
15
+ @drippers ||= DripperCollection.new
20
16
  end
21
17
 
22
18
  # Global configuration
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'caffeinate/action_mailer/extension'
4
- require 'caffeinate/action_mailer/helpers'
5
- require 'caffeinate/action_mailer/interceptor'
6
- require 'caffeinate/action_mailer/observer'
3
+ require 'mail'
4
+
5
+ # Includes all files in `caffeinate/action_mailer`
6
+ Dir["#{__dir__}/action_mailer/*"].each { |path| require "caffeinate/action_mailer/#{File.basename(path)}" }
@@ -2,21 +2,27 @@
2
2
 
3
3
  module Caffeinate
4
4
  module ActionMailer
5
+ # Convenience for setting `@mailing`, and convenience methods for inferred `caffeinate_unsubscribe_url` and
6
+ # `caffeinate_subscribe_url`.
5
7
  module Extension
6
8
  def self.included(klass)
7
9
  klass.before_action do
8
- @mailing = Thread.current[::Caffeinate::Mailing::CURRENT_THREAD_KEY] if Thread.current[::Caffeinate::Mailing::CURRENT_THREAD_KEY]
10
+ @mailing = params[:mailing] if params
9
11
  end
10
12
 
11
13
  klass.helper_method :caffeinate_unsubscribe_url, :caffeinate_subscribe_url
12
14
  end
13
15
 
14
- def caffeinate_unsubscribe_url(**options)
15
- Caffeinate::UrlHelpers.caffeinate_unsubscribe_url(@mailing.caffeinate_campaign_subscription, **options)
16
+ # Assumes `@mailing` is set
17
+ def caffeinate_unsubscribe_url(mailing: nil, **options)
18
+ mailing ||= @mailing
19
+ Caffeinate::UrlHelpers.caffeinate_unsubscribe_url(mailing.caffeinate_campaign_subscription, **options)
16
20
  end
17
21
 
18
- def caffeinate_subscribe_url
19
- Caffeinate::UrlHelpers.caffeinate_subscribe_url(@mailing.caffeinate_campaign_subscription, **options)
22
+ # Assumes `@mailing` is set
23
+ def caffeinate_subscribe_url(mailing: nil, **options)
24
+ mailing ||= @mailing
25
+ Caffeinate::UrlHelpers.caffeinate_subscribe_url(mailing.caffeinate_campaign_subscription, **options)
20
26
  end
21
27
  end
22
28
  end
@@ -2,13 +2,15 @@
2
2
 
3
3
  module Caffeinate
4
4
  module ActionMailer
5
+ # Handles the evaluation of a drip against a mailing to determine if it ultimately gets delivered.
6
+ # Also invokes the `before_send` callbacks.
5
7
  class Interceptor
6
8
  # Handles `before_send` callbacks for a `Caffeinate::Dripper`
7
9
  def self.delivering_email(message)
8
- mailing = Thread.current[::Caffeinate::Mailing::CURRENT_THREAD_KEY]
10
+ mailing = message.caffeinate_mailing
9
11
  return unless mailing
10
12
 
11
- mailing.caffeinate_campaign.to_dripper.run_callbacks(:before_send, mailing.caffeinate_campaign_subscription, mailing, message)
13
+ mailing.caffeinate_campaign.to_dripper.run_callbacks(:before_send, mailing, message)
12
14
  drip = mailing.drip
13
15
  message.perform_deliveries = drip.enabled?(mailing)
14
16
  end
@@ -2,15 +2,16 @@
2
2
 
3
3
  module Caffeinate
4
4
  module ActionMailer
5
- # Handles updating the Caffeinate::Message if it's available in Thread.current[::Caffeinate::Mailing::CURRENT_THREAD_KEY]
5
+ # Handles updating the Caffeinate::Message if it's available in Mail::Message.caffeinate_mailing
6
6
  # and runs any associated callbacks
7
7
  class Observer
8
8
  def self.delivered_email(message)
9
- mailing = Thread.current[::Caffeinate::Mailing::CURRENT_THREAD_KEY]
9
+ mailing = message.caffeinate_mailing
10
10
  return unless mailing
11
11
 
12
12
  mailing.update!(sent_at: Caffeinate.config.time_now, skipped_at: nil) if message.perform_deliveries
13
- mailing.caffeinate_campaign.to_dripper.run_callbacks(:after_send, mailing.caffeinate_campaign_subscription, mailing, message)
13
+ mailing.caffeinate_campaign.to_dripper.run_callbacks(:after_send, mailing, message)
14
+ true
14
15
  end
15
16
  end
16
17
  end
@@ -2,32 +2,38 @@
2
2
 
3
3
  module Caffeinate
4
4
  module ActiveRecord
5
+ # Includes the ActiveRecord association and relevant scopes for an ActiveRecord-backed model
5
6
  module Extension
6
7
  # Adds the associations for a subscriber
7
- def caffeinate_subscriber
8
- has_many :caffeinate_campaign_subscriptions, as: :subscriber, class_name: '::Caffeinate::CampaignSubscription'
8
+ def acts_as_caffeinate_subscriber
9
+ has_many :caffeinate_campaign_subscriptions, as: :subscriber, class_name: '::Caffeinate::CampaignSubscription', dependent: :destroy
9
10
  has_many :caffeinate_campaigns, through: :caffeinate_campaign_subscriptions, class_name: '::Caffeinate::Campaign'
10
11
  has_many :caffeinate_mailings, through: :caffeinate_campaign_subscriptions, class_name: '::Caffeinate::Mailing'
11
12
 
12
13
  scope :not_subscribed_to_campaign, lambda { |list|
13
- subscribed = ::Caffeinate::CampaignSubscription.select(:subscriber_id).joins(:caffeinate_campaign).where(caffeinate_campaigns: { slug: list }, subscriber_type: name)
14
- where.not(id: subscribed)
14
+ where.not(id: ::Caffeinate::CampaignSubscription
15
+ .select(:subscriber_id)
16
+ .joins(:caffeinate_campaign)
17
+ .where(subscriber_type: name, caffeinate_campaigns: { slug: list }))
15
18
  }
16
19
 
17
20
  scope :unsubscribed_from_campaign, lambda { |list|
18
- unsubscribed = ::Caffeinate::CampaignSubscription
19
- .unsubscribed
20
- .select(:subscriber_id)
21
- .joins(:caffeinate_campaign)
22
- .where(caffeinate_campaigns: { slug: list }, subscriber_type: name)
23
- where(id: unsubscribed)
21
+ where(id: ::Caffeinate::CampaignSubscription
22
+ .unsubscribed
23
+ .select(:subscriber_id)
24
+ .joins(:caffeinate_campaign)
25
+ .where(subscriber_type: name, caffeinate_campaigns: { slug: list }))
24
26
  }
25
27
  end
28
+ alias caffeinate_subscriber acts_as_caffeinate_subscriber
26
29
 
27
30
  # Adds the associations for a user
28
- def caffeinate_user
31
+ def acts_as_caffeinate_user
29
32
  has_many :caffeinate_campaign_subscriptions_as_user, as: :user, class_name: '::Caffeinate::CampaignSubscription'
33
+ has_many :caffeinate_campaigns_as_user, through: :caffeinate_campaign_subscriptions_as_user, class_name: '::Caffeinate::Campaign'
34
+ has_many :caffeinate_mailings_as_user, through: :caffeinate_campaign_subscriptions_as_user, class_name: '::Caffeinate::Campaign'
30
35
  end
36
+ alias caffeinate_user acts_as_caffeinate_user
31
37
  end
32
38
  end
33
39
  end
@@ -1,19 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Caffeinate
4
+ # Global configuration
4
5
  class Configuration
5
- attr_accessor :now, :async_delivery, :mailing_job
6
+ attr_accessor :now, :async_delivery, :mailing_job, :batch_size, :drippers_path, :implicit_campaigns
6
7
 
7
8
  def initialize
8
9
  @now = -> { Time.current }
9
10
  @async_delivery = false
10
11
  @mailing_job = nil
12
+ @batch_size = 1_000
13
+ @drippers_path = 'app/drippers'
14
+ @implicit_campaigns = true
11
15
  end
12
16
 
13
17
  def now=(val)
14
18
  raise ArgumentError, '#now must be a proc' unless val.respond_to?(:call)
15
19
 
16
- super
20
+ @now = val
21
+ end
22
+
23
+ # Automatically create a `::Caffeinate::Campaign` object if not found per `Dripper.inferred_campaign_slug`
24
+ def implicit_campaigns?
25
+ @implicit_campaigns == true
17
26
  end
18
27
 
19
28
  # The current time, for database calls
@@ -7,6 +7,7 @@ module Caffeinate
7
7
  # Handles the block and provides convenience methods for the drip
8
8
  class Drip
9
9
  attr_reader :dripper, :action, :options, :block
10
+
10
11
  def initialize(dripper, action, options, &block)
11
12
  @dripper = dripper
12
13
  @action = action
@@ -19,12 +20,24 @@ module Caffeinate
19
20
  options[:using] == :parameterized
20
21
  end
21
22
 
22
- def send_at
23
- options[:delay].from_now
23
+ def send_at(mailing = nil)
24
+ if periodical?
25
+ start = mailing.instance_exec(&options[:start])
26
+ start += options[:every] if mailing.caffeinate_campaign_subscription.caffeinate_mailings.count.positive?
27
+ start.from_now
28
+ else
29
+ options[:delay].from_now
30
+ end
31
+ end
32
+
33
+ def periodical?
34
+ options[:every].present?
24
35
  end
25
36
 
26
37
  # Checks if the drip is enabled
27
38
  def enabled?(mailing)
39
+ dripper.run_callbacks(:before_drip, self, mailing)
40
+
28
41
  DripEvaluator.new(mailing).call(&@block)
29
42
  end
30
43
  end
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Caffeinate
2
4
  # Handles evaluating the `drip` block and provides convenience methods for handling the mailing or its campaign.
3
5
  class DripEvaluator
4
6
  attr_reader :mailing
7
+
5
8
  def initialize(mailing)
6
9
  @mailing = mailing
7
10
  end
@@ -1,17 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'caffeinate/dripper/drip'
4
- require 'caffeinate/dripper/inferences'
3
+ require 'caffeinate/dripper/batching'
5
4
  require 'caffeinate/dripper/callbacks'
6
- require 'caffeinate/dripper/defaults'
7
- require 'caffeinate/dripper/subscriber'
8
5
  require 'caffeinate/dripper/campaign'
9
- require 'caffeinate/dripper/perform'
6
+ require 'caffeinate/dripper/defaults'
10
7
  require 'caffeinate/dripper/delivery'
8
+ require 'caffeinate/dripper/drip'
9
+ require 'caffeinate/dripper/inferences'
10
+ require 'caffeinate/dripper/perform'
11
+ require 'caffeinate/dripper/periodical'
12
+ require 'caffeinate/dripper/subscriber'
11
13
 
12
14
  module Caffeinate
13
15
  module Dripper
16
+ # Base class
14
17
  class Base
18
+ include Batching
15
19
  include Callbacks
16
20
  include Campaign
17
21
  include Defaults
@@ -19,7 +23,10 @@ module Caffeinate
19
23
  include Drip
20
24
  include Inferences
21
25
  include Perform
26
+ include Periodical
22
27
  include Subscriber
23
28
  end
24
29
  end
25
30
  end
31
+
32
+ ActiveSupport.run_load_hooks :caffeinate, Caffeinate::Dripper::Base
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caffeinate
4
+ module Dripper
5
+ # Includes batch support for setting the batch size for Perform
6
+ module Batching
7
+ def self.included(klass)
8
+ klass.extend ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+ def batch_size=(num)
13
+ @batch_size = num
14
+ end
15
+
16
+ def batch_size
17
+ @batch_size || ::Caffeinate.config.batch_size
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -2,12 +2,17 @@
2
2
 
3
3
  module Caffeinate
4
4
  module Dripper
5
+ # Callbacks for a Dripper.
5
6
  module Callbacks
6
7
  # :nodoc:
7
8
  def self.included(klass)
8
9
  klass.extend ClassMethods
9
10
  end
10
11
 
12
+ def run_callbacks(name, *args)
13
+ self.class.run_callbacks(name, *args)
14
+ end
15
+
11
16
  module ClassMethods
12
17
  # :nodoc:
13
18
  def run_callbacks(name, *args)
@@ -33,15 +38,80 @@ module Caffeinate
33
38
  @on_subscribe_blocks ||= []
34
39
  end
35
40
 
41
+ # Callback after a Caffeinate::CampaignSubscription is `#resubscribed!`
42
+ #
43
+ # on_resubscribe do |campaign_subscription|
44
+ # Slack.notify(:caffeinate, "Someone resubscribed to #{campaign_subscription.campaign.name}!")
45
+ # end
46
+ #
47
+ # @yield Caffeinate::CampaignSubscription
48
+ def on_resubscribe(&block)
49
+ on_resubscribe_blocks << block
50
+ end
51
+
52
+ # :nodoc:
53
+ def on_resubscribe_blocks
54
+ @on_resubscribe_blocks ||= []
55
+ end
56
+
57
+ # Callback before the mailings get processed.
58
+ #
59
+ # before_perform do |dripper|
60
+ # Slack.notify(:caffeinate, "Dripper is getting ready for mailing! #{dripper.caffeinate_campaign.name}!")
61
+ # end
62
+ #
63
+ # @yield Caffeinate::Dripper
64
+ def before_perform(&block)
65
+ before_perform_blocks << block
66
+ end
67
+
68
+ # :nodoc:
69
+ def before_perform_blocks
70
+ @before_perform_blocks ||= []
71
+ end
72
+
73
+ # Callback before the mailings get processed in a batch.
74
+ #
75
+ # after_process do |dripper, mailings|
76
+ # Slack.notify(:caffeinate, "Dripper #{dripper.name} sent #{mailings.size} mailings! Whoa!")
77
+ # end
78
+ #
79
+ # @yield Caffeinate::Dripper
80
+ # @yield Caffeinate::Mailing [Array]
81
+ def on_perform(&block)
82
+ on_perform_blocks << block
83
+ end
84
+
85
+ # :nodoc:
86
+ def on_perform_blocks
87
+ @on_perform_blocks ||= []
88
+ end
89
+
90
+ # Callback after the all the mailings have been sent.
91
+ #
92
+ # after_process do |dripper|
93
+ # Slack.notify(:caffeinate, "Dripper #{dripper.name} sent #{mailings.size} mailings! Whoa!")
94
+ # end
95
+ #
96
+ # @yield Caffeinate::Dripper
97
+ # @yield Caffeinate::Mailing [Array]
98
+ def after_perform(&block)
99
+ after_perform_blocks << block
100
+ end
101
+
102
+ # :nodoc:
103
+ def after_perform_blocks
104
+ @after_perform_blocks ||= []
105
+ end
106
+
36
107
  # Callback before a Drip has called the mailer.
37
108
  #
38
109
  # before_drip do |campaign_subscription, mailing, drip|
39
110
  # Slack.notify(:caffeinate, "#{drip.action_name} is starting")
40
111
  # end
41
112
  #
42
- # @yield Caffeinate::CampaignSubscription
43
- # @yield Caffeinate::Mailing
44
113
  # @yield Caffeinate::Drip current drip
114
+ # @yield Caffeinate::Mailing
45
115
  def before_drip(&block)
46
116
  before_drip_blocks << block
47
117
  end
@@ -57,7 +127,6 @@ module Caffeinate
57
127
  # Slack.notify(:caffeinate, "A new subscriber to #{campaign_subscription.campaign.name}!")
58
128
  # end
59
129
  #
60
- # @yield Caffeinate::CampaignSubscription
61
130
  # @yield Caffeinate::Mailing
62
131
  # @yield Mail::Message
63
132
  def before_send(&block)
@@ -75,7 +144,6 @@ module Caffeinate
75
144
  # Slack.notify(:caffeinate, "A new subscriber to #{campaign_subscription.campaign.name}!")
76
145
  # end
77
146
  #
78
- # @yield Caffeinate::CampaignSubscription
79
147
  # @yield Caffeinate::Mailing
80
148
  # @yield Mail::Message
81
149
  def after_send(&block)
@@ -119,14 +187,29 @@ module Caffeinate
119
187
  @on_unsubscribe_blocks ||= []
120
188
  end
121
189
 
190
+ # Callback after a CampaignSubscriber has ended.
191
+ #
192
+ # on_end do |campaign_sub|
193
+ # Slack.notify(:caffeinate, "#{campaign_sub.id} has ended... sad day.")
194
+ # end
195
+ #
196
+ # @yield Caffeinate::CampaignSubscription
197
+ def on_end(&block)
198
+ on_end_blocks << block
199
+ end
200
+
201
+ # :nodoc:
202
+ def on_end_blocks
203
+ @on_end_blocks ||= []
204
+ end
205
+
122
206
  # Callback after a `Caffeinate::Mailing` is skipped.
123
207
  #
124
208
  # on_skip do |campaign_subscription, mailing, message|
125
209
  # Slack.notify(:caffeinate, "#{campaign_sub.id} has unsubscribed... sad day.")
126
210
  # end
127
211
  #
128
- # @yield Caffeinate::CampaignSubscription
129
- # @yield Caffeinate::Mailing
212
+ # @yield `Caffeinate::Mailing`
130
213
  def on_skip(&block)
131
214
  on_skip_blocks << block
132
215
  end