caffeinate 0.1.1 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|