Skip to content

Instantly share code, notes, and snippets.

@kidpollo
Created August 1, 2012 22:18
Show Gist options
  • Save kidpollo/3231215 to your computer and use it in GitHub Desktop.
Save kidpollo/3231215 to your computer and use it in GitHub Desktop.
Generic pattern for handling account trial states and notifications
tn = TrialNotification.new(@account, Notifier)
tn.assm_current_state
# :trial
tn.send_first_notification!
tn.assm_current_state
# :first_notification_sent
tn.send_second_notification!
tn.assm_current_state
# :second_notification_sent
tn.send_third_notification!
tn.assm_current_state
# :third_notification_sent
tn.payment_information_provided!
tn.assm_current_state
# :converted_to_paying
class TrialNotification
include AASM
aasm_initial_state :trial
aasm_state :trial
aasm_state :first_notification_sent, :enter => :deliver_first_notification
aasm_state :second_notification_sent, :enter => :deliver_second_notification
aasm_state :third_notification_sent, :enter => :deliver_third_notification
aasm_state :trial_period_ended
aasm_state :converted_to_paying
aasm_event(:send_first_notification) do
transitions(:to => :first_notification_sent, :from => [:trial])
end
aasm_event(:send_second_notification) do
transitions(:to => :second_notification_sent, :from => [:first_notification_sent])
end
aasm_event(:send_third_notification) do
transitions(:to => :third_notification_sent, :from => [:second_notification_sent])
end
aasm_event(:no_payment_info_provided) do
transitions(:to => :trial_period_ended, :from => [:third_notification_sent])
end
aasm_event(:payment_information_provided) do
transitions(:to => :converted_to_paying, :from => [:new, :first_notification_sent,
:second_notification_sent,
:third_notification_sent])
end
def initialize(persister, notifier)
@persister = persister
@notifier = notifier
end
private
def aasm_read_state
state = @persister.trial_notification_state
if state
state.to_sym
else
:trial
end
end
def aasm_write_state(state)
# persist ignoring callbacks and validations to
# avoid dealing with active_record madness
if ::ActiveRecord::VERSION::MAJOR <= 2 && ::ActiveRecord::VERSION::MINOR > 0
@persister.trial_notification_state = state.to_s
@persister.send(:update_without_callbacks)
elsif ::ActiveRecord::VERSION::MINOR > 0
@persister.update_column(:trial_notification_state, state.to_s)
end
end
def deliver_first_notification
@notifier.deliver_first_notification
end
def deliver_second_notification
@notifier.deliver_second_notification
end
def deliver_third_notification
@notifier.deliver_third_notification
end
end
require 'spec_fast_helper'
require 'aasm'
require 'app/models/trial_notification'
describe TrialNotification do
before do
@persister = Object.new
@notifier = Object.new
@trial_notification = TrialNotification.new(@persister,@notifier)
#dont worry about persistanse
stub(@trial_notification).aasm_write_state {true}
end
describe "initial state" do
it "should default to :trial if false" do
stub(@persister).trial_notification_state {false}
@trial_notification.aasm_current_state.should == :trial
end
it "should default to :trial if null" do
stub(@persister).trial_notification_state {nil}
@trial_notification.aasm_current_state.should == :trial
end
it "should not allow other states" do
stub(@persister).trial_notification_state {:any_other_state}
lambda{@trial_notification.send_first_notification!}.should raise_error(AASM::UndefinedState)
end
end
describe 'transitions' do
it 'should transition from :trial to :first_notification_sent' do
stub(@persister).trial_notification_state {:trial}
mock(@notifier).deliver_first_notification {true}
@trial_notification.send_first_notification!.should be_true
end
it 'should transition from :first_notification_sent to :second_notification_sent' do
stub(@persister).trial_notification_state {:first_notification_sent}
mock(@notifier).deliver_second_notification {true}
@trial_notification.send_second_notification!.should be_true
end
it 'should transition from :second_notification_sent to :third_notification_sent' do
stub(@persister).trial_notification_state {:second_notification_sent}
mock(@notifier).deliver_third_notification {true}
@trial_notification.send_third_notification!.should be_true
end
it 'should transition from :third_notification_sent to :trial_period_ended' do
stub(@persister).trial_notification_state {:third_notification_sent}
@trial_notification.no_payment_info_provided!.should be_true
end
[:trial,:first_notification_sent,:second_notification_sent,:third_notification_sent].each do |state|
it "should be able to transition from #{state} to :converted_to_paying" do
stub(@persister).trial_notification_state {state}
lambda{@trial_notification.converted_to_paying!}.should be_true
end
end
#pretty much the same logic for sencond and third states so its not going to be tested
[:first_notification_sent,:second_notification_sent,:third_notification_sent,:trial_period_ended,:converted_to_paying].each do |state|
it "should not transition from any other but #{state} to :first_notification_sent" do
stub(@persister).trial_notification_state {state}
lambda{@trial_notification.send_first_notification!}.should raise_error(AASM::InvalidTransition)
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment