Skip to content

Commit

Permalink
Consolidate flows into FlowMap
Browse files Browse the repository at this point in the history
  • Loading branch information
mgomes committed May 10, 2018
1 parent 53dcfcc commit da13393
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 99 deletions.
1 change: 1 addition & 0 deletions lib/stealth/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def self.load_environment
require_directory("config/initializers")
# Require explicitly to ensure it loads first
require File.join(Stealth.root, 'bot', 'controllers', 'bot_controller')
require File.join(Stealth.root, 'config', 'flow_map')
require_directory("bot")
end

Expand Down
3 changes: 1 addition & 2 deletions lib/stealth/controller/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,7 @@ def get_flow_and_state(session: nil, flow: nil, state: nil)

if flow.present?
if state.blank?
flow_klass = [flow.to_s, 'flow'].join('_').classify.constantize
state = flow_klass.flow_spec.states.keys.first.to_s
state = FlowMap.flow_spec[flow.to_sym].states.keys.first.to_s
end

return flow.to_s, state.to_s
Expand Down
40 changes: 23 additions & 17 deletions lib/stealth/flow/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,41 @@ module Flow
class_methods do
attr_reader :flow_spec

def flow(&specification)
@flow_spec = Specification.new(&specification)
def flow(flow_name, &specification)
@flow_spec = {} unless @flow_spec.present?
@flow_spec[flow_name.to_sym] = Specification.new(&specification)
end
end

included do
attr_accessor :flow_state, :user_id
attr_accessor :flow, :flow_state, :user_id

def current_state
res = spec.states[@flow_state.to_sym] if @flow_state
res || spec.initial_state
res = self.spec.states[@flow_state.to_sym] if @flow_state
res || self.spec.initial_state
end

def spec
# check the singleton class first
class << self
return flow_spec if flow_spec
end
def current_flow
@flow || self.class.flow_spec.keys.first
end

self.class.flow_spec
def spec
self.class.flow_spec[current_flow]
end

def states
self.spec.states.keys
end

def init_state(state)
raise(ArgumentError, 'No state was specified.') if state.blank?

def init(flow:, state:)
new_flow = flow.to_sym
new_state = state.to_sym
unless states.include?(new_state)
raise(Stealth::Errors::InvalidStateTransition, "Unknown state '#{state}' for #{self.class.to_s}")

unless state_exists?(potential_flow: new_flow, potential_state: new_state)
raise(Stealth::Errors::InvalidStateTransition, "Unknown state '#{new_state}' for '#{new_flow}' flow")
end

@flow = new_flow
@flow_state = new_state

self
Expand All @@ -54,7 +56,11 @@ def init_state(state)
private

def flow_and_state
[self.class.to_s, current_state].join("->")
[current_flow, current_state].join("->")
end

def state_exists?(potential_flow:, potential_state:)
self.class.flow_spec[potential_flow].states.include?(potential_state)
end
end

Expand Down
6 changes: 1 addition & 5 deletions lib/stealth/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,7 @@ def self.flow_and_state_from_session_slug(slug:)
def flow
return nil if flow_string.blank?

@flow ||= begin
flow_klass = [flow_string, 'flow'].join('_').classify.constantize
flow = flow_klass.new.init_state(state_string)
flow
end
@flow ||= FlowMap.new.init(flow: flow_string, state: state_string)
end

def state
Expand Down
10 changes: 3 additions & 7 deletions spec/controller/callbacks_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -143,20 +143,16 @@ def run_around_filter
end
end

class FlowTesterFlow
class FlowMap
include Stealth::Flow

flow do
flow :flow_tester do
state :my_action
state :my_action2
state :my_action3
end
end

class OtherFlowTesterFlow
include Stealth::Flow

flow do
flow :other_flow_tester do
state :other_action
state :other_action2
state :other_action3
Expand Down
10 changes: 3 additions & 7 deletions spec/controller/controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,16 @@ def other_action3
end
end

class MrRobotFlow
class FlowMap
include Stealth::Flow

flow do
flow :mr_robot do
state :my_action
state :my_action2
state :my_action3
end
end

class MrTronFlow
include Stealth::Flow

flow do
flow :mr_tron do
state :other_action
state :other_action2
state :other_action3
Expand Down
61 changes: 41 additions & 20 deletions spec/flow/flow_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,57 +5,78 @@

describe Stealth::Flow do

class NewTodoFlow
class CustomFlowMap
include Stealth::Flow

flow do
flow :new_todo do
state :new

state :get_due_date

state :created

state :error
end

flow :hello do
state :say_hello
state :say_oi
end

flow "howdy" do
state :say_howdy
end
end

let(:flow) { NewTodoFlow.new }
let(:flow_map) { CustomFlowMap.new }

describe "inititating with states" do
it "should init a state given a state name" do
flow.init_state(:created)
expect(flow.current_state).to eq :created
flow_map.init(flow: 'new_todo', state: 'created')
expect(flow_map.current_state).to eq :created

