caffeinate 0.9 → 0.14.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: fa3c41f5c5af2ed5a8733c8c5f8614a99c2df6cfa6549bce48b6c521aedfc647
4
- data.tar.gz: b8585a93022bbf70657f8d81485d9691b6a0c90192fb9b632da2a11fde45579b
3
+ metadata.gz: a8ae5ac5375188ce5a7bc8960ac7d7b392ccc7dc7de71070e88a2285fa76e7af
4
+ data.tar.gz: dd06d837c2c1477bf8e48484e028016f00203066f0b2383c6f0a7a3cb5b5af1f
5
5
  SHA512:
6
- metadata.gz: 1cd7ab9dced54ac5cdc6be24498253bf95230d78301d1fecad21c9e74e325ad4bb05eea4cf22e50214448c304a25068f879f12ea2e9af5a6ce59b41e28aab654
7
- data.tar.gz: d066b26a02543079a9a9571d71042216e3ccb69d8440b6efccb93fb47bf78e3367b58d681c7312c0c806b96d829edca1114f67951eb2c97f0c7aec9c818a38c3
6
+ metadata.gz: 5c71b3ba2196a9a9f7ae66074b8eeb76ad6fe1a6b2ecff521e7f92c06da411c4722e0bc8b4a49fcfc9eaf0fde921c76fd4b13fbb5c7b59cfe1ec173ea69e5b29
7
+ data.tar.gz: 498848318785a2f8f4ab669cada385760f63663b1c6bf254235a7f640c189eaa170391aff58990c05c6a4d8ee6a6dc860399ea24745431dec7dfebaa8d760533
@@ -28,9 +28,13 @@ module Caffeinate
28
28
 
29
29
  has_many :caffeinate_mailings, class_name: 'Caffeinate::Mailing', foreign_key: :caffeinate_campaign_subscription_id, dependent: :destroy
30
30
  has_many :mailings, class_name: 'Caffeinate::Mailing', foreign_key: :caffeinate_campaign_subscription_id, dependent: :destroy
31
+ has_many :future_mailings, -> { upcoming.unsent }, class_name: '::Caffeinate::Mailing', foreign_key: :caffeinate_campaign_subscription_id
31
32
 
32
- has_one :next_caffeinate_mailing, -> { upcoming.unsent.order(send_at: :asc) }, class_name: '::Caffeinate::Mailing', foreign_key: :caffeinate_campaign_subscription_id
33
- has_one :next_mailing, -> { upcoming.unsent.order(send_at: :asc) }, class_name: '::Caffeinate::Mailing', foreign_key: :caffeinate_campaign_subscription_id
33
+ has_one :next_caffeinate_mailing, -> { joins(:caffeinate_campaign_subscription).where(caffeinate_campaign_subscriptions: { ended_at: nil, unsubscribed_at: nil }).upcoming.unsent.order(send_at: :asc) }, class_name: '::Caffeinate::Mailing', foreign_key: :caffeinate_campaign_subscription_id
34
+ has_one :next_mailing, -> { joins(:caffeinate_campaign_subscription).where(caffeinate_campaign_subscriptions: { ended_at: nil, unsubscribed_at: nil }).upcoming.unsent.order(send_at: :asc) }, class_name: '::Caffeinate::Mailing', foreign_key: :caffeinate_campaign_subscription_id
35
+
36
+ has_one :previous_caffeinate_mailing, -> { sent.order(sent_at: :desc) }, class_name: '::Caffeinate::Mailing', foreign_key: :caffeinate_campaign_subscription_id
37
+ has_one :previous_mailing, -> { sent.order(sent_at: :desc) }, class_name: '::Caffeinate::Mailing', foreign_key: :caffeinate_campaign_subscription_id
34
38
 
35
39
  belongs_to :caffeinate_campaign, class_name: 'Caffeinate::Campaign', foreign_key: :caffeinate_campaign_id
36
40
  alias_attribute :campaign, :caffeinate_campaign
@@ -49,6 +53,8 @@ module Caffeinate
49
53
  before_validation :set_token!, on: [:create]
50
54
  validates :token, uniqueness: true, on: [:create]
51
55
 
