caffeinate 0.1.2 → 0.4.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +58 -43
  3. data/app/controllers/caffeinate/campaign_subscriptions_controller.rb +17 -2
  4. data/app/models/caffeinate/campaign.rb +40 -1
  5. data/app/models/caffeinate/campaign_subscription.rb +34 -10
  6. data/app/models/caffeinate/mailing.rb +26 -3
  7. data/app/views/caffeinate/campaign_subscriptions/subscribe.html.erb +3 -0
  8. data/app/views/caffeinate/campaign_subscriptions/unsubscribe.html.erb +3 -0
  9. data/app/views/layouts/_caffeinate.html.erb +11 -0
  10. data/config/locales/en.yml +6 -0
  11. data/db/migrate/20201124183102_create_caffeinate_campaigns.rb +1 -0
  12. data/db/migrate/20201124183303_create_caffeinate_campaign_subscriptions.rb +3 -0
  13. data/db/migrate/20201124183419_create_caffeinate_mailings.rb +4 -1
  14. data/lib/caffeinate.rb +2 -0
  15. data/lib/caffeinate/action_mailer.rb +4 -4
  16. data/lib/caffeinate/action_mailer/extension.rb +17 -1
  17. data/lib/caffeinate/action_mailer/interceptor.rb +4 -2
  18. data/lib/caffeinate/action_mailer/observer.rb +4 -4
  19. data/lib/caffeinate/active_record/extension.rb +3 -2
  20. data/lib/caffeinate/configuration.rb +4 -1
  21. data/lib/caffeinate/drip.rb +22 -35
  22. data/lib/caffeinate/drip_evaluator.rb +35 -0
  23. data/lib/caffeinate/dripper/base.rb +13 -14
  24. data/lib/caffeinate/dripper/batching.rb +22 -0
  25. data/lib/caffeinate/dripper/callbacks.rb +57 -6
  26. data/lib/caffeinate/dripper/campaign.rb +8 -9
  27. data/lib/caffeinate/dripper/defaults.rb +4 -2
  28. data/lib/caffeinate/dripper/delivery.rb +8 -8
  29. data/lib/caffeinate/dripper/drip.rb +46 -15
  30. data/lib/caffeinate/dripper/inferences.rb +29 -0
  31. data/lib/caffeinate/dripper/perform.rb +24 -5
  32. data/lib/caffeinate/dripper/periodical.rb +24 -0
  33. data/lib/caffeinate/dripper/subscriber.rb +2 -2
  34. data/lib/caffeinate/engine.rb +13 -1
  35. data/lib/caffeinate/helpers.rb +24 -0
  36. data/lib/caffeinate/mail_ext.rb +12 -0
  37. data/lib/caffeinate/url_helpers.rb +10 -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 +11 -1
  41. metadata +13 -5
  42. data/app/views/layouts/caffeinate/application.html.erb +0 -15
  43. data/app/views/layouts/caffeinate/campaign_subscriptions/unsubscribe.html.erb +0 -1
  44. data/lib/caffeinate/action_mailer/helpers.rb +0 -12
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Caffeinate
4
4
  module Dripper
5
+ # Handles the default DSL for a `Caffeinate::Dripper`.
5
6
  module Defaults
6
7
  # :nodoc:
7
8
  def self.included(klass)
@@ -11,7 +12,7 @@ module Caffeinate
11
12
  module ClassMethods
12
13
  # The defaults set in the Campaign
13
14
  def defaults
14
- @defaults ||= { mailer_class: inferred_mailer_class }
15
+ @defaults ||= { mailer_class: inferred_mailer_class, batch_size: ::Caffeinate.config.batch_size }
15
16
  end
16
17
 
17
18
  # The default options for the Campaign
@@ -23,7 +24,8 @@ module Caffeinate
23
24
  # @param [Hash] options The options to set defaults with
24
25
  # @option options [String] :mailer_class The mailer class
25
26
  def default(options = {})
