caffeinate 0.2.1 → 0.7.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 +4 -4
- data/README.md +142 -70
- data/app/controllers/caffeinate/campaign_subscriptions_controller.rb +3 -3
- data/app/models/caffeinate/application_record.rb +0 -1
- data/app/models/caffeinate/campaign.rb +25 -7
- data/app/models/caffeinate/campaign_subscription.rb +44 -14
- data/app/models/caffeinate/mailing.rb +14 -6
- data/app/views/layouts/{caffeinate.html.erb → _caffeinate.html.erb} +0 -0
- data/db/migrate/20201124183102_create_caffeinate_campaigns.rb +1 -0
- data/db/migrate/20201124183303_create_caffeinate_campaign_subscriptions.rb +5 -3
- data/db/migrate/20201124183419_create_caffeinate_mailings.rb +2 -1
- data/lib/caffeinate.rb +4 -18
- data/lib/caffeinate/action_mailer/extension.rb +1 -1
- data/lib/caffeinate/action_mailer/interceptor.rb +2 -2
- data/lib/caffeinate/action_mailer/observer.rb +4 -3
- data/lib/caffeinate/active_record/extension.rb +15 -10
- data/lib/caffeinate/configuration.rb +9 -2
- data/lib/caffeinate/drip.rb +22 -2
- data/lib/caffeinate/drip_evaluator.rb +1 -0
- data/lib/caffeinate/dripper/base.rb +4 -0
- data/lib/caffeinate/dripper/batching.rb +13 -11
- data/lib/caffeinate/dripper/callbacks.rb +46 -18
- data/lib/caffeinate/dripper/campaign.rb +18 -4
- data/lib/caffeinate/dripper/defaults.rb +1 -0
- data/lib/caffeinate/dripper/delivery.rb +7 -7
- data/lib/caffeinate/dripper/drip.rb +2 -41
- data/lib/caffeinate/dripper/drip_collection.rb +62 -0
- data/lib/caffeinate/dripper/inferences.rb +3 -1
- data/lib/caffeinate/dripper/perform.rb +12 -10
- data/lib/caffeinate/dripper/periodical.rb +26 -0
- data/lib/caffeinate/dripper/subscriber.rb +14 -2
- data/lib/caffeinate/dripper_collection.rb +17 -0
- data/lib/caffeinate/engine.rb +9 -2
- data/lib/caffeinate/mail_ext.rb +12 -0
- data/lib/caffeinate/version.rb +1 -1
- data/lib/generators/caffeinate/install_generator.rb +1 -1
- data/lib/generators/caffeinate/templates/caffeinate.rb +10 -0
- metadata +21 -3
@@ -28,18 +28,32 @@ module Caffeinate
|
|
28
28
|
# self.name.delete_suffix("Campaign").underscore
|
29
29
|
#
|
30
30
|
# @param [Symbol] slug The slug of a persisted `Caffeinate::Campaign`.
|
31
|
-
def campaign(slug)
|
31
|
+
def campaign=(slug)
|
32
32
|
@caffeinate_campaign = nil
|
33
33
|
@_campaign_slug = slug.to_sym
|
34
|
-
Caffeinate.
|
34
|
+
Caffeinate.dripper_collection.register(@_campaign_slug, name)
|
35
35
|
end
|
36
36
|
|
37
|
-
# Returns the `Caffeinate::Campaign` object for the Dripper
|
37
|
+
# Returns the `Caffeinate::Campaign` object for the Dripper.
|
38
|
+
#
|
39
|
+
# If `config.implicit_campaigns` is true, this will automatically create a `Caffeinate::Campaign` if one is not
|
40
|
+
# found via the `campaign_slug`.
|
38
41
|
def caffeinate_campaign
|
39
42
|
return @caffeinate_campaign if @caffeinate_campaign.present?
|
40
43
|
|
41
|
-
|
44
|
+
if ::Caffeinate.config.implicit_campaigns?
|
45
|
+
@caffeinate_campaign = ::Caffeinate::Campaign.find_or_initialize_by(slug: campaign_slug)
|
46
|
+
if @caffeinate_campaign.new_record?
|
47
|
+
@caffeinate_campaign.name = "#{name.delete_suffix('Dripper').titleize} Campaign"
|
48
|
+
@caffeinate_campaign.save!
|
49
|
+
end
|
50
|
+
else
|
51
|
+
@caffeinate_campaign = ::Caffeinate::Campaign[campaign_slug]
|
52
|
+
end
|
53
|
+
|
54
|
+
@caffeinate_campaign
|
42
55
|
end
|
56
|
+
alias campaign caffeinate_campaign
|
43
57
|
|
44
58
|
# The defined slug or the inferred slug
|
45
59
|
def campaign_slug
|
@@ -24,6 +24,7 @@ module Caffeinate
|
|
24
24
|
# @param [Hash] options The options to set defaults with
|
25
25
|
# @option options [String] :mailer_class The mailer class
|
26
26
|
def default(options = {})
|
27
|
+
options.symbolize_keys!
|
27
28
|
options.assert_valid_keys(:mailer_class, :mailer, :using, :batch_size)
|
28
29
|
@defaults = options
|
29
30
|
end
|
@@ -14,13 +14,13 @@ module Caffeinate
|
|
14
14
|
#
|
15
15
|
# @param [Caffeinate::Mailing] mailing The mailing to deliver
|
16
16
|
def deliver!(mailing)
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
17
|
+
message = if mailing.drip.parameterized?
|
18
|
+
mailing.mailer_class.constantize.with(mailing: mailing).send(mailing.mailer_action)
|
19
|
+
else
|
20
|
+
mailing.mailer_class.constantize.send(mailing.mailer_action, mailing)
|
21
|
+
end
|
22
|
+
message.caffeinate_mailing = mailing
|
23
|
+
message.deliver
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
@@ -1,40 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'caffeinate/dripper/drip_collection'
|
3
4
|
module Caffeinate
|
4
5
|
module Dripper
|
5
6
|
# The Drip DSL for registering a drip.
|
6
7
|
module Drip
|
7
|
-
# A collection of Drip objects for a `Caffeinate::Dripper`
|
8
|
-
class DripCollection
|
9
|
-
include Enumerable
|
10
|
-
|
11
|
-
def initialize(dripper)
|
12
|
-
@dripper = dripper
|
13
|
-
@drips = {}
|
14
|
-
end
|
15
|
-
|
16
|
-
# Register the drip
|
17
|
-
def register(action, options, &block)
|
18
|
-
@drips[action.to_s] = ::Caffeinate::Drip.new(@dripper, action, options, &block)
|
19
|
-
end
|
20
|
-
|
21
|
-
def each(&block)
|
22
|
-
@drips.each { |action_name, drip| block.call(action_name, drip) }
|
23
|
-
end
|
24
|
-
|
25
|
-
def values
|
26
|
-
@drips.values
|
27
|
-
end
|
28
|
-
|
29
|
-
def size
|
30
|
-
@drips.size
|
31
|
-
end
|
32
|
-
|
33
|
-
def [](val)
|
34
|
-
@drips[val]
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
8
|
# :nodoc:
|
39
9
|
def self.included(klass)
|
40
10
|
klass.extend ClassMethods
|
@@ -60,17 +30,8 @@ module Caffeinate
|
|
60
30
|
# @option options [String] :mailer_class The mailer_class
|
61
31
|
# @option options [Integer] :step The order in which the drip is executed
|
62
32
|
# @option options [ActiveSupport::Duration] :delay When the drip should be ran
|
33
|
+
# @option options [Symbol] :using set to :parameters if the mailer action uses ActionMailer::Parameters
|
63
34
|
def drip(action_name, options = {}, &block)
|
64
|
-
options.assert_valid_keys(:mailer_class, :step, :delay, :using, :mailer)
|
65
|
-
options[:mailer_class] ||= options[:mailer] || defaults[:mailer_class]
|
66
|
-
options[:using] ||= defaults[:using]
|
67
|
-
options[:step] ||= drips.size + 1
|
68
|
-
|
69
|
-
if options[:mailer_class].nil?
|
70
|
-
raise ArgumentError, "You must define :mailer_class or :mailer in the options for :#{action_name}"
|
71
|
-
end
|
72
|
-
raise ArgumentError, "You must define :delay in the options for :#{action_name}" if options[:delay].nil?
|
73
|
-
|
74
35
|
drip_collection.register(action_name, options, &block)
|
75
36
|
end
|
76
37
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Caffeinate
|
4
|
+
module Dripper
|
5
|
+
# A collection of Drip objects for a `Caffeinate::Dripper`
|
6
|
+
class DripCollection
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
def initialize(dripper)
|
10
|
+
@dripper = dripper
|
11
|
+
@drips = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def for(action)
|
15
|
+
@drips[action.to_sym]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Register the drip
|
19
|
+
def register(action, options, &block)
|
20
|
+
options = validate_drip_options(action, options)
|
21
|
+
|
22
|
+
@drips[action.to_sym] = ::Caffeinate::Drip.new(@dripper, action, options, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def each(&block)
|
26
|
+
@drips.each { |action_name, drip| block.call(action_name, drip) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def values
|
30
|
+
@drips.values
|
31
|
+
end
|
32
|
+
|
33
|
+
def size
|
34
|
+
@drips.size
|
35
|
+
end
|
36
|
+
|
37
|
+
def [](val)
|
38
|
+
@drips[val]
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def validate_drip_options(action, options)
|
44
|
+
options.symbolize_keys!
|
45
|
+
options.assert_valid_keys(:mailer_class, :step, :delay, :every, :start, :using, :mailer, :at)
|
46
|
+
options[:mailer_class] ||= options[:mailer] || @dripper.defaults[:mailer_class]
|
47
|
+
options[:using] ||= @dripper.defaults[:using]
|
48
|
+
options[:step] ||= @dripper.drips.size + 1
|
49
|
+
|
50
|
+
if options[:mailer_class].nil?
|
51
|
+
raise ArgumentError, "You must define :mailer_class or :mailer in the options for #{action.inspect} on #{@dripper.inspect}"
|
52
|
+
end
|
53
|
+
|
54
|
+
if options[:every].nil? && options[:delay].nil?
|
55
|
+
raise ArgumentError, "You must define :delay in the options for #{action.inspect} on #{@dripper.inspect}"
|
56
|
+
end
|
57
|
+
|
58
|
+
options
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -9,22 +9,24 @@ module Caffeinate
|
|
9
9
|
klass.extend ClassMethods
|
10
10
|
end
|
11
11
|
|
12
|
-
# Delivers the next_caffeinate_mailer for the campaign's subscribers.
|
13
|
-
#
|
14
|
-
# Handles with batches based on batch_size.
|
12
|
+
# Delivers the next_caffeinate_mailer for the campaign's subscribers. Handles with batches based on `batch_size`.
|
15
13
|
#
|
16
14
|
# OrderDripper.new.perform!
|
17
15
|
#
|
18
16
|
# @return nil
|
19
17
|
def perform!
|
20
|
-
run_callbacks(:
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
18
|
+
run_callbacks(:before_perform, self)
|
19
|
+
Caffeinate::Mailing
|
20
|
+
.upcoming
|
21
|
+
.unsent
|
22
|
+
.joins(:caffeinate_campaign_subscription)
|
23
|
+
.merge(Caffeinate::CampaignSubscription.active)
|
24
|
+
.in_batches(of: self.class.batch_size)
|
25
|
+
.each do |batch|
|
26
|
+
run_callbacks(:on_perform, self, batch)
|
27
|
+
batch.each(&:process!)
|
26
28
|
end
|
27
|
-
run_callbacks(:
|
29
|
+
run_callbacks(:after_perform, self)
|
28
30
|
nil
|
29
31
|
end
|
30
32
|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Caffeinate
|
4
|
+
module Dripper
|
5
|
+
module Periodical
|
6
|
+
def self.included(klass)
|
7
|
+
klass.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def periodical(action_name, every:, start: -> { ::Caffeinate.config.time_now }, **options, &block)
|
12
|
+
options[:start] = start
|
13
|
+
options[:every] = every
|
14
|
+
drip(action_name, options, &block)
|
15
|
+
after_send do |mailing, _message|
|
16
|
+
if mailing.drip.action == action_name
|
17
|
+
next_mailing = mailing.dup
|
18
|
+
next_mailing.send_at = mailing.drip.send_at(mailing)
|
19
|
+
next_mailing.save!
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -18,8 +18,8 @@ module Caffeinate
|
|
18
18
|
end
|
19
19
|
|
20
20
|
# Returns the campaign's `Caffeinate::CampaignSubscriber`
|
21
|
-
def
|
22
|
-
caffeinate_campaign.
|
21
|
+
def subscriptions
|
22
|
+
caffeinate_campaign.caffeinate_campaign_subscriptions
|
23
23
|
end
|
24
24
|
|
25
25
|
# Subscribes to the campaign.
|
@@ -34,6 +34,18 @@ module Caffeinate
|
|
34
34
|
caffeinate_campaign.subscribe(subscriber, **args)
|
35
35
|
end
|
36
36
|
|
37
|
+
# Unsubscribes from the campaign.
|
38
|
+
#
|
39
|
+
# OrderDripper.unsubscribe(order, user: order.user)
|
40
|
+
#
|
41
|
+
# @param [ActiveRecord::Base] subscriber The object subscribing
|
42
|
+
# @option [ActiveRecord::Base] :user The associated user (optional)
|
43
|
+
#
|
44
|
+
# @return [Caffeinate::CampaignSubscriber] the CampaignSubscriber
|
45
|
+
def unsubscribe(subscriber, **args)
|
46
|
+
caffeinate_campaign.unsubscribe(subscriber, **args)
|
47
|
+
end
|
48
|
+
|
37
49
|
# :nodoc:
|
38
50
|
def subscribes_block
|
39
51
|
raise(NotImplementedError, 'Define subscribes') unless @subscribes_block
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Caffeinate
|
4
|
+
class DripperCollection
|
5
|
+
def initialize
|
6
|
+
@registry = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def register(name, klass)
|
10
|
+
@registry[name.to_sym] = klass
|
11
|
+
end
|
12
|
+
|
13
|
+
def resolve(campaign)
|
14
|
+
@registry[campaign.slug.to_sym].constantize
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/caffeinate/engine.rb
CHANGED
@@ -8,7 +8,14 @@ module Caffeinate
|
|
8
8
|
# Adds Caffeinate to Rails
|
9
9
|
class Engine < ::Rails::Engine
|
10
10
|
isolate_namespace Caffeinate
|
11
|
-
|
11
|
+
|
12
|
+
# :nocov:
|
13
|
+
config.to_prepare do
|
14
|
+
Dir.glob(Rails.root.join(Caffeinate.config.drippers_path, '**', '*.rb')).sort.each do |dripper|
|
15
|
+
require dripper
|
16
|
+
end
|
17
|
+
end
|
18
|
+
# :nocov:
|
12
19
|
|
13
20
|
ActiveSupport.on_load(:action_mailer) do
|
14
21
|
include ::Caffeinate::ActionMailer::Extension
|
@@ -21,7 +28,7 @@ module Caffeinate
|
|
21
28
|
end
|
22
29
|
|
23
30
|
ActiveSupport.on_load(:action_view) do
|
24
|
-
|
31
|
+
ActionView::Base.include ::Caffeinate::Helpers
|
25
32
|
end
|
26
33
|
end
|
27
34
|
end
|
data/lib/caffeinate/version.rb
CHANGED
@@ -20,7 +20,7 @@ module Caffeinate
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def install_routes
|
23
|
-
inject_into_file 'config/routes.rb', "\n mount ::Caffeinate::Engine => '/caffeinate", after: /Rails.application.routes.draw do/
|
23
|
+
inject_into_file 'config/routes.rb', "\n mount ::Caffeinate::Engine => '/caffeinate'", after: /Rails.application.routes.draw do/
|
24
24
|
end
|
25
25
|
|
26
26
|
# :nodoc:
|
@@ -31,4 +31,14 @@ Caffeinate.setup do |config|
|
|
31
31
|
# config.batch_size = 1_000
|
32
32
|
#
|
33
33
|
# config.batch_size = 100
|
34
|
+
#
|
35
|
+
# == Implicit Campaigns
|
36
|
+
#
|
37
|
+
# Instead of manually having to create a Campaign, you can let Caffeinate do a `find_or_create_by` at runtime.
|
38
|
+
# This is probably dangerous but it hasn't burned me yet so here you go:
|
39
|
+
#
|
40
|
+
# Default:
|
41
|
+
# config.implicit_campaigns = true
|
42
|
+
#
|
43
|
+
# config.implicit_campaigns = false
|
34
44
|
end
|
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.
|
4
|
+
version: 0.7.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-
|
11
|
+
date: 2020-12-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: simplecov
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: sqlite3
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -126,7 +140,7 @@ files:
|
|
126
140
|
- app/models/caffeinate/mailing.rb
|
127
141
|
- app/views/caffeinate/campaign_subscriptions/subscribe.html.erb
|
128
142
|
- app/views/caffeinate/campaign_subscriptions/unsubscribe.html.erb
|
129
|
-
- app/views/layouts/
|
143
|
+
- app/views/layouts/_caffeinate.html.erb
|
130
144
|
- config/locales/en.yml
|
131
145
|
- config/routes.rb
|
132
146
|
- db/migrate/20201124183102_create_caffeinate_campaigns.rb
|
@@ -149,11 +163,15 @@ files:
|
|
149
163
|
- lib/caffeinate/dripper/defaults.rb
|
150
164
|
- lib/caffeinate/dripper/delivery.rb
|
151
165
|
- lib/caffeinate/dripper/drip.rb
|
166
|
+
- lib/caffeinate/dripper/drip_collection.rb
|
152
167
|
- lib/caffeinate/dripper/inferences.rb
|
153
168
|
- lib/caffeinate/dripper/perform.rb
|
169
|
+
- lib/caffeinate/dripper/periodical.rb
|
154
170
|
- lib/caffeinate/dripper/subscriber.rb
|
171
|
+
- lib/caffeinate/dripper_collection.rb
|
155
172
|
- lib/caffeinate/engine.rb
|
156
173
|
- lib/caffeinate/helpers.rb
|
174
|
+
- lib/caffeinate/mail_ext.rb
|
157
175
|
- lib/caffeinate/url_helpers.rb
|
158
176
|
- lib/caffeinate/version.rb
|
159
177
|
- lib/generators/caffeinate/install_generator.rb
|