56
+ before_validation :call_dripper_before_subscribe_blocks!, on: :create
57
+
52
58
  after_commit :create_mailings!, on: :create
53
59
 
54
60
  after_commit :on_complete, if: :completed?
@@ -74,7 +80,7 @@ module Caffeinate
74
80
  end
75
81
 
76
82
  # Updates `ended_at` and runs `on_complete` callbacks
77
- def end!(reason = nil)
83
+ def end!(reason = ::Caffeinate.config.default_ended_reason)
78
84
  raise ::Caffeinate::InvalidState, 'CampaignSubscription is already unsubscribed.' if unsubscribed?
79
85
 
80
86
  update!(ended_at: ::Caffeinate.config.time_now, ended_reason: reason)
@@ -84,7 +90,7 @@ module Caffeinate
84
90
  end
85
91
 
86
92
  # Updates `ended_at` and runs `on_complete` callbacks
87
- def end(reason = nil)
93
+ def end(reason = ::Caffeinate.config.default_ended_reason)
88
94
  return false if unsubscribed?
89
95
 
90
96
  result = update(ended_at: ::Caffeinate.config.time_now, ended_reason: reason)
@@ -94,7 +100,7 @@ module Caffeinate
94
100
  end
95
101
 
96
102
  # Updates `unsubscribed_at` and runs `on_subscribe` callbacks
97
- def unsubscribe!(reason = nil)
103
+ def unsubscribe!(reason = ::Caffeinate.config.default_unsubscribe_reason)
98
104
  raise ::Caffeinate::InvalidState, 'CampaignSubscription is already ended.' if ended?
99
105
 
100
106
  update!(unsubscribed_at: ::Caffeinate.config.time_now, unsubscribe_reason: reason)
@@ -104,7 +110,7 @@ module Caffeinate
104
110
  end
105
111
 
106
112
  # Updates `unsubscribed_at` and runs `on_subscribe` callbacks
107
- def unsubscribe(reason = nil)
113
+ def unsubscribe(reason = ::Caffeinate.config.default_unsubscribe_reason)
108
114
  return false if ended?
109
115
 
110
116
  result = update(unsubscribed_at: ::Caffeinate.config.time_now, unsubscribe_reason: reason)
@@ -143,6 +149,10 @@ module Caffeinate
143
149
 
144
150
  private
145
151
 
152
+ def call_dripper_before_subscribe_blocks!
153
+ caffeinate_campaign.to_dripper.run_callbacks(:before_subscribe, self)
154
+ end
155
+
146
156
  def on_complete
147
157
  caffeinate_campaign.to_dripper.run_callbacks(:on_complete, self)
148
158
  end
@@ -30,6 +30,8 @@ module Caffeinate
30
30
  scope :skipped, -> { where.not(skipped_at: nil) }
31
31
  scope :unskipped, -> { where(skipped_at: nil) }
32
32
 
33
+ after_touch :end_if_no_mailings!
34
+
33
35
  def initialize_dup(args)
34
36
  super
35
37
  self.send_at = nil
@@ -119,5 +121,9 @@ module Caffeinate
119
121
  raise NoMethodError, "Neither perform_later or perform_async are defined on #{klass}."
120
122
  end
121
123
  end
124
+
125
+ def end_if_no_mailings!
126
+ end! if future_mailings.empty?
127
+ end
122
128
  end
123
129
  end
@@ -5,7 +5,6 @@ module Caffeinate
5
5
  # Handles the evaluation of a drip against a mailing to determine if it ultimately gets delivered.
6
6
  # Also invokes the `before_send` callbacks.
7
7
  class Interceptor
8
- # Handles `before_send` callbacks for a `Caffeinate::Dripper`
9
8
  def self.delivering_email(message)
10
9
  mailing = message.caffeinate_mailing
11
10
  return unless mailing
@@ -3,15 +3,19 @@
3
3
  module Caffeinate
4
4
  # Global configuration
5
5
  class Configuration
6
- attr_accessor :now, :async_delivery, :mailing_job, :batch_size, :drippers_path, :implicit_campaigns
6
+ attr_accessor :now, :async_delivery, :deliver_later,:mailing_job, :batch_size, :drippers_path, :implicit_campaigns,
7
+ :default_ended_reason, :default_unsubscribe_reason
7
8
 
