caffeinate 0.2.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +142 -70
  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 +25 -7
  6. data/app/models/caffeinate/campaign_subscription.rb +44 -14
  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 +5 -3
  11. data/db/migrate/20201124183419_create_caffeinate_mailings.rb +2 -1
  12. data/lib/caffeinate.rb +4 -18
  13. data/lib/caffeinate/action_mailer/extension.rb +1 -1
  14. data/lib/caffeinate/action_mailer/interceptor.rb +2 -2
  15. data/lib/caffeinate/action_mailer/observer.rb +4 -3
  16. data/lib/caffeinate/active_record/extension.rb +15 -10
  17. data/lib/caffeinate/configuration.rb +9 -2
  18. data/lib/caffeinate/drip.rb +22 -2
  19. data/lib/caffeinate/drip_evaluator.rb +1 -0
  20. data/lib/caffeinate/dripper/base.rb +4 -0
  21. data/lib/caffeinate/dripper/batching.rb +13 -11
  22. data/lib/caffeinate/dripper/callbacks.rb +46 -18
  23. data/lib/caffeinate/dripper/campaign.rb +18 -4
  24. data/lib/caffeinate/dripper/defaults.rb +1 -0
  25. data/lib/caffeinate/dripper/delivery.rb +7 -7
  26. data/lib/caffeinate/dripper/drip.rb +2 -41
  27. data/lib/caffeinate/dripper/drip_collection.rb +62 -0
  28. data/lib/caffeinate/dripper/inferences.rb +3 -1
  29. data/lib/caffeinate/dripper/perform.rb +12 -10
  30. data/lib/caffeinate/dripper/periodical.rb +26 -0
  31. data/lib/caffeinate/dripper/subscriber.rb +14 -2
  32. data/lib/caffeinate/dripper_collection.rb +17 -0
  33. data/lib/caffeinate/engine.rb +9 -2
  34. data/lib/caffeinate/mail_ext.rb +12 -0
  35. data/lib/caffeinate/version.rb +1 -1
  36. data/lib/generators/caffeinate/install_generator.rb +1 -1
  37. data/lib/generators/caffeinate/templates/caffeinate.rb +10 -0
  38. 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.register_dripper(@_campaign_slug, name)
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
- @caffeinate_campaign = ::Caffeinate::Campaign[campaign_slug]
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
- Caffeinate.current_mailing = mailing
18
-
19
- if mailing.drip.parameterized?
20
- mailing.mailer_class.constantize.with(mailing: mailing).send(mailing.mailer_action).deliver
21
- else
22
- mailing.mailer_class.constantize.send(mailing.mailer_action, mailing).deliver
23
- end
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
@@ -19,7 +19,9 @@ module Caffeinate
19
19
  nil
20
20
  end
21
21
 
22
- # The inferred mailer class
22
+ # The inferred campaign slug
23
+ #
24
+ # MyCoolDripper => my_cool
23
25
  def inferred_campaign_slug
24
26
  name.delete_suffix('Dripper').to_s.underscore
25
27
  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(:before_process, self)
21
- campaign.caffeinate_campaign_subscriptions.active.in_batches(of: self.class._batch_size).each do |batch|
22
- run_callbacks(:on_process, self, batch)
23
- batch.each do |subscriber|
24
- subscriber.next_caffeinate_mailing&.process!
25
- end
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(:after_process, self)
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 subscribers
22
- caffeinate_campaign.caffeinate_campaign_subscribers
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
@@ -8,7 +8,14 @@ module Caffeinate
8
8
  # Adds Caffeinate to Rails
9
9
  class Engine < ::Rails::Engine
10
10
  isolate_namespace Caffeinate
11
- config.eager_load_namespaces << Caffeinate
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
- ApplicationHelper.include ::Caffeinate::Helpers
31
+ ActionView::Base.include ::Caffeinate::Helpers
25
32
  end
26
33
  end
27
34
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mail
4
+ # Extend Mail::Message to account for a Caffeinate::Mailing
5
+ class Message
6
+ attr_accessor :caffeinate_mailing
7
+
8
+ def caffeinate?
9
+ caffeinate_mailing.present?
10
+ end
11
+ end
12
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Caffeinate
4
- VERSION = '0.2.1'
4
+ VERSION = '0.7.0'
5
5
  end
@@ -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.2.1
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-28 00:00:00.000000000 Z
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/caffeinate.html.erb
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