flow.init_state('error')
expect(flow.current_state).to eq :error
flow_map.init(flow: 'new_todo', state: 'error')
expect(flow_map.current_state).to eq :error
end

it "should raise an error if an invalid state is specified" do
expect {
flow.init_state(:invalid)
flow_map.init(flow: 'new_todo', state: 'invalid')
}.to raise_error(Stealth::Errors::InvalidStateTransition)
end
end

describe "accessing states" do
it "should start out in the initial state" do
expect(flow.current_state).to eq :new
it "should default to the first flow and state" do
expect(flow_map.current_flow).to eq(:new_todo)
expect(flow_map.current_state).to eq(:new)
end

it "should support comparing states" do
first_state = NewTodoFlow.flow_spec.states[:new]
last_state = NewTodoFlow.flow_spec.states[:error]
first_state = CustomFlowMap.flow_spec[:new_todo].states[:new]
last_state = CustomFlowMap.flow_spec[:new_todo].states[:error]
expect(first_state < last_state).to be true
expect(last_state > first_state).to be true
end

it "should allow every state to be fetched for the class" do
expect(NewTodoFlow.flow_spec.states.length).to eq 4
expect(NewTodoFlow.flow_spec.states.keys).to eq([:new, :get_due_date, :created, :error])
it "should allow every state to be fetched for a flow" do
expect(CustomFlowMap.flow_spec[:new_todo].states.length).to eq 4
expect(CustomFlowMap.flow_spec[:hello].states.length).to eq 2
expect(CustomFlowMap.flow_spec[:new_todo].states.keys).to eq([:new, :get_due_date, :created, :error])
expect(CustomFlowMap.flow_spec[:hello].states.keys).to eq([:say_hello, :say_oi])
end

it "should return the states in an array for a given FlowMap instance" do
expect(flow_map.states).to eq [:new, :get_due_date, :created, :error]
flow_map.init(flow: :hello, state: :say_oi)
expect(flow_map.states).to eq [:say_hello, :say_oi]
end

it "should allow flows to be specified with strings" do
expect(CustomFlowMap.flow_spec[:howdy].states.length).to eq 1
expect(CustomFlowMap.flow_spec[:howdy].states.keys).to eq([:say_howdy])
end

it "should return the states in an array for a given flow instance" do
expect(flow.states).to eq [:new, :get_due_date, :created, :error]
it "should allow FlowMaps to be intialized with strings" do
flow_map.init(flow: "hello", state: "say_oi")
expect(flow_map.states).to eq [:say_hello, :say_oi]
end
end

Expand Down
37 changes: 17 additions & 20 deletions spec/flow/state_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,66 +5,63 @@

describe Stealth::Flow::State do

class NewTodoFlow
class SuperFlowMap
include Stealth::Flow

flow do
flow :new_todo do
state :new

state :get_due_date

state :created, fails_to: :new

state :error
end
end

let(:flow) { NewTodoFlow.new }
let(:flow_map) { SuperFlowMap.new }

describe "flow states" do
it "should convert itself to a string" do
expect(flow.current_state.to_s).to be_a(String)
expect(flow_map.current_state.to_s).to be_a(String)
end

it "should convert itself to a symbol" do
expect(flow.current_state.to_sym).to be_a(Symbol)
expect(flow_map.current_state.to_sym).to be_a(Symbol)
end
end

describe "fails_to" do
it "should be nil for a state that has not specified a fails_to" do
expect(flow.current_state.fails_to).to be_nil
expect(flow_map.current_state.fails_to).to be_nil
end

it "should return the fail_state if a fails_to was specified" do
flow.init_state(:created)
expect(flow.current_state.fails_to).to be_a(Stealth::Flow::State)
expect(flow.current_state.fails_to).to eq :new
flow_map.init(flow: :new_todo, state: :created)
expect(flow_map.current_state.fails_to).to be_a(Stealth::Flow::State)
expect(flow_map.current_state.fails_to).to eq :new
end
end

describe "state incrementing and decrementing" do
it "should increment the state" do
flow.init_state(:get_due_date)
new_state = flow.current_state + 1.state
flow_map.init(flow: :new_todo, state: :get_due_date)
new_state = flow_map.current_state + 1.state
expect(new_state).to eq(:created)
end

it "should decrement the state" do
flow.init_state(:error)
new_state = flow.current_state - 2.states
flow_map.init(flow: :new_todo, state: :error)
new_state = flow_map.current_state - 2.states
expect(new_state).to eq(:get_due_date)
end

it "should return the first state if the decrement is out of bounds" do
flow.init_state(:get_due_date)
new_state = flow.current_state - 5.states
flow_map.init(flow: :new_todo, state: :get_due_date)
new_state = flow_map.current_state - 5.states
expect(new_state).to eq(:new)
end

it "should return the last state if the increment is out of bounds" do
flow.init_state(:created)
new_state = flow.current_state + 5.states
flow_map.init(flow: :new_todo, state: :created)
new_state = flow_map.current_state + 5.states
expect(new_state).to eq(:error)
end
end
Expand Down

0 comments on commit da13393

Please sign in to comment.