8
9
  def initialize
9
10
  @now = -> { Time.current }
10
11
  @async_delivery = false
12
+ @deliver_later = false
11
13
  @mailing_job = nil
12
14
  @batch_size = 1_000
13
15
  @drippers_path = 'app/drippers'
14
16
  @implicit_campaigns = true
17
+ @default_ended_reason = nil
18
+ @default_unsubscribe_reason = nil
15
19
  end
16
20
 
17
21
  def now=(val)
@@ -35,6 +39,11 @@ module Caffeinate
35
39
  @async_delivery
36
40
  end
37
41
 
42
+ # If we should use `#deliver_later` instead of `#deliver`
43
+ def deliver_later?
44
+ @deliver_later
45
+ end
46
+
38
47
  # The @mailing_job constantized. Only used if `async_delivery = true`
39
48
  def mailing_job_class
40
49
  @mailing_job.constantize
@@ -6,6 +6,26 @@ module Caffeinate
6
6
  #
7
7
  # Handles the block and provides convenience methods for the drip
8
8
  class Drip
9
+ class OptionEvaluator
10
+ def initialize(thing, drip, mailing)
11
+ @thing = thing
12
+ @drip = drip
13
+ @mailing = mailing
14
+ end
15
+
16
+ def call
17
+ if @thing.is_a?(Symbol)
18
+ @drip.dripper.new.send(@thing, @drip, @mailing)
19
+ elsif @thing.is_a?(Proc)
20
+ @mailing.instance_exec(&@thing)
21
+ elsif @thing.is_a?(String)
22
+ Time.parse(@thing)
23
+ else
24
+ @thing
25
+ end
26
+ end
27
+ end
28
+
9
29
  attr_reader :dripper, :action, :options, :block
10
30
 
11
31
  def initialize(dripper, action, options, &block)
@@ -26,13 +46,16 @@ module Caffeinate
26
46
  start += options[:every] if mailing.caffeinate_campaign_subscription.caffeinate_mailings.count.positive?
27
47
  date = start.from_now
28
48
  elsif options[:on]
29
- date = mailing.instance_exec(&options[:on])
49
+ date = OptionEvaluator.new(options[:on], self, mailing).call
30
50
  else
31
- date = options[:delay].from_now
51
+ date = OptionEvaluator.new(options[:delay], self, mailing).call
52
+ if date.respond_to?(:from_now)
53
+ date = date.from_now
54
+ end
32
55
  end
33
56
 
34
57
  if options[:at]
35
- time = Time.parse(options[:at])
58
+ time = OptionEvaluator.new(options[:at], self, mailing).call
36
59
  return date.change(hour: time.hour, min: time.min, sec: time.sec)
37
60
  end
38
61
 
@@ -44,10 +67,18 @@ module Caffeinate
44
67
  end
45
68
 
46
69
  # Checks if the drip is enabled
70
+ #
71
+ # This is kind of messy and could use some love.
72
+ # todo: better.
47
73
  def enabled?(mailing)
48
- dripper.run_callbacks(:before_drip, self, mailing)
49
-
50
- DripEvaluator.new(mailing).call(&@block)
74
+ catch(:abort) do
75
+ if dripper.run_callbacks(:before_drip, self, mailing)
76
+ return DripEvaluator.new(mailing).call(&@block)
77
+ else
78
+ return false
79
+ end
80
+ end
81
+ false
51
82
  end
52
83
  end
53
84
  end
@@ -11,9 +11,9 @@ module Caffeinate
11
11
 
12
12
  def call(&block)
13
13
  return true unless block
14
-
15
14
  catch(:abort) do
16
- return instance_eval(&block)
15
+ result = instance_eval(&block)
16
+ return result.nil? || result === true
17
17
  end
18
18
  false
19
19
  end
@@ -13,12 +13,33 @@ module Caffeinate
13
13
  self.class.run_callbacks(name, *args)
14
14
  end
15
15
 
16
+ def callbacks_for(name)
17
+ self.class.callbacks_for(name)
18
+ end
19
+
16
20
  module ClassMethods
17
21
  # :nodoc:
18
22
  def run_callbacks(name, *args)
