welcome_cycle 0.0.4
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.
- data/.gitignore +11 -0
- data/Gemfile +4 -0
- data/README.md +91 -0
- data/Rakefile +1 -0
- data/lib/generators/welcome_cycle/email/email_generator.rb +41 -0
- data/lib/generators/welcome_cycle/install/install_generator.rb +17 -0
- data/lib/generators/welcome_cycle/install/templates/welcome_cycle.rb +7 -0
- data/lib/generators/welcome_cycle/install/templates/welcome_cycle_mailer.rb +3 -0
- data/lib/welcome_cycle.rb +20 -0
- data/lib/welcome_cycle/config.rb +22 -0
- data/lib/welcome_cycle/driver.rb +11 -0
- data/lib/welcome_cycle/email.rb +78 -0
- data/lib/welcome_cycle/email_register.rb +21 -0
- data/lib/welcome_cycle/version.rb +3 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/welcome_cycle/config_spec.rb +29 -0
- data/spec/welcome_cycle/driver_spec.rb +50 -0
- data/spec/welcome_cycle/email_register_spec.rb +42 -0
- data/spec/welcome_cycle/email_spec.rb +180 -0
- data/spec/welcome_cycle_spec.rb +32 -0
- data/welcome_cycle.gemspec +24 -0
- metadata +97 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# welcome_cycle
|
2
|
+
|
3
|
+
`welcome_cycle` is a ruby gem to help you send out a cycle of emails. Specifically when new users sign up to your app.
|
4
|
+
|
5
|
+
## Gemfile
|
6
|
+
gem 'welcome_cycle'
|
7
|
+
bundle install
|
8
|
+
|
9
|
+
## Install
|
10
|
+
rails generate welcome_cycle:install
|
11
|
+
|
12
|
+
**Creates:**
|
13
|
+
|
14
|
+
`config/initializers/welcome_cycle.rb` - Defines config and your emails.
|
15
|
+
|
16
|
+
`app/mailers/welcome_cycle_mailer.rb` - Mailer methods for each email, each one is passed the recipient object.
|
17
|
+
|
18
|
+
## Config
|
19
|
+
|
20
|
+
The configure block should be specified at the top of `welcome_cycle.rb`
|
21
|
+
|
22
|
+
WelcomeCycle.configure do |c|
|
23
|
+
c.base_class = Subscription
|
24
|
+
c.welcome_cycle_start_date = :trial_started_at
|
25
|
+
c.welcome_cycle_end_date = :trial_ends_at
|
26
|
+
c.before { run_something_special }
|
27
|
+
c.after { run_something_special }
|
28
|
+
end
|
29
|
+
|
30
|
+
**base_class** - The base model in your app to query for email recipients. (E.g Organisation, Subscription, User, Account, etc.)
|
31
|
+
|
32
|
+
**welcome_cycle_start_date** - The date/datetime field that determines the start of your welcome cycle. (If you want to send email 'n' days after the start.)
|
33
|
+
|
34
|
+
**welcome_cycle_end_date** - The date/datetime field that determines the end of the welcome cycle. (If you want to send emails 'n' days around the end.)
|
35
|
+
|
36
|
+
**before** - An optional callback to run before all emails are sent.
|
37
|
+
|
38
|
+
**after** - An optional callback to run after all emails are sent.
|
39
|
+
|
40
|
+
|
41
|
+
## Defining new emails
|
42
|
+
|
43
|
+
rails generate welcome_cycle:email name_for_your_email_here
|
44
|
+
|
45
|
+
The above generator will add an email definition to `welcome_cycle.rb`, add the mailer method to `welcome_cycle_mailer.rb` and create the corresponding text and html erb templates.
|
46
|
+
|
47
|
+
### Options for each email
|
48
|
+
|
49
|
+
**days_into_cycle** – The days on which you would like the email to be sent after signing up. E.g.: 3 = Three days after the sign up.
|
50
|
+
|
51
|
+
**days_offset_from_cycle_end** - The days on which you like the email to go out around the cycle/trial end.
|
52
|
+
|
53
|
+
* positive numbers: Number of days *before* the welcome cycle ends.
|
54
|
+
* negative number: Number of days *after* the welcome cycle ends.
|
55
|
+
|
56
|
+
**scope** - An optional arel chain that allows you restrict the recipients to any conditions you like.
|
57
|
+
|
58
|
+
* With some simple conditions you can easily set up email variations. E.g. have they performed a certain task? Have they subscribed? Etc.
|
59
|
+
|
60
|
+
You **must** specify days_into_cycle or days_offset_from_cycle_end.
|
61
|
+
|
62
|
+
### Example email definitions:
|
63
|
+
|
64
|
+
WelcomeCycle::Email.new("We miss you") do
|
65
|
+
days 7, 14, 21
|
66
|
+
scope do
|
67
|
+
where('last_login = ?', nil)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
WelcomeCycle::Email.new("Trial ends soon!") do
|
72
|
+
days -5
|
73
|
+
scope do
|
74
|
+
where(:subscribed_at => false)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
|
80
|
+
## Sending the emails
|
81
|
+
Set your favourite scheduler to run the WelcomeCycle::Driver.run daily. E.g. using CRON to send them everyday at 6am:
|
82
|
+
|
83
|
+
0 6 * * * cd /path/to/you/app && rails runner -e production 'WelcomeCycle::Driver.run'
|
84
|
+
|
85
|
+
## Notes
|
86
|
+
1. The email templates and corresponding methods live inside your app.
|
87
|
+
2. Ironically welcome_cycle is not designed to send an actual welcome_email! You probably want do that immediately when people sign-up. Feel free to use the provided mailer class.
|
88
|
+
3. We'll try our best to stick to semantic versioning (http://semver.org/)
|
89
|
+
|
90
|
+
## TODO
|
91
|
+
* Work out a clean way of ignoring the initializer unless rails runner is being used.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module WelcomeCycle
|
2
|
+
module Generators
|
3
|
+
class EmailGenerator < Rails::Generators::NamedBase
|
4
|
+
|
5
|
+
source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
|
6
|
+
|
7
|
+
def register_new_mail
|
8
|
+
append_to_file 'config/initializers/welcome_cycle.rb' do
|
9
|
+
<<-EOS
|
10
|
+
|
11
|
+
|
12
|
+
WelcomeCycle::Email.new("#{file_name.gsub(/_/, ' ').capitalize}") do
|
13
|
+
days_into_cycle 5, 10 # Send on 5 and 10 days into trial
|
14
|
+
days_offset_from_cycle_end -5, 5 # Send 5 days before and 5 days after the cycle ends
|
15
|
+
scope do
|
16
|
+
# send if these Arel conditions are met
|
17
|
+
end
|
18
|
+
end
|
19
|
+
EOS
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_new_mailer_action
|
24
|
+
gsub_file 'app/mailers/welcome_cycle_mailer.rb', '# your actions here', ''
|
25
|
+
inject_into_class 'app/mailers/welcome_cycle_mailer.rb', WelcomeCycleMailer do
|
26
|
+
<<-EOS
|
27
|
+
|
28
|
+
def #{file_name}(recipient)
|
29
|
+
mail(:to => recipient.email, :subject => 'Your subject here')
|
30
|
+
end
|
31
|
+
EOS
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_new_mailer_templates
|
36
|
+
create_file "app/views/welcome_cycle_mailer/#{file_name}.text.erb"
|
37
|
+
create_file "app/views/welcome_cycle_mailer/#{file_name}.html.erb"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module WelcomeCycle
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
|
5
|
+
source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
|
6
|
+
|
7
|
+
def copy_welcome_cycle_mailer
|
8
|
+
copy_file 'welcome_cycle_mailer.rb', File.join('app','mailers','welcome_cycle_mailer.rb')
|
9
|
+
end
|
10
|
+
|
11
|
+
def copy_email_list
|
12
|
+
copy_file 'welcome_cycle.rb', File.join('config','initializers','welcome_cycle.rb')
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
require "welcome_cycle/version"
|
4
|
+
require "welcome_cycle/driver"
|
5
|
+
require "welcome_cycle/email"
|
6
|
+
require "welcome_cycle/email_register"
|
7
|
+
require "welcome_cycle/config"
|
8
|
+
|
9
|
+
module WelcomeCycle
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_accessor :config
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.configure
|
16
|
+
self.config ||= WelcomeCycle::Config.new
|
17
|
+
yield(config)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module WelcomeCycle
|
2
|
+
|
3
|
+
class Config
|
4
|
+
attr_accessor :base_class, :welcome_cycle_start_date, :welcome_cycle_end_date
|
5
|
+
attr_reader :before_run, :after_run
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@welcome_cycle_start_date = :trial_stared_at
|
9
|
+
@welcome_cycle_end_date = :trial_ends_at
|
10
|
+
end
|
11
|
+
|
12
|
+
def before(&block)
|
13
|
+
@before_run = block
|
14
|
+
end
|
15
|
+
|
16
|
+
def after(&block)
|
17
|
+
@after_run = block
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module WelcomeCycle
|
2
|
+
class Driver
|
3
|
+
def self.run
|
4
|
+
raise 'WelcomeCycle is not configured! See the README config section for help.' if WelcomeCycle.config.nil?
|
5
|
+
raise "You must set the 'base_class' option. See README config section." if WelcomeCycle.config.base_class.nil?
|
6
|
+
WelcomeCycle.config.before_run.call if WelcomeCycle.config.before_run
|
7
|
+
WelcomeCycle::EmailRegister.instance.emails.each { |e| e.send_to_recipients! }
|
8
|
+
WelcomeCycle.config.after_run.call if WelcomeCycle.config.after_run
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module WelcomeCycle
|
2
|
+
|
3
|
+
class Email
|
4
|
+
|
5
|
+
def initialize(name, &block)
|
6
|
+
@name = name
|
7
|
+
instance_eval &block
|
8
|
+
raise "You must specify at least one day to send this email by specifying 'days_into_cycle' and/or 'days_offset_from_cycle_end'" if @days_into_cycle.nil? && @days_offset_from_cycle_end.nil?
|
9
|
+
WelcomeCycle::EmailRegister.instance << self
|
10
|
+
end
|
11
|
+
|
12
|
+
def days_into_cycle(*days)
|
13
|
+
raise ArgumentError, "'days_into_cycle' can only contain positive numbers" if days.detect { |d| d <= 0 }
|
14
|
+
raise "When specifying 'days_into_cycle' you must set the 'welcome_cycle_start_date'. See README config section" if WelcomeCycle.config.welcome_cycle_start_date.nil?
|
15
|
+
@days_into_cycle = days
|
16
|
+
end
|
17
|
+
|
18
|
+
def days_offset_from_cycle_end(*days)
|
19
|
+
raise "When specifying 'days_offset_from_cycle_end' you must set the 'welcome_cycle_end_date' config section" if WelcomeCycle.config.welcome_cycle_end_date.nil?
|
20
|
+
@days_offset_from_cycle_end = days
|
21
|
+
end
|
22
|
+
|
23
|
+
def scope(&block)
|
24
|
+
@scope_chain = block
|
25
|
+
end
|
26
|
+
|
27
|
+
def send_to_recipients!
|
28
|
+
recipients.each { |r| deliver(r) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def recipients
|
32
|
+
recipients = WelcomeCycle.config.base_class
|
33
|
+
recipients = recipients.where(date_conditions)
|
34
|
+
@scope_chain.nil? ? recipients.all : recipients.instance_eval(&@scope_chain)
|
35
|
+
end
|
36
|
+
|
37
|
+
def deliver(r)
|
38
|
+
if defined?(Delayed) && (defined?(Delayed::Job) || defined?(Delayed::Worker))
|
39
|
+
deliver_via_delayed_job(r)
|
40
|
+
else
|
41
|
+
deliver_directly(r)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def deliver_via_delayed_job(r)
|
48
|
+
WelcomeCycleMailer.delay.send(template_name, r)
|
49
|
+
end
|
50
|
+
|
51
|
+
def deliver_directly(r)
|
52
|
+
if mail_message_obj = WelcomeCycleMailer.send(template_name, r)
|
53
|
+
mail_message_obj.deliver
|
54
|
+
else
|
55
|
+
raise "Failed to create Mail::Message object from the current template name '#{template_name}'. Check it is specified in your WelcomeCycleMailer."
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def template_name
|
60
|
+
@name.downcase.gsub(/\s/, '_')
|
61
|
+
end
|
62
|
+
|
63
|
+
def date_conditions
|
64
|
+
conditions = ['']
|
65
|
+
[[WelcomeCycle.config.welcome_cycle_start_date, @days_into_cycle], [WelcomeCycle.config.welcome_cycle_end_date, @days_offset_from_cycle_end]].each do |field_name, cycle_days|
|
66
|
+
next if cycle_days.nil?
|
67
|
+
cycle_days.each do |day_in_cycle|
|
68
|
+
conditions[0] << " OR " unless conditions[0].empty?
|
69
|
+
conditions[0] << "date(`#{WelcomeCycle.config.base_class.table_name}`.`#{field_name}`) = ?"
|
70
|
+
conditions << Time.now.utc.to_date - day_in_cycle # Positive: 6th-5.days = 1st / Negative: 25th--5.days = 30th
|
71
|
+
end
|
72
|
+
end
|
73
|
+
conditions
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module WelcomeCycle
|
2
|
+
class EmailRegister
|
3
|
+
|
4
|
+
include Singleton
|
5
|
+
|
6
|
+
attr_accessor :emails
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@emails = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def <<(email)
|
13
|
+
@emails << email
|
14
|
+
end
|
15
|
+
|
16
|
+
def each
|
17
|
+
@emails.each { |email| yield(email) }
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe WelcomeCycle::Config do
|
4
|
+
|
5
|
+
subject { WelcomeCycle::Config.new }
|
6
|
+
|
7
|
+
describe "new config object" do
|
8
|
+
it 'has sensible defaults for the welcome cycle start and end dates' do
|
9
|
+
subject.welcome_cycle_start_date.should eq(:trial_stared_at)
|
10
|
+
subject.welcome_cycle_end_date.should eq(:trial_ends_at)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "attributes" do
|
15
|
+
it 'allows you to set :base_class' do
|
16
|
+
subject.base_class = 'SomeClass'
|
17
|
+
subject.base_class.should eq('SomeClass')
|
18
|
+
end
|
19
|
+
it 'allows you to set :welcome_cycle_start_date' do
|
20
|
+
subject.welcome_cycle_start_date = :signed_up_at
|
21
|
+
subject.welcome_cycle_start_date.should eq(:signed_up_at)
|
22
|
+
end
|
23
|
+
it 'allows you to set :welcome_cycle_end_date' do
|
24
|
+
subject.welcome_cycle_end_date = :close_at
|
25
|
+
subject.welcome_cycle_end_date.should eq(:close_at)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe WelcomeCycle::Driver do
|
4
|
+
|
5
|
+
describe "::run" do
|
6
|
+
let(:email1) { mock('email 1').as_null_object }
|
7
|
+
let(:email2) { mock('email 1').as_null_object }
|
8
|
+
|
9
|
+
context "when WelcomeCycle has not been configured" do
|
10
|
+
it 'raises an error telling you to configure it' do
|
11
|
+
lambda { WelcomeCycle::Driver.run }.should raise_error('WelcomeCycle is not configured! See the README config section for help.')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context "when no base_class is configured" do
|
16
|
+
before { WelcomeCycle.configure {} }
|
17
|
+
it 'raises an error telling you to set the base class' do
|
18
|
+
lambda { WelcomeCycle::Driver.run }.should raise_error("You must set the 'base_class' option. See README config section.")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context "when configured with a base class" do
|
23
|
+
before do
|
24
|
+
WelcomeCycle.configure { |c| c.base_class = mock('class') }
|
25
|
+
WelcomeCycle::EmailRegister.instance << email1
|
26
|
+
WelcomeCycle::EmailRegister.instance << email2
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'calls #send_to_recipients! on each email in the register' do
|
30
|
+
email1.should_receive(:send_to_recipients!)
|
31
|
+
email2.should_receive(:send_to_recipients!)
|
32
|
+
WelcomeCycle::Driver.run
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'callbacks' do
|
37
|
+
it 'calls the before_run callback if one is set' do
|
38
|
+
WelcomeCycle.configure { |c| c.before {} }
|
39
|
+
WelcomeCycle.config.before_run.should_receive(:call)
|
40
|
+
WelcomeCycle::Driver.run
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'calls the after_run callback if one is set' do
|
44
|
+
WelcomeCycle.configure { |c| c.after {} }
|
45
|
+
WelcomeCycle.config.after_run.should_receive(:call)
|
46
|
+
WelcomeCycle::Driver.run
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe WelcomeCycle::EmailRegister do
|
4
|
+
|
5
|
+
subject { WelcomeCycle::EmailRegister.instance }
|
6
|
+
|
7
|
+
after(:each) { WelcomeCycle::EmailRegister.instance.emails = [] }
|
8
|
+
|
9
|
+
describe "attributes" do
|
10
|
+
it 'allows setting/getting :emails' do
|
11
|
+
subject.emails = [1, 2, 3]
|
12
|
+
subject.emails.should eq([1, 2, 3])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "adding emails into the register" do
|
17
|
+
let(:email_register) { WelcomeCycle::EmailRegister.instance }
|
18
|
+
it 'stores them in an array' do
|
19
|
+
subject << "email1"
|
20
|
+
subject << "email2"
|
21
|
+
subject << "email3"
|
22
|
+
subject.emails.should eq(['email1', 'email2', 'email3'])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "each" do
|
27
|
+
let(:email1) { mock('email1') }
|
28
|
+
let(:email2) { mock('email2') }
|
29
|
+
let(:email3) { mock('email2') }
|
30
|
+
|
31
|
+
it 'yields the given block for each email in the register' do
|
32
|
+
subject << email1
|
33
|
+
subject << email2
|
34
|
+
subject << email3
|
35
|
+
email1.should_receive(:some_method)
|
36
|
+
email2.should_receive(:some_method)
|
37
|
+
email3.should_receive(:some_method)
|
38
|
+
subject.emails.each { |e| e.some_method }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe WelcomeCycle::Email do
|
4
|
+
|
5
|
+
let(:today) { Time.now.utc.to_date }
|
6
|
+
|
7
|
+
class WelcomeCycleMailer
|
8
|
+
end
|
9
|
+
|
10
|
+
class Subscription
|
11
|
+
def self.table_name
|
12
|
+
'organisations'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
before(:each) do
|
17
|
+
WelcomeCycle.configure do |c|
|
18
|
+
c.base_class = Subscription
|
19
|
+
c.welcome_cycle_start_date = :trial_started_at
|
20
|
+
c.welcome_cycle_end_date = :trial_ends_at
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
subject do
|
25
|
+
WelcomeCycle::Email.new("Test email") do
|
26
|
+
days_into_cycle 1, 10, 25
|
27
|
+
scope do
|
28
|
+
where('created_at < ?', today)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#initialize" do
|
34
|
+
it 'raises an error if you do not specify any days' do
|
35
|
+
lambda { WelcomeCycle::Email.new("Test email") {} }.should raise_error("You must specify at least one day to send this email by specifying 'days_into_cycle' and/or 'days_offset_from_cycle_end'")
|
36
|
+
end
|
37
|
+
it 'adds itself into the register' do
|
38
|
+
lambda {
|
39
|
+
WelcomeCycle::Email.new("Test email") { days_into_cycle 1 }
|
40
|
+
}.should change(WelcomeCycle::EmailRegister.instance.emails, :size).by(1)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#days_into_cycle" do
|
45
|
+
it 'allows you to set the days the email should be sent after the cycle begins' do
|
46
|
+
subject.days_into_cycle(1,2,3,4,5).should eq([1,2,3,4,5])
|
47
|
+
subject.days_into_cycle(1,2,3).should eq([1,2,3])
|
48
|
+
end
|
49
|
+
it 'raises an ArgumentError if passed day 0' do
|
50
|
+
lambda { subject.days_into_cycle(1, 10, 0, 25) }.should raise_error(ArgumentError, "'days_into_cycle' can only contain positive numbers")
|
51
|
+
end
|
52
|
+
it 'raises an ArgumentError if passed day a negative day' do
|
53
|
+
lambda { subject.days_into_cycle(1, -4, 25) }.should raise_error(ArgumentError, "'days_into_cycle' can only contain positive numbers")
|
54
|
+
end
|
55
|
+
context 'with no welcome_start_date set' do
|
56
|
+
before { WelcomeCycle.config.welcome_cycle_start_date = nil }
|
57
|
+
it 'raises an error' do
|
58
|
+
lambda { subject.days_into_cycle(1) }.should raise_error("When specifying 'days_into_cycle' you must set the 'welcome_cycle_start_date'. See README config section")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#days_offset_from_cycle_end" do
|
64
|
+
it 'allows you to set the days the email should be sent, offset from the cycle beginning' do
|
65
|
+
subject.days_offset_from_cycle_end(-1,0,1).should eq([-1,0,1])
|
66
|
+
end
|
67
|
+
context 'with no welcome_cycle_end_date set' do
|
68
|
+
before { WelcomeCycle.config.welcome_cycle_end_date = nil }
|
69
|
+
it 'raises an error' do
|
70
|
+
lambda { subject.days_offset_from_cycle_end(1, -4, 25)}.should raise_error("When specifying 'days_offset_from_cycle_end' you must set the 'welcome_cycle_end_date' config section")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe '#scope' do
|
76
|
+
it 'allows you to set a further scope for the email recipients' do
|
77
|
+
subject.scope do
|
78
|
+
where('1=1')
|
79
|
+
end.should be_a(Proc)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '#send_to_recipients!' do
|
84
|
+
let(:r1) { mock 'mock recipient 1' }
|
85
|
+
let(:r2) { mock 'mock recipient 2' }
|
86
|
+
let(:r3) { mock 'mock recipient 3' }
|
87
|
+
|
88
|
+
before { subject.stub(:recipients).and_return([r1, r2, r3]) }
|
89
|
+
|
90
|
+
it 'delivers the email to each recipient' do
|
91
|
+
subject.should_receive(:deliver).with(r1)
|
92
|
+
subject.should_receive(:deliver).with(r2)
|
93
|
+
subject.should_receive(:deliver).with(r3)
|
94
|
+
subject.send_to_recipients!
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "#recipients" do
|
99
|
+
let(:mock_orgs) { mock "Orgs" }
|
100
|
+
context "with the email set to go out on day 5 and 10 of the welcome cycle" do
|
101
|
+
let(:email) { WelcomeCycle::Email.new("Test email") { days_into_cycle(5, 10) } }
|
102
|
+
it 'queries the base class for all the records that started the welcome cycle 5 or 10 days ago' do
|
103
|
+
Subscription.should_receive(:where).with(["date(`organisations`.`trial_started_at`) = ? OR date(`organisations`.`trial_started_at`) = ?", today - 5, today - 10]).and_return(mock_orgs)
|
104
|
+
mock_orgs.should_receive(:all)
|
105
|
+
email.recipients
|
106
|
+
end
|
107
|
+
end
|
108
|
+
context "with the email set to go out 9 and 3 days before the welcome cycle ends" do
|
109
|
+
let(:email) { WelcomeCycle::Email.new("Test email") { days_offset_from_cycle_end(-9, -3) } }
|
110
|
+
it 'queries the base class for all the records that are 5/3 days away from ending the welcome cycle' do
|
111
|
+
Subscription.should_receive(:where).with(["date(`organisations`.`trial_ends_at`) = ? OR date(`organisations`.`trial_ends_at`) = ?", today + 9, today + 3]).and_return(mock_orgs)
|
112
|
+
mock_orgs.should_receive(:all)
|
113
|
+
email.recipients
|
114
|
+
end
|
115
|
+
end
|
116
|
+
context "with an extra Arel scope set" do
|
117
|
+
let(:email) do
|
118
|
+
WelcomeCycle::Email.new("Test email") do
|
119
|
+
days_into_cycle(3)
|
120
|
+
days_offset_from_cycle_end(-5)
|
121
|
+
scope do
|
122
|
+
active.where('1=1')
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
it "adds the extra scope to the date based query on the base_class" do
|
127
|
+
mock_active_orgs = mock 'mock active orgs'
|
128
|
+
Subscription.should_receive(:where).with(["date(`organisations`.`trial_started_at`) = ? OR date(`organisations`.`trial_ends_at`) = ?", today - 3, today + 5]).and_return(mock_orgs)
|
129
|
+
mock_orgs.should_receive(:active).and_return(mock_active_orgs)
|
130
|
+
mock_active_orgs.should_receive(:where).with('1=1')
|
131
|
+
email.recipients
|
132
|
+
end
|
133
|
+
context 'with a non-default configuration for the date fields' do
|
134
|
+
before do
|
135
|
+
WelcomeCycle.configure do |c|
|
136
|
+
c.welcome_cycle_start_date = :my_test_start_date
|
137
|
+
c.welcome_cycle_end_date = :my_test_end_date
|
138
|
+
end
|
139
|
+
end
|
140
|
+
it "queries based on the configured field names" do
|
141
|
+
mock_active_orgs = mock 'mock active orgs'
|
142
|
+
Subscription.should_receive(:where).with(["date(`organisations`.`my_test_start_date`) = ? OR date(`organisations`.`my_test_end_date`) = ?", today - 3, today + 5]).and_return(mock_orgs)
|
143
|
+
mock_orgs.should_receive(:active).and_return(mock_active_orgs)
|
144
|
+
mock_active_orgs.should_receive(:where).with('1=1')
|
145
|
+
email.recipients
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe "#deliver" do
|
152
|
+
let(:mock_email) { mock 'email' }
|
153
|
+
let(:mock_recipient) { mock 'recipient' }
|
154
|
+
let(:mock_dj) { mock 'delayed job' }
|
155
|
+
|
156
|
+
context 'without DelayedJob in the project' do
|
157
|
+
it 'calls deliver on the mailer template for this email passing in the recipient' do
|
158
|
+
WelcomeCycleMailer.should_receive(:test_email).with(mock_recipient).and_return(mock_email)
|
159
|
+
mock_email.should_receive(:deliver)
|
160
|
+
subject.deliver(mock_recipient)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context 'with DelayedJob in place' do
|
165
|
+
before do
|
166
|
+
module Delayed
|
167
|
+
class Job
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'queues the email for delivery' do
|
173
|
+
WelcomeCycleMailer.should_receive(:delay).and_return(mock_dj)
|
174
|
+
mock_dj.should_receive(:test_email).with(mock_recipient).and_return(true)
|
175
|
+
subject.deliver(mock_recipient)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe WelcomeCycle do
|
4
|
+
|
5
|
+
describe "::configure" do
|
6
|
+
before do
|
7
|
+
WelcomeCycle.configure do |c|
|
8
|
+
c.base_class = String
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'sets a module attribute :config to a new WelcomeCycle::Config object and sets the attributes specified in the block given' do
|
13
|
+
WelcomeCycle.config.should be_a(WelcomeCycle::Config)
|
14
|
+
WelcomeCycle.config.base_class.should eq(String)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'calling config more than once' do
|
18
|
+
before do
|
19
|
+
WelcomeCycle.configure do |c|
|
20
|
+
c.base_class = String
|
21
|
+
end
|
22
|
+
end
|
23
|
+
it 'does not create a new WelcomeCycle::Config object' do
|
24
|
+
WelcomeCycle::Config.should_receive(:new).never
|
25
|
+
WelcomeCycle.configure do |c|
|
26
|
+
c.base_class = Fixnum
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "welcome_cycle/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "welcome_cycle"
|
7
|
+
s.version = WelcomeCycle::VERSION
|
8
|
+
s.authors = ["Luke Brown", "Chris Stainthorpe"]
|
9
|
+
s.email = ["tsdbrown@gmail.com", "chris@randomcat.co.uk"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Welcome cycle code for SAAS apps}
|
12
|
+
s.description = %q{The idea behind this gem is to abstract out the logic of sending out welcome emails during a free trial.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "welcome_cycle"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency(%q<rails>, [">= 3.0.10"])
|
22
|
+
|
23
|
+
s.add_development_dependency "rspec"
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: welcome_cycle
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Luke Brown
|
9
|
+
- Chris Stainthorpe
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-02-17 00:00:00.000000000Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rails
|
17
|
+
requirement: &2156475920 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 3.0.10
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *2156475920
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: rspec
|
28
|
+
requirement: &2156475500 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *2156475500
|
37
|
+
description: The idea behind this gem is to abstract out the logic of sending out
|
38
|
+
welcome emails during a free trial.
|
39
|
+
email:
|
40
|
+
- tsdbrown@gmail.com
|
41
|
+
- chris@randomcat.co.uk
|
42
|
+
executables: []
|
43
|
+
extensions: []
|
44
|
+
extra_rdoc_files: []
|
45
|
+
files:
|
46
|
+
- .gitignore
|
47
|
+
- Gemfile
|
48
|
+
- README.md
|
49
|
+
- Rakefile
|
50
|
+
- lib/generators/welcome_cycle/email/email_generator.rb
|
51
|
+
- lib/generators/welcome_cycle/install/install_generator.rb
|
52
|
+
- lib/generators/welcome_cycle/install/templates/welcome_cycle.rb
|
53
|
+
- lib/generators/welcome_cycle/install/templates/welcome_cycle_mailer.rb
|
54
|
+
- lib/welcome_cycle.rb
|
55
|
+
- lib/welcome_cycle/config.rb
|
56
|
+
- lib/welcome_cycle/driver.rb
|
57
|
+
- lib/welcome_cycle/email.rb
|
58
|
+
- lib/welcome_cycle/email_register.rb
|
59
|
+
- lib/welcome_cycle/version.rb
|
60
|
+
- spec/spec_helper.rb
|
61
|
+
- spec/welcome_cycle/config_spec.rb
|
62
|
+
- spec/welcome_cycle/driver_spec.rb
|
63
|
+
- spec/welcome_cycle/email_register_spec.rb
|
64
|
+
- spec/welcome_cycle/email_spec.rb
|
65
|
+
- spec/welcome_cycle_spec.rb
|
66
|
+
- welcome_cycle.gemspec
|
67
|
+
homepage: ''
|
68
|
+
licenses: []
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ! '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
requirements: []
|
86
|
+
rubyforge_project: welcome_cycle
|
87
|
+
rubygems_version: 1.8.6
|
88
|
+
signing_key:
|
89
|
+
specification_version: 3
|
90
|
+
summary: Welcome cycle code for SAAS apps
|
91
|
+
test_files:
|
92
|
+
- spec/spec_helper.rb
|
93
|
+
- spec/welcome_cycle/config_spec.rb
|
94
|
+
- spec/welcome_cycle/driver_spec.rb
|
95
|
+
- spec/welcome_cycle/email_register_spec.rb
|
96
|
+
- spec/welcome_cycle/email_spec.rb
|
97
|
+
- spec/welcome_cycle_spec.rb
|