caffeinate 0.1.1 → 0.2.1
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 +57 -44
- data/app/controllers/caffeinate/campaign_subscriptions_controller.rb +17 -2
- data/app/models/caffeinate/campaign.rb +40 -1
- data/app/models/caffeinate/campaign_subscription.rb +30 -6
- data/app/models/caffeinate/mailing.rb +18 -2
- data/app/views/caffeinate/campaign_subscriptions/subscribe.html.erb +3 -0
- data/app/views/caffeinate/campaign_subscriptions/unsubscribe.html.erb +3 -0
- data/app/views/layouts/caffeinate.html.erb +11 -0
- data/config/locales/en.yml +6 -0
- data/db/migrate/20201124183303_create_caffeinate_campaign_subscriptions.rb +1 -0
- data/lib/caffeinate.rb +11 -0
- data/lib/caffeinate/action_mailer.rb +4 -4
- data/lib/caffeinate/action_mailer/extension.rb +17 -1
- data/lib/caffeinate/action_mailer/interceptor.rb +3 -1
- data/lib/caffeinate/action_mailer/observer.rb +3 -3
- data/lib/caffeinate/active_record/extension.rb +3 -2
- data/lib/caffeinate/configuration.rb +3 -1
- data/lib/caffeinate/drip.rb +10 -35
- data/lib/caffeinate/drip_evaluator.rb +35 -0
- data/lib/caffeinate/dripper/base.rb +9 -14
- data/lib/caffeinate/dripper/batching.rb +20 -0
- data/lib/caffeinate/dripper/callbacks.rb +57 -2
- data/lib/caffeinate/dripper/campaign.rb +6 -8
- data/lib/caffeinate/dripper/defaults.rb +3 -2
- data/lib/caffeinate/dripper/delivery.rb +2 -2
- data/lib/caffeinate/dripper/drip.rb +20 -6
- data/lib/caffeinate/dripper/inferences.rb +29 -0
- data/lib/caffeinate/dripper/perform.rb +11 -4
- data/lib/caffeinate/dripper/subscriber.rb +2 -2
- data/lib/caffeinate/engine.rb +7 -1
- data/lib/caffeinate/helpers.rb +24 -0
- data/lib/caffeinate/url_helpers.rb +10 -0
- data/lib/caffeinate/version.rb +1 -1
- data/lib/generators/caffeinate/install_generator.rb +5 -1
- data/lib/generators/caffeinate/templates/{application_campaign.rb → application_dripper.rb} +0 -0
- data/lib/generators/caffeinate/templates/caffeinate.rb +11 -1
- metadata +12 -6
- data/app/views/layouts/caffeinate/application.html.erb +0 -15
- data/app/views/layouts/caffeinate/campaign_subscriptions/unsubscribe.html.erb +0 -1
- data/lib/caffeinate/action_mailer/helpers.rb +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3241ca7ca0c6e31220511ad04172105d97058fa84f1de1e6ac442052823a91e0
|
4
|
+
data.tar.gz: 5b0bda6f08ae89f79cbd64df4c75f48fcb0dacf7471155aae779290b6a1b2e41
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d43a0030851c7fa107fed83fcc05c9cfa8596be068a8327d01f597a0a45fc84aa403d1a95990e8a4c45013fb44bc899215778d36ce8a8f5cc770db50b33cd0df
|
7
|
+
data.tar.gz: 6f9e399cc7e0a00bbb544b668d80e0438f4482013f3163a819b9f2ab8ea502f3c8535aacb91d57eb80efc5f12c22bd03a2bdfd36c2216544bb9aa1fd6928e797
|
data/README.md
CHANGED
@@ -1,81 +1,89 @@
|
|
1
1
|
# Caffeinate
|
2
2
|
|
3
|
-
|
3
|
+
Caffeinate is a drip campaign engine for Ruby on Rails applications.
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
Caffeinate tries to make creating and managing timed and scheduled email sequences fun. It works alongside ActionMailer
|
6
|
+
and has everything you need to get started and to successfully manage campaigns. It's only dependency is the stack you're
|
7
|
+
already familiar with: Ruby on Rails.
|
8
8
|
|
9
9
|
## Usage
|
10
10
|
|
11
|
-
|
11
|
+
You can probably imagine seeing a Mailer like this:
|
12
12
|
|
13
13
|
```ruby
|
14
|
-
class
|
15
|
-
|
16
|
-
|
14
|
+
class OnboardingMailer < ActionMailer::Base
|
15
|
+
# Send on account creation
|
16
|
+
def welcome_to_my_cool_app(user)
|
17
|
+
mail(to: user.email, subject: "You forgot something!")
|
17
18
|
end
|
18
19
|
|
19
|
-
|
20
|
-
|
20
|
+
# Send 2 days after the user signs up
|
21
|
+
def some_cool_tips(user)
|
22
|
+
mail(to: user.email, subject: "Here are some cool tips for MyCoolApp")
|
23
|
+
end
|
24
|
+
|
25
|
+
# Sends 3 days after the user signs up and hasn't added a company profile yet
|
26
|
+
def help_getting_started(user)
|
27
|
+
return if user.company.present?
|
28
|
+
|
29
|
+
mail(to: user.email, subject: "Did you know...")
|
21
30
|
end
|
22
31
|
end
|
23
32
|
```
|
24
33
|
|
34
|
+
With background jobs running, checking, and everything else. That's messy. Why are we checking state in the Mailer? Ugh.
|
35
|
+
|
36
|
+
We can clean this up with Caffeinate. Here's how we'd do it.
|
37
|
+
|
25
38
|
### Create a Campaign
|
26
39
|
|
27
40
|
```ruby
|
28
|
-
Caffeinate::Campaign.create!(name: "
|
41
|
+
Caffeinate::Campaign.create!(name: "Onboarding Campaign", slug: "onboarding")
|
29
42
|
```
|
30
43
|
|
31
44
|
### Create a Caffeinate::Dripper
|
32
45
|
|
46
|
+
Place the contents below in `app/drippers/onboarding_dripper.rb`:
|
47
|
+
|
33
48
|
```ruby
|
34
|
-
class
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
# probably in a background process, run at a given interval
|
41
|
-
subscribes do
|
42
|
-
Cart.left_joins(:cart_items)
|
43
|
-
.includes(:user)
|
44
|
-
.where(completed_at: nil)
|
45
|
-
.where(updated_at: 1.day.ago..2.days.ago)
|
46
|
-
.having('count(cart_items.id) = 0').each do |cart|
|
47
|
-
subscribe(cart, user: cart.user)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
# Register your drips! Syntax is
|
52
|
-
# drip <mailer_action_name>, mailer: <MailerClass>, delay: <ActiveSupport::Interval>
|
53
|
-
drip :you_forgot_something, mailer: "AbandonedCartMailer", delay: 1.hour
|
54
|
-
drip :selling_out_soon, mailer: "AbandonedCartMailer", delay: 8.hours do
|
55
|
-
cart = mailing.subscriber
|
56
|
-
if cart.completed?
|
57
|
-
end! # you can also invoke `unsubscribe!` to cancel this mailing and all future mailings
|
49
|
+
class OnboardingDripper < ApplicationDripper
|
50
|
+
drip :welcome_to_my_cool_app, delay: 0.hours
|
51
|
+
drip :some_cool_tips, delay: 2.days
|
52
|
+
drip :help_getting_started, delay: 3.days do
|
53
|
+
if mailing.user.company.present?
|
54
|
+
mailing.unsubscribe!
|
58
55
|
return false
|
59
|
-
end
|
60
|
-
end
|
56
|
+
end
|
57
|
+
end
|
61
58
|
end
|
62
59
|
```
|
63
60
|
|
64
|
-
|
61
|
+
### Add a subscriber to the Campaign
|
65
62
|
|
66
63
|
```ruby
|
67
|
-
|
64
|
+
class User < ApplicationRecord
|
65
|
+
after_create_commit do
|
66
|
+
Caffeinate::Campaign.find_by(slug: "onboarding").subscribe(self)
|
67
|
+
end
|
68
|
+
end
|
68
69
|
```
|
69
70
|
|
70
|
-
|
71
|
+
### Run the Dripper
|
71
72
|
|
72
|
-
|
73
|
+
You'd normally want to do this in a cron/whenever/scheduled Sidekiq/etc job.
|
73
74
|
|
74
|
-
```ruby
|
75
|
-
|
75
|
+
```ruby
|
76
|
+
OnboardingDripper.perform!
|
76
77
|
```
|
77
78
|
|
78
|
-
|
79
|
+
### Spend more time building
|
80
|
+
|
81
|
+
Now you can spend more time building your app and less time managing your marketing campaigns.
|
82
|
+
* Centralized logic makes it easy to understand the flow
|
83
|
+
* Subscription management, timings, send history all built-in
|
84
|
+
* Built on the stack you're already familiar with
|
85
|
+
|
86
|
+
There's a lot more than what you just saw, too! Caffeinate almost makes managing timed email sequences fun.
|
79
87
|
|
80
88
|
## Installation
|
81
89
|
|
@@ -103,6 +111,11 @@ Followed by a migrate:
|
|
103
111
|
$ rails db:migrate
|
104
112
|
```
|
105
113
|
|
114
|
+
## Documentation
|
115
|
+
|
116
|
+
* [Getting started, tips and tricks](https://github.com/joshmn/caffeinate/blob/master/docs/README.md)
|
117
|
+
* [Better-than-average code documentation](https://rubydoc.info/github/joshmn/caffeinate)
|
118
|
+
|
106
119
|
## Upcoming features/todo
|
107
120
|
|
108
121
|
* Ability to optionally use relative start time when creating a step
|
@@ -2,18 +2,33 @@
|
|
2
2
|
|
3
3
|
module Caffeinate
|
4
4
|
class CampaignSubscriptionsController < ApplicationController
|
5
|
+
layout 'caffeinate'
|
6
|
+
|
7
|
+
helper_method :caffeinate_unsubscribe_url, :caffeinate_subscribe_url
|
8
|
+
|
5
9
|
before_action :find_campaign_subscription!
|
6
10
|
|
7
11
|
def unsubscribe
|
8
12
|
@campaign_subscription.unsubscribe!
|
9
|
-
|
13
|
+
end
|
14
|
+
|
15
|
+
def subscribe
|
16
|
+
@campaign_subscription.subscribe!
|
10
17
|
end
|
11
18
|
|
12
19
|
private
|
13
20
|
|
21
|
+
def caffeinate_subscribe_url(**options)
|
22
|
+
Caffeinate::UrlHelpers.caffeinate_subscribe_url(@campaign_subscription, options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def caffeinate_unsubscribe_url
|
26
|
+
Caffeinate::UrlHelpers.caffeinate_unsubscribe_url(@campaign_subscription, options)
|
27
|
+
end
|
28
|
+
|
14
29
|
def find_campaign_subscription!
|
15
30
|
@campaign_subscription = ::Caffeinate::CampaignSubscription.find_by(token: params[:token])
|
16
|
-
|
31
|
+
raise ::ActiveRecord::RecordNotFound if @campaign_subscription.nil?
|
17
32
|
end
|
18
33
|
end
|
19
34
|
end
|
@@ -1,7 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# == Schema Information
|
4
|
+
#
|
5
|
+
# Table name: caffeinate_campaigns
|
6
|
+
#
|
7
|
+
# id :integer not null, primary key
|
8
|
+
# name :string not null
|
9
|
+
# slug :string not null
|
10
|
+
# created_at :datetime not null
|
11
|
+
# updated_at :datetime not null
|
12
|
+
#
|
3
13
|
module Caffeinate
|
4
|
-
# Campaign.
|
14
|
+
# Campaign ties together subscribers and mailings, and provides one core model for handling your Drippers.
|
5
15
|
class Campaign < ApplicationRecord
|
6
16
|
self.table_name = 'caffeinate_campaigns'
|
7
17
|
has_many :caffeinate_campaign_subscriptions, class_name: 'Caffeinate::CampaignSubscription', foreign_key: :caffeinate_campaign_id
|
@@ -12,9 +22,38 @@ module Caffeinate
|
|
12
22
|
Caffeinate.dripper_to_campaign_class[slug.to_sym].constantize
|
13
23
|
end
|
14
24
|
|
25
|
+
# Convenience method for find_by!(slug: value)
|
26
|
+
#
|
27
|
+
# ::Caffeinate::Campaign[:onboarding]
|
28
|
+
# # is the same as
|
29
|
+
# ::Caffeinate::Campaign.find_by(slug: :onboarding)
|
30
|
+
def self.[](val)
|
31
|
+
find_by!(slug: val)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Checks to see if the subscriber exists.
|
35
|
+
#
|
36
|
+
# Use `find_by` so that we don't have to load the record twice. Often used with `subscribes?`
|
37
|
+
def subscriber(record, **args)
|
38
|
+
@subscriber ||= caffeinate_campaign_subscriptions.find_by(subscriber: record, **args)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Check if the subscriber exists
|
42
|
+
def subscribes?(record, **args)
|
43
|
+
subscriber(record, **args).present?
|
44
|
+
end
|
45
|
+
|
15
46
|
# Subscribes an object to a campaign.
|
16
47
|
def subscribe(subscriber, **args)
|
17
48
|
caffeinate_campaign_subscriptions.find_or_create_by(subscriber: subscriber, **args)
|
18
49
|
end
|
50
|
+
|
51
|
+
# Subscribes an object to a campaign.
|
52
|
+
def subscribe!(subscriber, **args)
|
53
|
+
subscription = subscribe(subscriber, **args)
|
54
|
+
return subscription if subscribe.persisted?
|
55
|
+
|
56
|
+
raise ActiveRecord::RecordInvalid, subscription
|
57
|
+
end
|
19
58
|
end
|
20
59
|
end
|
@@ -1,12 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# == Schema Information
|
4
|
+
#
|
5
|
+
# Table name: caffeinate_campaign_subscriptions
|
6
|
+
#
|
7
|
+
# id :integer not null, primary key
|
8
|
+
# caffeinate_campaign_id :integer not null
|
9
|
+
# subscriber_type :string not null
|
10
|
+
# subscriber_id :string not null
|
11
|
+
# user_type :string
|
12
|
+
# user_id :string
|
13
|
+
# token :string not null
|
14
|
+
# ended_at :datetime
|
15
|
+
# unsubscribed_at :datetime
|
16
|
+
# created_at :datetime not null
|
17
|
+
# updated_at :datetime not null
|
18
|
+
#
|
3
19
|
module Caffeinate
|
4
|
-
# CampaignSubscription associates an object and its optional user to a Campaign
|
20
|
+
# CampaignSubscription associates an object and its optional user to a Campaign
|
21
|
+
# and its relevant Mailings.
|
5
22
|
class CampaignSubscription < ApplicationRecord
|
6
23
|
self.table_name = 'caffeinate_campaign_subscriptions'
|
7
24
|
|
8
|
-
has_many :caffeinate_mailings, class_name: 'Caffeinate::Mailing', foreign_key: :caffeinate_campaign_subscription_id
|
9
|
-
has_one :next_caffeinate_mailing, -> { upcoming.unsent
|
25
|
+
has_many :caffeinate_mailings, class_name: 'Caffeinate::Mailing', foreign_key: :caffeinate_campaign_subscription_id, dependent: :destroy
|
26
|
+
has_one :next_caffeinate_mailing, -> { upcoming.unsent }, class_name: '::Caffeinate::Mailing', foreign_key: :caffeinate_campaign_subscription_id
|
10
27
|
belongs_to :caffeinate_campaign, class_name: 'Caffeinate::Campaign', foreign_key: :caffeinate_campaign_id
|
11
28
|
belongs_to :subscriber, polymorphic: true
|
12
29
|
belongs_to :user, polymorphic: true, optional: true
|
@@ -37,12 +54,12 @@ module Caffeinate
|
|
37
54
|
!ended? && !unsubscribed?
|
38
55
|
end
|
39
56
|
|
40
|
-
# Checks if the CampaignSubscription is not subscribed
|
57
|
+
# Checks if the CampaignSubscription is not subscribed by checking the presence of `unsubscribed_at`
|
41
58
|
def unsubscribed?
|
42
|
-
|
59
|
+
unsubscribed_at.present?
|
43
60
|
end
|
44
61
|
|
45
|
-
# Checks if the CampaignSubscription is ended
|
62
|
+
# Checks if the CampaignSubscription is ended by checking the presence of `ended_at`
|
46
63
|
def ended?
|
47
64
|
ended_at.present?
|
48
65
|
end
|
@@ -61,6 +78,13 @@ module Caffeinate
|
|
61
78
|
caffeinate_campaign.to_dripper.run_callbacks(:on_unsubscribe, self)
|
62
79
|
end
|
63
80
|
|
81
|
+
# Updates `unsubscribed_at` to nil and runs `on_subscribe` callbacks
|
82
|
+
def resubscribe!
|
83
|
+
update!(unsubscribed_at: nil, resubscribed_at: ::Caffeinate.config.time_now)
|
84
|
+
|
85
|
+
caffeinate_campaign.to_dripper.run_callbacks(:on_resubscribe, self)
|
86
|
+
end
|
87
|
+
|
64
88
|
private
|
65
89
|
|
66
90
|
# Create mailings according to the drips registered in the Campaign
|
@@ -1,8 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# == Schema Information
|
4
|
+
#
|
5
|
+
# Table name: caffeinate_mailings
|
6
|
+
#
|
7
|
+
# id :integer not null, primary key
|
8
|
+
# caffeinate_campaign_subscription_id :integer not null
|
9
|
+
# send_at :datetime
|
10
|
+
# sent_at :datetime
|
11
|
+
# skipped_at :datetime
|
12
|
+
# mailer_class :string not null
|
13
|
+
# mailer_action :string not null
|
14
|
+
# created_at :datetime not null
|
15
|
+
# updated_at :datetime not null
|
16
|
+
#
|
3
17
|
module Caffeinate
|
4
18
|
# Records of the mails sent and to be sent for a given `::Caffeinate::CampaignSubscriber`
|
5
19
|
class Mailing < ApplicationRecord
|
20
|
+
CURRENT_THREAD_KEY = :current_caffeinate_mailing
|
21
|
+
|
6
22
|
self.table_name = 'caffeinate_mailings'
|
7
23
|
|
8
24
|
belongs_to :caffeinate_campaign_subscription, class_name: 'Caffeinate::CampaignSubscription'
|
@@ -49,7 +65,7 @@ module Caffeinate
|
|
49
65
|
# The associated drip
|
50
66
|
# @todo This can be optimized with a better cache
|
51
67
|
def drip
|
52
|
-
@drip ||= caffeinate_campaign.to_dripper.
|
68
|
+
@drip ||= caffeinate_campaign.to_dripper.drip_collection[mailer_action]
|
53
69
|
end
|
54
70
|
|
55
71
|
# The associated Subscriber from `::Caffeinate::CampaignSubscription`
|
@@ -64,7 +80,7 @@ module Caffeinate
|
|
64
80
|
|
65
81
|
# Assigns attributes to the Mailing from the Drip
|
66
82
|
def from_drip(drip)
|
67
|
-
self.send_at = drip.
|
83
|
+
self.send_at = drip.send_at
|
68
84
|
self.mailer_class = drip.options[:mailer_class]
|
69
85
|
self.mailer_action = drip.action
|
70
86
|
self
|
data/lib/caffeinate.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'rails/all'
|
4
4
|
require 'caffeinate/engine'
|
5
5
|
require 'caffeinate/drip'
|
6
|
+
require 'caffeinate/url_helpers'
|
6
7
|
require 'caffeinate/configuration'
|
7
8
|
require 'caffeinate/dripper/base'
|
8
9
|
require 'caffeinate/deliver_async'
|
@@ -27,4 +28,14 @@ module Caffeinate
|
|
27
28
|
def self.setup
|
28
29
|
yield config
|
29
30
|
end
|
31
|
+
|
32
|
+
# The current mailing
|
33
|
+
def self.current_mailing=(val)
|
34
|
+
Thread.current[::Caffeinate::Mailing::CURRENT_THREAD_KEY] = val
|
35
|
+
end
|
36
|
+
|
37
|
+
# The current mailing
|
38
|
+
def self.current_mailing
|
39
|
+
Thread.current[::Caffeinate::Mailing::CURRENT_THREAD_KEY]
|
40
|
+
end
|
30
41
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
|
5
|
-
|
6
|
-
require
|
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,11 +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 =
|
10
|
+
@mailing = Caffeinate.current_mailing if Caffeinate.current_mailing
|
9
11
|
end
|
12
|
+
|
13
|
+
klass.helper_method :caffeinate_unsubscribe_url, :caffeinate_subscribe_url
|
14
|
+
end
|
15
|
+
|
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)
|
20
|
+
end
|
21
|
+
|
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)
|
10
26
|
end
|
11
27
|
end
|
12
28
|
end
|
@@ -2,10 +2,12 @@
|
|
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 =
|
10
|
+
mailing = Caffeinate.current_mailing
|
9
11
|
return unless mailing
|
10
12
|
|
11
13
|
mailing.caffeinate_campaign.to_dripper.run_callbacks(:before_send, mailing.caffeinate_campaign_subscription, mailing, message)
|
@@ -2,14 +2,14 @@
|
|
2
2
|
|
3
3
|
module Caffeinate
|
4
4
|
module ActionMailer
|
5
|
-
# Handles updating the Caffeinate::Message if it's available in
|
5
|
+
# Handles updating the Caffeinate::Message if it's available in Caffeinate.current_mailing
|
6
6
|
# and runs any associated callbacks
|
7
7
|
class Observer
|
8
8
|
def self.delivered_email(message)
|
9
|
-
mailing =
|
9
|
+
mailing = Caffeinate.current_mailing
|
10
10
|
return unless mailing
|
11
11
|
|
12
|
-
mailing.update!(sent_at: Caffeinate.config.time_now) if message.perform_deliveries
|
12
|
+
mailing.update!(sent_at: Caffeinate.config.time_now, skipped_at: nil) if message.perform_deliveries
|
13
13
|
mailing.caffeinate_campaign.to_dripper.run_callbacks(:after_send, mailing.caffeinate_campaign_subscription, mailing, message)
|
14
14
|
end
|
15
15
|
end
|
@@ -2,14 +2,15 @@
|
|
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
8
|
def caffeinate_subscriber
|
8
|
-
has_many :caffeinate_campaign_subscriptions, as: :subscriber, class_name: '::Caffeinate::CampaignSubscription'
|
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
|
-
scope :
|
13
|
+
scope :not_subscribed_to_campaign, lambda { |list|
|
13
14
|
subscribed = ::Caffeinate::CampaignSubscription.select(:subscriber_id).joins(:caffeinate_campaign).where(caffeinate_campaigns: { slug: list }, subscriber_type: name)
|
14
15
|
where.not(id: subscribed)
|
15
16
|
}
|
@@ -1,13 +1,15 @@
|
|
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
|
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
|
11
13
|
end
|
12
14
|
|
13
15
|
def now=(val)
|
data/lib/caffeinate/drip.rb
CHANGED
@@ -1,40 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'caffeinate/drip_evaluator'
|
3
4
|
module Caffeinate
|
4
5
|
# A Drip object
|
6
|
+
#
|
7
|
+
# Handles the block and provides convenience methods for the drip
|
5
8
|
class Drip
|
6
|
-
# Handles the block and provides convenience methods for the drip
|
7
|
-
class Evaluator
|
8
|
-
attr_reader :mailing
|
9
|
-
def initialize(mailing)
|
10
|
-
@mailing = mailing
|
11
|
-
end
|
12
|
-
|
13
|
-
def call(&block)
|
14
|
-
return true unless block
|
15
|
-
|
16
|
-
instance_eval(&block)
|
17
|
-
end
|
18
|
-
|
19
|
-
# Ends the CampaignSubscription
|
20
|
-
def end!
|
21
|
-
mailing.caffeinate_campaign_subscription.end!
|
22
|
-
false
|
23
|
-
end
|
24
|
-
|
25
|
-
# Unsubscribes the CampaignSubscription
|
26
|
-
def unsubscribe!
|
27
|
-
mailing.caffeinate_campaign_subscription.unsubscribe!
|
28
|
-
false
|
29
|
-
end
|
30
|
-
|
31
|
-
# Skips the mailing
|
32
|
-
def skip!
|
33
|
-
mailing.skip!
|
34
|
-
false
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
9
|
attr_reader :dripper, :action, :options, :block
|
39
10
|
def initialize(dripper, action, options, &block)
|
40
11
|
@dripper = dripper
|
@@ -43,14 +14,18 @@ module Caffeinate
|
|
43
14
|
@block = block
|
44
15
|
end
|
45
16
|
|
46
|
-
# If the associated ActionMailer uses `ActionMailer::Parameterized` initialization
|
17
|
+
# If the associated ActionMailer uses `ActionMailer::Parameterized` initialization instead of argument-based initialization
|
47
18
|
def parameterized?
|
48
19
|
options[:using] == :parameterized
|
49
20
|
end
|
50
21
|
|
51
|
-
|
22
|
+
def send_at
|
23
|
+
options[:delay].from_now
|
24
|
+
end
|
25
|
+
|
26
|
+
# Checks if the drip is enabled
|
52
27
|
def enabled?(mailing)
|
53
|
-
|
28
|
+
DripEvaluator.new(mailing).call(&@block)
|
54
29
|
end
|
55
30
|
end
|
56
31
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Caffeinate
|
4
|
+
# Handles evaluating the `drip` block and provides convenience methods for handling the mailing or its campaign.
|
5
|
+
class DripEvaluator
|
6
|
+
attr_reader :mailing
|
7
|
+
def initialize(mailing)
|
8
|
+
@mailing = mailing
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(&block)
|
12
|
+
return true unless block
|
13
|
+
|
14
|
+
instance_eval(&block)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Ends the CampaignSubscription
|
18
|
+
def end!
|
19
|
+
mailing.caffeinate_campaign_subscription.end!
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
# Unsubscribes the CampaignSubscription
|
24
|
+
def unsubscribe!
|
25
|
+
mailing.caffeinate_campaign_subscription.unsubscribe!
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
# Skips the mailing
|
30
|
+
def skip!
|
31
|
+
mailing.skip!
|
32
|
+
false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -1,33 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'caffeinate/dripper/
|
3
|
+
require 'caffeinate/dripper/batching'
|
4
4
|
require 'caffeinate/dripper/callbacks'
|
5
|
-
require 'caffeinate/dripper/defaults'
|
6
|
-
require 'caffeinate/dripper/subscriber'
|
7
5
|
require 'caffeinate/dripper/campaign'
|
8
|
-
require 'caffeinate/dripper/
|
6
|
+
require 'caffeinate/dripper/defaults'
|
9
7
|
require 'caffeinate/dripper/delivery'
|
8
|
+
require 'caffeinate/dripper/drip'
|
9
|
+
require 'caffeinate/dripper/inferences'
|
10
|
+
require 'caffeinate/dripper/perform'
|
11
|
+
require 'caffeinate/dripper/subscriber'
|
10
12
|
|
11
13
|
module Caffeinate
|
12
14
|
module Dripper
|
15
|
+
# Base class
|
13
16
|
class Base
|
17
|
+
include Batching
|
14
18
|
include Callbacks
|
15
19
|
include Campaign
|
16
20
|
include Defaults
|
17
21
|
include Delivery
|
18
22
|
include Drip
|
23
|
+
include Inferences
|
19
24
|
include Perform
|
20
25
|
include Subscriber
|
21
|
-
|
22
|
-
# The inferred mailer class
|
23
|
-
def self.inferred_mailer_class
|
24
|
-
klass_name = "#{name.delete_suffix('Dripper')}Mailer"
|
25
|
-
klass = klass_name.safe_constantize
|
26
|
-
return nil unless klass
|
27
|
-
return klass_name if klass < ::ActionMailer::Base
|
28
|
-
|
29
|
-
nil
|
30
|
-
end
|
31
26
|
end
|
32
27
|
end
|
33
28
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Caffeinate
|
4
|
+
# Includes batch support for setting the batch size for Perform
|
5
|
+
module Batching
|
6
|
+
def self.included(klass)
|
7
|
+
klass.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def batch_size(num)
|
12
|
+
@_batch_size = num
|
13
|
+
end
|
14
|
+
|
15
|
+
def _batch_size
|
16
|
+
@_batch_size || ::Caffeinate.config.batch_size
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
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,6 +38,56 @@ module Caffeinate
|
|
33
38
|
@on_subscribe_blocks ||= []
|
34
39
|
end
|
35
40
|
|
41
|
+
# Callback before the mailings get processed.
|
42
|
+
#
|
43
|
+
# before_process do |dripper|
|
44
|
+
# Slack.notify(:caffeinate, "Dripper is getting ready for mailing! #{dripper.caffeinate_campaign.name}!")
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# @yield Caffeinate::Dripper
|
48
|
+
def before_process(&block)
|
49
|
+
before_process_blocks << block
|
50
|
+
end
|
51
|
+
|
52
|
+
# :nodoc:
|
53
|
+
def before_process_blocks
|
54
|
+
@before_process_blocks ||= []
|
55
|
+
end
|
56
|
+
|
57
|
+
# Callback before the mailings get processed in a batch.
|
58
|
+
#
|
59
|
+
# after_process do |dripper, mailings|
|
60
|
+
# Slack.notify(:caffeinate, "Dripper #{dripper.name} sent #{mailings.size} mailings! Whoa!")
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# @yield Caffeinate::Dripper
|
64
|
+
# @yield Caffeinate::Mailing [Array]
|
65
|
+
def on_process(&block)
|
66
|
+
on_process_blocks << block
|
67
|
+
end
|
68
|
+
|
69
|
+
# :nodoc:
|
70
|
+
def on_process_blocks
|
71
|
+
@on_process_blocks ||= []
|
72
|
+
end
|
73
|
+
|
74
|
+
# Callback after the all the mailings have been sent.
|
75
|
+
#
|
76
|
+
# after_process do |dripper|
|
77
|
+
# Slack.notify(:caffeinate, "Dripper #{dripper.name} sent #{mailings.size} mailings! Whoa!")
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# @yield Caffeinate::Dripper
|
81
|
+
# @yield Caffeinate::Mailing [Array]
|
82
|
+
def after_process(&block)
|
83
|
+
after_process_blocks << block
|
84
|
+
end
|
85
|
+
|
86
|
+
# :nodoc:
|
87
|
+
def after_process_blocks
|
88
|
+
@after_process_blocks ||= []
|
89
|
+
end
|
90
|
+
|
36
91
|
# Callback before a Drip has called the mailer.
|
37
92
|
#
|
38
93
|
# before_drip do |campaign_subscription, mailing, drip|
|
@@ -125,8 +180,8 @@ module Caffeinate
|
|
125
180
|
# Slack.notify(:caffeinate, "#{campaign_sub.id} has unsubscribed... sad day.")
|
126
181
|
# end
|
127
182
|
#
|
128
|
-
# @yield Caffeinate::CampaignSubscription
|
129
|
-
# @yield Caffeinate::Mailing
|
183
|
+
# @yield `Caffeinate::CampaignSubscription`
|
184
|
+
# @yield `Caffeinate::Mailing`
|
130
185
|
def on_skip(&block)
|
131
186
|
on_skip_blocks << block
|
132
187
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module Caffeinate
|
4
4
|
module Dripper
|
5
|
+
# Campaign methods for `Caffeinate::Dripper`.
|
5
6
|
module Campaign
|
6
7
|
# :nodoc:
|
7
8
|
def self.included(klass)
|
@@ -10,7 +11,7 @@ module Caffeinate
|
|
10
11
|
|
11
12
|
# The campaign for this Dripper
|
12
13
|
#
|
13
|
-
# @return Caffeinate::Campaign
|
14
|
+
# @return `Caffeinate::Campaign`
|
14
15
|
def campaign
|
15
16
|
self.class.caffeinate_campaign
|
16
17
|
end
|
@@ -18,7 +19,7 @@ module Caffeinate
|
|
18
19
|
module ClassMethods
|
19
20
|
# Sets the campaign on the Dripper and resets any existing `@caffeinate_campaign`
|
20
21
|
#
|
21
|
-
# class OrdersDripper
|
22
|
+
# class OrdersDripper < ApplicationDripper
|
22
23
|
# campaign :order_drip
|
23
24
|
# end
|
24
25
|
#
|
@@ -33,19 +34,16 @@ module Caffeinate
|
|
33
34
|
Caffeinate.register_dripper(@_campaign_slug, name)
|
34
35
|
end
|
35
36
|
|
36
|
-
# Returns the `Caffeinate::Campaign` object for the
|
37
|
+
# Returns the `Caffeinate::Campaign` object for the Dripper
|
37
38
|
def caffeinate_campaign
|
38
39
|
return @caffeinate_campaign if @caffeinate_campaign.present?
|
39
40
|
|
40
|
-
@caffeinate_campaign = ::Caffeinate::Campaign
|
41
|
-
return @caffeinate_campaign if @caffeinate_campaign
|
42
|
-
|
43
|
-
raise(::ActiveRecord::RecordNotFound, "Unable to find ::Caffeinate::Campaign with slug #{campaign_slug}.")
|
41
|
+
@caffeinate_campaign = ::Caffeinate::Campaign[campaign_slug]
|
44
42
|
end
|
45
43
|
|
46
44
|
# The defined slug or the inferred slug
|
47
45
|
def campaign_slug
|
48
|
-
@_campaign_slug ||
|
46
|
+
@_campaign_slug || inferred_campaign_slug
|
49
47
|
end
|
50
48
|
end
|
51
49
|
end
|
@@ -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,7 @@ 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.assert_valid_keys(:mailer_class, :mailer, :using, :batch_size)
|
27
28
|
@defaults = options
|
28
29
|
end
|
29
30
|
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,7 +14,7 @@ module Caffeinate
|
|
14
14
|
#
|
15
15
|
# @param [Caffeinate::Mailing] mailing The mailing to deliver
|
16
16
|
def deliver!(mailing)
|
17
|
-
|
17
|
+
Caffeinate.current_mailing = mailing
|
18
18
|
|
19
19
|
if mailing.drip.parameterized?
|
20
20
|
mailing.mailer_class.constantize.with(mailing: mailing).send(mailing.mailer_action).deliver
|
@@ -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,29 @@ module Caffeinate
|
|
10
10
|
|
11
11
|
def initialize(dripper)
|
12
12
|
@dripper = dripper
|
13
|
-
@drips =
|
13
|
+
@drips = {}
|
14
14
|
end
|
15
15
|
|
16
16
|
# Register the drip
|
17
17
|
def register(action, options, &block)
|
18
|
-
@drips
|
18
|
+
@drips[action.to_s] = ::Caffeinate::Drip.new(@dripper, action, options, &block)
|
19
19
|
end
|
20
20
|
|
21
21
|
def each(&block)
|
22
|
-
@drips.each { |drip| block.call(drip) }
|
22
|
+
@drips.each { |action_name, drip| block.call(action_name, drip) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def values
|
26
|
+
@drips.values
|
23
27
|
end
|
24
28
|
|
25
29
|
def size
|
26
30
|
@drips.size
|
27
31
|
end
|
32
|
+
|
33
|
+
def [](val)
|
34
|
+
@drips[val]
|
35
|
+
end
|
28
36
|
end
|
29
37
|
|
30
38
|
# :nodoc:
|
@@ -33,9 +41,14 @@ module Caffeinate
|
|
33
41
|
end
|
34
42
|
|
35
43
|
module ClassMethods
|
44
|
+
# A collection of Drip objects associated with a given `Caffeinate::Dripper`
|
45
|
+
def drip_collection
|
46
|
+
@drip_collection ||= DripCollection.new(self)
|
47
|
+
end
|
48
|
+
|
36
49
|
# A collection of Drip objects associated with a given `Caffeinate::Dripper`
|
37
50
|
def drips
|
38
|
-
|
51
|
+
drip_collection.values
|
39
52
|
end
|
40
53
|
|
41
54
|
# Register a drip on the Dripper
|
@@ -50,6 +63,7 @@ module Caffeinate
|
|
50
63
|
def drip(action_name, options = {}, &block)
|
51
64
|
options.assert_valid_keys(:mailer_class, :step, :delay, :using, :mailer)
|
52
65
|
options[:mailer_class] ||= options[:mailer] || defaults[:mailer_class]
|
66
|
+
options[:using] ||= defaults[:using]
|
53
67
|
options[:step] ||= drips.size + 1
|
54
68
|
|
55
69
|
if options[:mailer_class].nil?
|
@@ -57,7 +71,7 @@ module Caffeinate
|
|
57
71
|
end
|
58
72
|
raise ArgumentError, "You must define :delay in the options for :#{action_name}" if options[:delay].nil?
|
59
73
|
|
60
|
-
|
74
|
+
drip_collection.register(action_name, options, &block)
|
61
75
|
end
|
62
76
|
end
|
63
77
|
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,7 +2,7 @@
|
|
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)
|
@@ -11,14 +11,21 @@ module Caffeinate
|
|
11
11
|
|
12
12
|
# Delivers the next_caffeinate_mailer for the campaign's subscribers.
|
13
13
|
#
|
14
|
+
# Handles with batches based on batch_size.
|
15
|
+
#
|
14
16
|
# OrderDripper.new.perform!
|
15
17
|
#
|
16
18
|
# @return nil
|
17
19
|
def perform!
|
18
|
-
|
19
|
-
|
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
|
20
26
|
end
|
21
|
-
|
27
|
+
run_callbacks(:after_process, self)
|
28
|
+
nil
|
22
29
|
end
|
23
30
|
|
24
31
|
module ClassMethods
|
@@ -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,
|
34
|
-
caffeinate_campaign.subscribe(subscriber,
|
33
|
+
def subscribe(subscriber, **args)
|
34
|
+
caffeinate_campaign.subscribe(subscriber, **args)
|
35
35
|
end
|
36
36
|
|
37
37
|
# :nodoc:
|
data/lib/caffeinate/engine.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
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
|
-
#
|
8
|
+
# Adds Caffeinate to Rails
|
8
9
|
class Engine < ::Rails::Engine
|
9
10
|
isolate_namespace Caffeinate
|
11
|
+
config.eager_load_namespaces << Caffeinate
|
10
12
|
|
11
13
|
ActiveSupport.on_load(:action_mailer) do
|
12
14
|
include ::Caffeinate::ActionMailer::Extension
|
@@ -17,5 +19,9 @@ module Caffeinate
|
|
17
19
|
ActiveSupport.on_load(:active_record) do
|
18
20
|
extend ::Caffeinate::ActiveRecord::Extension
|
19
21
|
end
|
22
|
+
|
23
|
+
ActiveSupport.on_load(:action_view) do
|
24
|
+
ApplicationHelper.include ::Caffeinate::Helpers
|
25
|
+
end
|
20
26
|
end
|
21
27
|
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
|
data/lib/caffeinate/version.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Caffeinate
|
4
4
|
module Generators
|
5
|
-
#
|
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
|
File without changes
|
@@ -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.
|
4
|
+
version: 0.2.1
|
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-
|
11
|
+
date: 2020-11-28 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/
|
128
|
-
- app/views/
|
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,25 +135,29 @@ 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
|
150
154
|
- lib/caffeinate/dripper/subscriber.rb
|
151
155
|
- lib/caffeinate/engine.rb
|
156
|
+
- lib/caffeinate/helpers.rb
|
157
|
+
- lib/caffeinate/url_helpers.rb
|
152
158
|
- lib/caffeinate/version.rb
|
153
159
|
- lib/generators/caffeinate/install_generator.rb
|
154
|
-
- lib/generators/caffeinate/templates/
|
160
|
+
- lib/generators/caffeinate/templates/application_dripper.rb
|
155
161
|
- lib/generators/caffeinate/templates/caffeinate.rb
|
156
162
|
homepage: https://github.com/joshmn/caffeinate
|
157
163
|
licenses:
|
@@ -1 +0,0 @@
|
|
1
|
-
<h5>You have been unsubscribed.</h5>
|
@@ -1,12 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Caffeinate
|
4
|
-
module ActionMailer
|
5
|
-
module Helpers
|
6
|
-
def caffeinate_unsubscribe_url(caffeinate_campaign_subscription, **options)
|
7
|
-
opts = (::ActionMailer::Base.default_url_options || {}).merge(options)
|
8
|
-
Caffeinate::Engine.routes.url_helpers.caffeinate_unsubscribe_url(token: caffeinate_campaign_subscription.token, **opts)
|
9
|
-
end
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|