19
- send("#{name}_blocks").each do |callback|
20
- callback.call(*args)
23
+ catch(:abort) do
24
+ callbacks_for(name).each do |callback|
25
+ callback.call(*args)
26
+ end
27
+ return true
21
28
  end
29
+ false
30
+ end
31
+
32
+ # :nodoc:
33
+ def callbacks_for(name)
34
+ send("#{name}_blocks")
35
+ end
36
+
37
+ def before_subscribe(&block)
38
+ before_subscribe_blocks << block
39
+ end
40
+
41
+ def before_subscribe_blocks
42
+ @before_subscribe_blocks ||= []
22
43
  end
23
44
 
24
45
  # Callback after a Caffeinate::CampaignSubscription is created, and after the Caffeinate::Mailings have
@@ -106,10 +127,19 @@ module Caffeinate
106
127
 
107
128
  # Callback before a Drip has called the mailer.
108
129
  #
109
- # before_drip do |campaign_subscription, mailing, drip|
130
+ # before_drip do |drip, mailing|
110
131
  # Slack.notify(:caffeinate, "#{drip.action_name} is starting")
111
132
  # end
112
133
  #
134
+ # Note: If you want to bail on the mailing for some reason, you need invoke `throw(:abort)`
135
+ #
136
+ # before_drip do |drip, mailing|
137
+ # if mailing.caffeinate_campaign_subscription.subscriber.trial_ended?
138
+ # unsubscribe!("Trial ended")
139
+ # throw(:abort)
140
+ # end
141
+ # end
142
+ #
113
143
  # @yield Caffeinate::Drip current drip
114
144
  # @yield Caffeinate::Mailing
115
145
  def before_drip(&block)
@@ -20,7 +20,12 @@ module Caffeinate
20
20
  mailing.mailer_class.constantize.send(mailing.mailer_action, mailing)
21
21
  end
22
22
  message.caffeinate_mailing = mailing
23
- message.deliver
23
+ if ::Caffeinate.config.deliver_later?
24
+ message.deliver_later
25
+ else
26
+ message.deliver
27
+ end
28
+
24
29
  end
25
30
  end
26
31
  end
@@ -16,11 +16,7 @@ module Caffeinate
16
16
  # @return nil
17
17
  def perform!
18
18
  run_callbacks(:before_perform, self)
19
- Caffeinate::Mailing
20
- .upcoming
21
- .unsent
22
- .joins(:caffeinate_campaign_subscription)
23
- .merge(Caffeinate::CampaignSubscription.active.where(caffeinate_campaign: self.campaign))
19
+ self.class.upcoming_mailings
24
20
  .in_batches(of: self.class.batch_size)
25
21
  .each do |batch|
26
22
  run_callbacks(:on_perform, self, batch)
@@ -35,6 +31,14 @@ module Caffeinate
35
31
  def perform!
36
32
  new.perform!
37
33
  end
34
+
35
+ def upcoming_mailings
36
+ Caffeinate::Mailing
37
+ .upcoming
38
+ .unsent
39
+ .joins(:caffeinate_campaign_subscription)
40
+ .merge(Caffeinate::CampaignSubscription.active.where(caffeinate_campaign: campaign))
41
+ end
38
42
  end
39
43
  end
40
44
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Caffeinate
4
- VERSION = '0.9'
4
+ VERSION = '0.14.0'
5
5
  end
@@ -5,7 +5,6 @@ module Caffeinate
5
5
  # Installs Caffeinate
6
6
  class InstallGenerator < Rails::Generators::Base
7
7
  source_root File.expand_path('templates', __dir__)
8
- include ::Rails::Generators::Migration
9
8
 
10
9
  desc 'Creates a Caffeinate initializer and copies migrations to your application.'
11
10
 
@@ -33,12 +32,21 @@ module Caffeinate
33
32
  @prev_migration_nr.to_s
34
33
  end
35
34
 
35
+ def migration_version
36
+ if rails5_and_up?
37
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
38
+ end
39
+ end
40
+
41
+ def rails5_and_up?
42
+ Rails::VERSION::MAJOR >= 5
43
+ end
44
+
36
45
  # :nodoc:
37
46
  def copy_migrations