26
- options.assert_valid_keys(:mailer_class, :mailer)
27
+ options.symbolize_keys!
28
+ options.assert_valid_keys(:mailer_class, :mailer, :using, :batch_size)
27
29
  @defaults = options
28
30
  end
29
31
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Caffeinate
4
4
  module Dripper
5
- # Handles delivery of a Caffeinate::Mailer for a Caffeinate::Dripper
5
+ # Handles delivery of a `Caffeinate::Mailer` for a `Caffeinate::Dripper`.
6
6
  module Delivery
7
7
  # :nodoc:
8
8
  def self.included(klass)
@@ -14,13 +14,13 @@ module Caffeinate
14
14
  #
15
15
  # @param [Caffeinate::Mailing] mailing The mailing to deliver
16
16
  def deliver!(mailing)
17
- Thread.current[:current_caffeinate_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
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Caffeinate
4
4
  module Dripper
5
- # The Drip DSL for registering a drip
5
+ # The Drip DSL for registering a drip.
6
6
  module Drip
7
7
  # A collection of Drip objects for a `Caffeinate::Dripper`
8
8
  class DripCollection
@@ -10,21 +10,55 @@ module Caffeinate
10
10
 
11
11
  def initialize(dripper)
12
12
  @dripper = dripper
13
- @drips = []
13
+ @drips = {}
14
+ end
15
+
16
+ def for(action)
17
+ @drips[action.to_sym]
14
18
  end
15
19
 
16
20
  # Register the drip
17
21
  def register(action, options, &block)
18
- @drips << ::Caffeinate::Drip.new(@dripper, action, options, &block)
22
+ options = validate_drip_options(action, options)
23
+
24
+ @drips[action.to_sym] = ::Caffeinate::Drip.new(@dripper, action, options, &block)
19
25
  end
20
26
 
21
27
  def each(&block)
22
- @drips.each { |drip| block.call(drip) }
28
+ @drips.each { |action_name, drip| block.call(action_name, drip) }
29
+ end
30
+
31
+ def values
32
+ @drips.values
23
33
  end
24
34
 
25
35
  def size
26
36
  @drips.size
27
37
  end
38
+
39
+ def [](val)
40
+ @drips[val]
41
+ end
42
+
43
+ private
44
+
45
+ def validate_drip_options(action, options)
46
+ options.symbolize_keys!
47
+ options.assert_valid_keys(:mailer_class, :step, :delay, :every, :start, :using, :mailer)
48
+ options[:mailer_class] ||= options[:mailer] || @dripper.defaults[:mailer_class]
49
+ options[:using] ||= @dripper.defaults[:using]
50
+ options[:step] ||= @dripper.drips.size + 1
51
+
52
+ if options[:mailer_class].nil?
53
+ raise ArgumentError, "You must define :mailer_class or :mailer in the options for #{action.inspect} on #{@dripper.inspect}"
54
+ end
55
+
56
+ if options[:every].nil? && options[:delay].nil?
57
+ raise ArgumentError, "You must define :delay in the options for #{action.inspect} on #{@dripper.inspect}"
58
+ end
59
+
60
+ options
61
+ end
28
62
  end
29
63
 
30
64
  # :nodoc:
@@ -33,9 +67,14 @@ module Caffeinate
33
67
  end
34
68
 
35
69
  module ClassMethods
70
+ # A collection of Drip objects associated with a given `Caffeinate::Dripper`
71
+ def drip_collection
72
+ @drip_collection ||= DripCollection.new(self)
73
+ end
74
+
36
75
  # A collection of Drip objects associated with a given `Caffeinate::Dripper`
37
76
  def drips
38
- @drips ||= DripCollection.new(self)
77
+ drip_collection.values
39
78
  end
40
79
 
41
80
  # Register a drip on the Dripper
@@ -47,17 +86,9 @@ module Caffeinate
47
86
  # @option options [String] :mailer_class The mailer_class
48
87
  # @option options [Integer] :step The order in which the drip is executed
49
88
  # @option options [ActiveSupport::Duration] :delay When the drip should be ran
89
+ # @option options [Symbol] :using set to :parameters if the mailer action uses ActionMailer::Parameters
50
90
  def drip(action_name, options = {}, &block)
51
- options.assert_valid_keys(:mailer_class, :step, :delay, :using, :mailer)
52
- options[:mailer_class] ||= options[:mailer] || defaults[:mailer_class]
53
- options[:step] ||= drips.size + 1
54
-
55
- if options[:mailer_class].nil?
56
- raise ArgumentError, "You must define :mailer_class or :mailer in the options for :#{action_name}"
57
- end
58
- raise ArgumentError, "You must define :delay in the options for :#{action_name}" if options[:delay].nil?
59
-
60
- drips.register(action_name, options, &block)
91
+ drip_collection.register(action_name, options, &block)
61
92
  end
62
93
  end
63
94
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caffeinate
4
+ module Dripper
5
+ # Includes the inferred methods based on a Dripper name.
6
+ module Inferences
7
+ def self.included(klass)
8
+ klass.extend ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+ # The inferred mailer class
13
+ def inferred_mailer_class
14
+ klass_name = "#{name.delete_suffix('Dripper')}Mailer"
15
+ klass = klass_name.safe_constantize
16
+ return nil unless klass
17
+ return klass_name if klass < ::ActionMailer::Base
18
+
19
+ nil
20
+ end
21
+
22
+ # The inferred mailer class
23
+ def inferred_campaign_slug
24
+ name.delete_suffix('Dripper').to_s.underscore
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -2,23 +2,42 @@
2
2
 
3
3
  module Caffeinate
4
4
  module Dripper
5
- # Handles delivering a `Caffeinate::Mailing` for the `Caffeinate::Dripper`
5
+ # Handles delivering a `Caffeinate::Mailing` for the `Caffeinate::Dripper`.
6
6
  module Perform
7
7
  # :nodoc:
8
8
  def self.included(klass)
9
9
  klass.extend ClassMethods
10
10
  end
11
11
 
12
- # Delivers the next_caffeinate_mailer for the campaign's subscribers.
12
+ # Delivers the next_caffeinate_mailer for the campaign's subscribers. Handles with batches based on `batch_size`.
13
13
  #
14
14
  # OrderDripper.new.perform!
15
15
  #
16
16
  # @return nil
17
17
  def perform!
18
- campaign.caffeinate_campaign_subscriptions.joins(:next_caffeinate_mailing).includes(:next_caffeinate_mailing).each do |subscriber|
19
- subscriber.next_caffeinate_mailing.process!
18
+ includes = [:next_caffeinate_mailing]
19
+ preloads = []
20
+ if ::Caffeinate.config.async_delivery?
21
+ # nothing
22
+ else
23
+ preloads += %i[subscriber user]
20
24
  end
21
- true
25
+
26
+ run_callbacks(:before_process, self)
27
+ campaign.caffeinate_campaign_subscriptions
28
+ .active
29
+ .joins(:next_caffeinate_mailing)
30
+ .preload(*preloads)
31
+ .includes(*includes)
32
+ .in_batches(of: self.class.batch_size)
33
+ .each do |batch|
34
+ run_callbacks(:on_process, self, batch)
35
+ batch.each do |subscriber|
36
+ subscriber.next_caffeinate_mailing.process!
37
+ end
38
+ end
39
+ run_callbacks(:after_process, self)
40
+ nil
22
41
  end
23
42
 
24
43
  module ClassMethods
@@ -0,0 +1,24 @@
1
+ module Caffeinate
2
+ module Dripper
3
+ module Periodical
4
+ def self.included(klass)
5
+ klass.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def periodical(action_name, every:, start: -> { ::Caffeinate.config.time_now }, **options, &block)
10
+ options[:start] = start
11
+ options[:every] = every
12
+ drip(action_name, options, &block)
13
+ after_send do |mailing, _message|
14
+ if mailing.drip.action == action_name
15
+ next_mailing = mailing.dup
16
+ next_mailing.send_at = mailing.drip.send_at(mailing)
17
+ next_mailing.save!
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -30,8 +30,8 @@ module Caffeinate
30
30
  # @option [ActiveRecord::Base] :user The associated user (optional)
31
31
  #
32
32
  # @return [Caffeinate::CampaignSubscriber] the created CampaignSubscriber
33
- def subscribe(subscriber, user:)
34
- caffeinate_campaign.subscribe(subscriber, user: user)
33
+ def subscribe(subscriber, **args)
34
+ caffeinate_campaign.subscribe(subscriber, **args)
35
35
  end
36
36
 
37
37
  # :nodoc:
@@ -1,12 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'caffeinate/helpers'
3
4
  require 'caffeinate/action_mailer'
4
5
  require 'caffeinate/active_record/extension'
5
6
 
6
7
  module Caffeinate
7
- # :nodoc:
8
+ # Adds Caffeinate to Rails
8
9
  class Engine < ::Rails::Engine
9
10
  isolate_namespace Caffeinate
11
+ config.eager_load_namespaces << Caffeinate
12
+
13
+ config.to_prepare do
14
+ Dir.glob(Rails.root.join(Caffeinate.config.drippers_path, "**", "*.rb")).each do |dripper|
15
+ require dripper
16
+ end
17
+ end
10
18
 
11
19
  ActiveSupport.on_load(:action_mailer) do
12
20
  include ::Caffeinate::ActionMailer::Extension
@@ -17,5 +25,9 @@ module Caffeinate
17
25
  ActiveSupport.on_load(:active_record) do
18
26
  extend ::Caffeinate::ActiveRecord::Extension
19
27
  end
28
+
29
+ ActiveSupport.on_load(:action_view) do
30
+ ActionView::Base.include ::Caffeinate::Helpers
31
+ end
20
32
  end
21
33
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caffeinate
4
+ # URL helpers for accessing the mounted Caffeinate instance.
5
+ module Helpers
6
+ def caffeinate_unsubscribe_url(subscription, **options)
7
+ opts = (::ActionMailer::Base.default_url_options || {}).merge(options)
8
+ Caffeinate::Engine.routes.url_helpers.unsubscribe_campaign_subscription_url(token: subscription.token, **opts)
9
+ end
10
+
11
+ def caffeinate_subscribe_url(subscription, **options)
12
+ opts = (::ActionMailer::Base.default_url_options || {}).merge(options)
13
+ Caffeinate::Engine.routes.url_helpers.subscribe_campaign_subscription_url(token: subscription.token, **opts)
14
+ end
15
+
16
+ def caffeinate_unsubscribe_path(subscription, **options)
17
+ Caffeinate::Engine.routes.url_helpers.unsubscribe_campaign_subscription_path(token: subscription.token, **options)
18
+ end
19
+
20
+ def caffeinate_subscribe_path(subscription, **options)
21
+ Caffeinate::Engine.routes.url_helpers.subscribe_campaign_subscription_path(token: subscription.token, **options)
22
+ end
23
+ end
24
+ 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
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'caffeinate/helpers'
4
+
5
+ module Caffeinate
6
+ # Convenience class for the URL helpers.
7
+ class UrlHelpers
8
+ extend Helpers
9
+ end
10
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Caffeinate
4
- VERSION = '0.1.2'
4
+ VERSION = '0.4.0'
5
5
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Caffeinate
4
4
  module Generators
5
- # :nodoc:
5
+ # Installs Caffeinate
6
6
  class InstallGenerator < Rails::Generators::Base
7
7
  source_root File.expand_path('templates', __dir__)
8
8
  include ::Rails::Generators::Migration
@@ -19,6 +19,10 @@ module Caffeinate
19
19
  template 'application_dripper.rb', 'app/drippers/application_dripper.rb'
20
20
  end
21
21
 
22
+ def install_routes
23
+ inject_into_file 'config/routes.rb', "\n mount ::Caffeinate::Engine => '/caffeinate'", after: /Rails.application.routes.draw do/
24
+ end
25
+
22
26
  # :nodoc:
23
27
  def self.next_migration_number(_path)
24
28
  if @prev_migration_nr
@@ -6,7 +6,7 @@ Caffeinate.setup do |config|
6
6
  # Used for when we set a datetime column to "now" in the database
7
7
  #
8
8
  # Default:
9
- # -> { Time.current }
9
+ # config.now = -> { Time.current }
10
10
  #
11
11
  # config.now = -> { DateTime.now }
12
12
  #
@@ -21,4 +21,14 @@ Caffeinate.setup do |config|
21
21
  #
22
22
  # config.async_delivery = true
23
23
  # config.mailing_job = 'MyCustomCaffeinateJob'
24
+ #
25
+ # == Batching
26
+ #
27
+ # When a Dripper is performed and sends the mails, we use `find_in_batches`. Use `batch_size` to set the batch size.
28
+ # You can set this on a dripper as well for more granular control.
29
+ #
30
+ # Default:
31
+ # config.batch_size = 1_000
32
+ #
33
+ # config.batch_size = 100
24
34
  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.1.2
4
+ version: 0.4.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-27 00:00:00.000000000 Z
11
+ date: 2020-12-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -124,8 +124,10 @@ files:
124
124
  - app/models/caffeinate/campaign.rb
125
125
  - app/models/caffeinate/campaign_subscription.rb
126
126
  - app/models/caffeinate/mailing.rb
127
- - app/views/layouts/caffeinate/application.html.erb
128
- - app/views/layouts/caffeinate/campaign_subscriptions/unsubscribe.html.erb
127
+ - app/views/caffeinate/campaign_subscriptions/subscribe.html.erb
128
+ - app/views/caffeinate/campaign_subscriptions/unsubscribe.html.erb
129
+ - app/views/layouts/_caffeinate.html.erb
130
+ - config/locales/en.yml
129
131
  - config/routes.rb
130
132
  - db/migrate/20201124183102_create_caffeinate_campaigns.rb
131
133
  - db/migrate/20201124183303_create_caffeinate_campaign_subscriptions.rb
@@ -133,22 +135,28 @@ files:
133
135
  - lib/caffeinate.rb
134
136
  - lib/caffeinate/action_mailer.rb
135
137
  - lib/caffeinate/action_mailer/extension.rb
136
- - lib/caffeinate/action_mailer/helpers.rb
137
138
  - lib/caffeinate/action_mailer/interceptor.rb
138
139
  - lib/caffeinate/action_mailer/observer.rb
139
140
  - lib/caffeinate/active_record/extension.rb
140
141
  - lib/caffeinate/configuration.rb
141
142
  - lib/caffeinate/deliver_async.rb
142
143
  - lib/caffeinate/drip.rb
144
+ - lib/caffeinate/drip_evaluator.rb
143
145
  - lib/caffeinate/dripper/base.rb
146
+ - lib/caffeinate/dripper/batching.rb
144
147
  - lib/caffeinate/dripper/callbacks.rb
145
148
  - lib/caffeinate/dripper/campaign.rb
146
149
  - lib/caffeinate/dripper/defaults.rb
147
150
  - lib/caffeinate/dripper/delivery.rb
148
151
  - lib/caffeinate/dripper/drip.rb
152
+ - lib/caffeinate/dripper/inferences.rb
149
153
  - lib/caffeinate/dripper/perform.rb
154
+ - lib/caffeinate/dripper/periodical.rb
150
155
  - lib/caffeinate/dripper/subscriber.rb
151
156
  - lib/caffeinate/engine.rb
157
+ - lib/caffeinate/helpers.rb
158
+ - lib/caffeinate/mail_ext.rb
159
+ - lib/caffeinate/url_helpers.rb
152
160
  - lib/caffeinate/version.rb
153
161
  - lib/generators/caffeinate/install_generator.rb
154
162
  - lib/generators/caffeinate/templates/application_dripper.rb