38
- require 'rake'
39
- Rails.application.load_tasks
40
- Rake::Task['railties:install:migrations'].reenable
41
- Rake::Task['caffeinate:install:migrations'].invoke
47
+ template 'migrations/create_caffeinate_campaigns.rb', "db/migrate/#{self.class.next_migration_number("")}_create_caffeinate_campaigns.rb"
48
+ template 'migrations/create_caffeinate_campaign_subscriptions.rb', "db/migrate/#{self.class.next_migration_number("")}_create_caffeinate_campaign_subscriptions.rb"
49
+ template 'migrations/create_caffeinate_mailings.rb', "db/migrate/#{self.class.next_migration_number("")}_create_caffeinate_mailings.rb"
42
50
  end
43
51
  end
44
52
  end
@@ -41,4 +41,15 @@ Caffeinate.setup do |config|
41
41
  # config.implicit_campaigns = true
42
42
  #
43
43
  # config.implicit_campaigns = false
44
+ #
45
+ # == Default reasons
46
+ #
47
+ # The default unsubscribe and end reasons.
48
+ #
49
+ # Default:
50
+ # config.default_unsubscribe_reason = nil
51
+ # config.default_ended_reason = nil
52
+ #
53
+ # config.default_unsubscribe_reason = "User unsubscribed"
54
+ # config.default_ended_reason = "User ended"
44
55
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class CreateCaffeinateCampaignSubscriptions < ActiveRecord::Migration[6.0]
3
+ class CreateCaffeinateCampaignSubscriptions < ActiveRecord::Migration<%= migration_version %>
4
4
  def change
5
5
  drop_table :caffeinate_campaign_subscriptions if table_exists?(:caffeinate_campaign_subscriptions)
6
6
 
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class CreateCaffeinateCampaigns < ActiveRecord::Migration[6.0]
3
+ class CreateCaffeinateCampaigns < ActiveRecord::Migration<%= migration_version %>
4
4
  def change
5
- drop_table :caffeinate_campaigns if table_exists?(:caffeinate_campaigns)
6
5
  create_table :caffeinate_campaigns do |t|
7
6
  t.string :name, null: false
8
7
  t.string :slug, null: false
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class CreateCaffeinateMailings < ActiveRecord::Migration[6.0]
3
+ class CreateCaffeinateMailings < ActiveRecord::Migration<%= migration_version %>
4
4
  def change
5
- drop_table :caffeinate_mailings if table_exists?(:caffeinate_mailings)
6
-
7
5
  create_table :caffeinate_mailings do |t|
8
6
  t.references :caffeinate_campaign_subscription, null: false, foreign_key: true, index: { name: 'index_caffeinate_mailings_on_campaign_subscription' }
9
7
  t.datetime :send_at
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: caffeinate
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.9'
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Brody
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-24 00:00:00.000000000 Z
11
+ date: 2021-01-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -143,9 +143,6 @@ files:
143
143
  - app/views/layouts/_caffeinate.html.erb
144
144
  - config/locales/en.yml
145
145
  - config/routes.rb
146
- - db/migrate/20201124183102_create_caffeinate_campaigns.rb
147
- - db/migrate/20201124183303_create_caffeinate_campaign_subscriptions.rb
148
- - db/migrate/20201124183419_create_caffeinate_mailings.rb
149
146
  - lib/caffeinate.rb
150
147
  - lib/caffeinate/action_mailer.rb
151
148
  - lib/caffeinate/action_mailer/extension.rb
@@ -179,6 +176,9 @@ files:
179
176
  - lib/generators/caffeinate/templates/application_dripper.rb
180
177
  - lib/generators/caffeinate/templates/caffeinate.rb
181
178
  - lib/generators/caffeinate/templates/mailer.rb.tt
179
+ - lib/generators/caffeinate/templates/migrations/create_caffeinate_campaign_subscriptions.rb.tt
180
+ - lib/generators/caffeinate/templates/migrations/create_caffeinate_campaigns.rb.tt
181
+ - lib/generators/caffeinate/templates/migrations/create_caffeinate_mailings.rb.tt
182
182
  - lib/generators/caffeinate/views_generator.rb
183
183
  homepage: https://github.com/joshmn/caffeinate
184
184
  licenses: