hat-trick 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,35 @@
1
+ require 'hat_trick/step_definition'
2
+
3
+ module HatTrick
4
+ class Step
5
+ attr_reader :step_def, :wizard
6
+ attr_writer :skipped
7
+ attr_accessor :next_step
8
+
9
+ delegate :name, :fieldset, :buttons, :repeat_of, :to_sym, :to_s, :as_json,
10
+ :run_after_callback, :run_before_callback, :repeat?,
11
+ :run_include_data_callback, :include_data_key, :to => :step_def
12
+
13
+ def initialize(step_def, wizard)
14
+ @step_def = step_def
15
+ @wizard = wizard
16
+ @skipped = step_def.skipped?
17
+ end
18
+
19
+ def session
20
+ wizard.session
21
+ end
22
+
23
+ def skipped?
24
+ not visited? and @skipped
25
+ end
26
+
27
+ def visited?
28
+ session["hat-trick.steps_visited"].include? self.to_sym
29
+ end
30
+
31
+ def visited=(_visited)
32
+ session["hat-trick.steps_visited"] << self.to_sym
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,104 @@
1
+ module HatTrick
2
+ class StepDefinition
3
+ attr_reader :callbacks, :include_data_key
4
+ attr_accessor :name, :fieldset, :buttons, :repeat_of
5
+ attr_writer :skipped
6
+
7
+ def initialize(args={})
8
+ args.each_pair do |k,v|
9
+ setter = "#{k}="
10
+ if respond_to?(setter)
11
+ send(setter,v)
12
+ end
13
+ end
14
+ @callbacks = {}
15
+ @buttons = {}
16
+ @skipped = false
17
+ end
18
+
19
+ def initialize_copy(source)
20
+ @callbacks = source.callbacks.dup
21
+ @buttons = source.buttons.dup
22
+ @skipped = false
23
+ @repeat_of = source
24
+ end
25
+
26
+ def name=(name)
27
+ @name = name.to_sym
28
+ end
29
+
30
+ def fieldset=(fieldset)
31
+ @fieldset = fieldset.to_sym
32
+ end
33
+
34
+ def fieldset
35
+ @fieldset or name
36
+ end
37
+
38
+ def repeat?
39
+ !repeat_of.nil?
40
+ end
41
+
42
+ def skipped?
43
+ @skipped
44
+ end
45
+
46
+ def to_s
47
+ str = "<HatTrick::Step:0x%08x :#{name}" % (object_id * 2)
48
+ str += " fieldset: #{fieldset}" if fieldset != name
49
+ str += ">"
50
+ str
51
+ end
52
+
53
+ def to_sym
54
+ name.to_sym
55
+ end
56
+
57
+ def as_json(options = nil)
58
+ json = { :name => name, :fieldset => fieldset }
59
+ json[:repeatOf] = repeat_of.as_json if repeat?
60
+ json[:buttons] = buttons unless buttons.empty?
61
+ json
62
+ end
63
+
64
+ def before_callback=(blk)
65
+ callbacks[:before] = blk
66
+ end
67
+
68
+ def after_callback=(blk)
69
+ callbacks[:after] = blk
70
+ end
71
+
72
+ def include_data=(hash)
73
+ callbacks[:include_data] = hash.values.first
74
+ @include_data_key = hash.keys.first
75
+ end
76
+
77
+ def run_before_callback(context, wizard_dsl, model)
78
+ run_callback(:before, context, wizard_dsl, model)
79
+ end
80
+
81
+ def run_include_data_callback(context, wizard_dsl, model)
82
+ run_callback(:include_data, context, wizard_dsl, model)
83
+ end
84
+
85
+ def run_after_callback(context, wizard_dsl, model)
86
+ run_callback(:after, context, wizard_dsl, model)
87
+ end
88
+
89
+ protected
90
+
91
+ def run_callback(type, context, *args)
92
+ callback = callbacks[type.to_sym]
93
+ if callback && callback.is_a?(Proc)
94
+ if callback.arity > 1
95
+ context.instance_exec args[0], args[1], &callback
96
+ elsif callback.arity == 1
97
+ context.instance_exec args[0], &callback
98
+ else
99
+ context.instance_eval &callback
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,3 @@
1
+ module HatTrick
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,176 @@
1
+ require 'hat_trick/controller_hooks'
2
+ require 'hat_trick/wizard_steps'
3
+
4
+ module HatTrick
5
+ class Wizard
6
+ include WizardSteps
7
+
8
+ attr_accessor :controller, :model
9
+ attr_reader :current_step, :wizard_def, :wizard_dsl_context, :steps
10
+
11
+ def initialize(wizard_def)
12
+ @wizard_def = wizard_def
13
+ @wizard_dsl_context = DSL::WizardContext.new(@wizard_def)
14
+ @wizard_dsl_context.wizard = self
15
+ @steps = @wizard_def.steps.map { |s| HatTrick::Step.new(s, self) }
16
+ end
17
+
18
+ def controller=(new_controller)
19
+ @controller = new_controller
20
+ end
21
+
22
+ def current_step=(_step)
23
+ raise "Don't set current_step to nil" if _step.nil?
24
+ step = find_step(_step)
25
+ raise "#{step} is not a member of this wizard" unless step
26
+ @current_step = step
27
+ end
28
+
29
+ def session
30
+ controller.session unless controller.nil?
31
+ end
32
+
33
+ def model_created?
34
+ !(model.nil? || (model.respond_to?(:new_record?) && model.new_record?))
35
+ end
36
+
37
+ def current_form_url
38
+ model_created? ? update_url : create_url
39
+ end
40
+
41
+ def current_form_method
42
+ model_created? ? 'put' : 'post'
43
+ end
44
+
45
+ def create_url
46
+ controller.url_for(:controller => controller.controller_name,
47
+ :action => 'create', :only_path => true)
48
+ end
49
+
50
+ def update_url
51
+ if model_created?
52
+ controller.url_for(:controller => controller.controller_name,
53
+ :action => 'update', :id => model,
54
+ :only_path => true)
55
+ else
56
+ nil
57
+ end
58
+ end
59
+
60
+ def next_step
61
+ step = find_next_step
62
+ while step.skipped? do
63
+ step = find_step_after(step)
64
+ end
65
+ step
66
+ end
67
+
68
+ def previously_visited_step
69
+ steps_before(current_step).select { |s| s.visited? }.last
70
+ end
71
+
72
+ def skip_step(_step)
73
+ step = find_step(_step)
74
+ step.skipped = true
75
+ end
76
+
77
+ def started?
78
+ !current_step.nil?
79
+ end
80
+
81
+ def start
82
+ session["hat-trick.steps_visited"] = []
83
+ self.current_step = first_step
84
+ run_before_callback
85
+ end
86
+
87
+ def finish
88
+ # Do something here
89
+ # Such as: Force the wizard to display the "done" page
90
+ Rails.logger.info "WIZARD FINISHED!"
91
+ end
92
+
93
+ def run_before_callback(step=current_step)
94
+ step.run_before_callback(controller, wizard_dsl_context, model)
95
+ end
96
+
97
+ def run_after_callback(step=current_step)
98
+ step.run_after_callback(controller, wizard_dsl_context, model)
99
+ end
100
+
101
+ def advance_step_with_debugger
102
+ require 'ruby-debug'
103
+ debugger
104
+ advance_step
105
+ end
106
+
107
+ def advance_step(next_step_name=nil)
108
+ # clean up current step
109
+ current_step.visited = true
110
+ run_after_callback
111
+
112
+ # see if there is a requested next step
113
+ requested_next_step = find_step(next_step_name) unless next_step_name.nil?
114
+
115
+ # finish if we're on the last step
116
+ if current_step == last_step && !requested_next_step
117
+ finish
118
+ else # we're not on the last step
119
+ if requested_next_step
120
+ Rails.logger.info "Force advancing to step: #{requested_next_step}"
121
+ self.current_step = requested_next_step
122
+ run_before_callback
123
+ # if the step was explicitly requested, we ignore #skipped?
124
+ else
125
+ Rails.logger.info "Advancing to step: #{next_step}"
126
+ self.current_step = next_step
127
+ run_before_callback
128
+ # Running the before callback may have marked current_step as skipped
129
+ while current_step.skipped?
130
+ self.current_step = next_step
131
+ run_before_callback
132
+ end
133
+ end
134
+ end
135
+ end
136
+
137
+ def include_data
138
+ return {} unless model
139
+ inc_data = current_step.run_include_data_callback(controller, wizard_dsl_context, model)
140
+ data_key = current_step.include_data_key
141
+ { data_key.to_s.camelize(:lower) => camelize_hash_keys(inc_data) }
142
+ end
143
+
144
+ def alias_action_methods
145
+ action_methods = controller.action_methods.reject do |m|
146
+ /^render/ =~ m.to_s ||
147
+ controller.respond_to?("#{m}_with_hat_trick", :include_private)
148
+ end
149
+ HatTrick::ControllerHooks.def_action_method_aliases(action_methods)
150
+ action_methods.each do |meth|
151
+ controller.class.send(:alias_method_chain, meth, :hat_trick)
152
+ controller.class.send(:private, "#{meth}_without_hat_trick")
153
+ end
154
+ end
155
+
156
+ private
157
+
158
+ def find_next_step
159
+ find_step(current_step.next_step) or find_step_after(current_step)
160
+ end
161
+
162
+ def find_step_after(step)
163
+ next_path_step = step_after step
164
+ next_path_step or find_next_active_step(step)
165
+ end
166
+
167
+ def camelize_hash_keys(_hash)
168
+ return nil if _hash.nil?
169
+ hash = {}
170
+ _hash.each do |k,v|
171
+ hash[k.to_s.camelize(:lower)] = v
172
+ end
173
+ hash
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,22 @@
1
+ require 'hat_trick/step'
2
+ require 'hat_trick/wizard_steps'
3
+ require 'hat_trick/wizard'
4
+
5
+ module HatTrick
6
+ class WizardDefinition
7
+ include WizardSteps
8
+
9
+ def initialize
10
+ @steps = []
11
+ end
12
+
13
+ def get_wizard(controller)
14
+ controller.send(:ht_wizard) or (
15
+ wizard = HatTrick::Wizard.new(self)
16
+ wizard.controller = controller
17
+ wizard.alias_action_methods
18
+ wizard
19
+ )
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,95 @@
1
+ module HatTrick
2
+ module WizardSteps
3
+ include Enumerable
4
+ attr_reader :steps
5
+
6
+ delegate :each, :empty?, :first, :last, :to => :steps
7
+
8
+ alias_method :each_step, :each
9
+ alias_method :to_ary, :steps
10
+ alias_method :first_step, :first
11
+ alias_method :last_step, :last
12
+
13
+ def find_step(step)
14
+ return nil if step.nil?
15
+ if step.is_a?(HatTrick::Step) || step.is_a?(HatTrick::StepDefinition)
16
+ find { |s| s.equal? step }
17
+ else
18
+ find { |s| s.name == step.to_sym }
19
+ end
20
+ end
21
+
22
+ def step_after(_step)
23
+ steps_after(_step).first
24
+ end
25
+
26
+ def steps_after(_step)
27
+ step = find_step(_step)
28
+ return [] unless step
29
+ after_index = steps.index(step) + 1
30
+ steps[after_index .. -1]
31
+ end
32
+
33
+ def step_before(_step)
34
+ steps_before(_step).last
35
+ end
36
+
37
+ def steps_before(_step)
38
+ step = find_step(_step)
39
+ return [] unless step
40
+ before_index = steps.index(step) - 1
41
+ steps[0 .. before_index]
42
+ end
43
+
44
+ def add_step(step, args={})
45
+ if step.is_a?(HatTrick::Step) || step.is_a?(HatTrick::StepDefinition)
46
+ new_step = step
47
+ else
48
+ step_args = args.merge(:name => step)
49
+ new_step = HatTrick::StepDefinition.new(step_args)
50
+ end
51
+
52
+ steps << new_step
53
+
54
+ new_step
55
+ end
56
+
57
+ def delete_step(_step)
58
+ step = find_step(_step)
59
+ steps.delete(step)
60
+ end
61
+
62
+ def replace_step(_old_step, _new_step)
63
+ old_step = find_step(_old_step)
64
+ old_index = steps.index(old_step)
65
+ raise ArgumentError, "Couldn't find step #{_old_step}" unless old_step
66
+ new_step_in_wizard = find_step(_new_step)
67
+ if new_step_in_wizard
68
+ # new_step is already in the wizard
69
+ return move_step(new_step_in_wizard, old_index)
70
+ end
71
+
72
+ if _new_step.is_a?(HatTrick::Step) || _new_step.is_a?(HatTrick::StepDefinition)
73
+ new_step = _new_step
74
+ else
75
+ new_step = HatTrick::StepDefinition.new(:name => _new_step)
76
+ end
77
+
78
+ steps.delete_at(old_index)
79
+ steps.insert(old_index, new_step)
80
+ new_step
81
+ end
82
+
83
+ def move_step(step, index)
84
+ raise ArgumentError, "#{step} isn't in this wizard" unless steps.include?(step)
85
+ current_index = steps.index(step)
86
+ unless index < current_index
87
+ raise ArgumentError, "#{step} has index #{current_index}; must be >= #{index}"
88
+ end
89
+
90
+ while steps.index(step) > index
91
+ steps.delete_at(index)
92
+ end
93
+ end
94
+ end
95
+ end
data/lib/hat_trick.rb ADDED
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
+ require 'hat-trick'
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe HatTrick::Step do
4
+ subject { HatTrick::Step.new(name: "test_step") }
5
+
6
+ let(:wizard_def) { HatTrick::WizardDefinition.new }
7
+
8
+ it { should be_a(HatTrick::Step) }
9
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe HatTrick::WizardDefinition do
4
+ subject { HatTrick::WizardDefinition.new }
5
+
6
+ it { should be_empty }
7
+
8
+ describe "#add_step" do
9
+ it "should accept a symbol for the step name" do
10
+ step = subject.add_step(:step1)
11
+ step.name.should == :step1
12
+ end
13
+
14
+ it "should add a step to the wizard def" do
15
+ subject.add_step(:step1)
16
+ subject.should have(1).step
17
+ end
18
+
19
+ it "should update the last_step attr" do
20
+ step = subject.add_step(:step1)
21
+ subject.last_step.should == step
22
+ end
23
+ end
24
+
25
+ describe "#delete_step" do
26
+ it "should remove the step from the wizard def" do
27
+ step = subject.add_step(:goner)
28
+ subject.delete_step(step)
29
+ subject.steps.should_not include(step)
30
+ end
31
+
32
+ it "should update the the links between surrounding steps" do
33
+ end
34
+ end
35
+
36
+ describe "#replace_step" do
37
+ end
38
+
39
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe HatTrick::Wizard do
4
+ subject {
5
+ wiz_def = HatTrick::WizardDefinition.new
6
+ wiz_def.add_step(:step1)
7
+ wiz_def.add_step(:step2)
8
+ wiz_def.add_step(:step3)
9
+ wiz_def.add_step(:step4)
10
+ wiz_def.add_step(:step5)
11
+
12
+ HatTrick::Wizard.new(wiz_def)
13
+ }
14
+
15
+ before :each do
16
+ subject.start
17
+ end
18
+
19
+ describe "advancing steps" do
20
+ it "should go from step1 to step2" do
21
+ subject.current_step.to_sym.should == :step1
22
+ subject.advance_step
23
+ subject.current_step.to_sym.should == :step2
24
+ end
25
+ end
26
+
27
+ describe "skipping steps" do
28
+ it "should skip step2 when requested" do
29
+ subject.skip_step :step2
30
+ subject.advance_step
31
+ subject.current_step.to_sym.should == :step3
32
+ end
33
+
34
+ it "should skip steps 2 & 3 when requested" do
35
+ subject.skip_step :step2
36
+ subject.skip_step :step3
37
+ subject.advance_step
38
+ subject.current_step.to_sym.should == :step4
39
+ end
40
+ end
41
+
42
+ describe "repeating steps" do
43
+ it "should repeat step 2 when requested" do
44
+ end
45
+ end
46
+
47
+ describe "setting explicit next steps" do
48
+ it "should advance to the requested next step when one is set" do
49
+ subject.steps.first.next_step = :step4
50
+ subject.advance_step
51
+ subject.current_step.to_sym.should == :step4
52
+ end
53
+ end
54
+
55
+ describe "#previously_visited_step" do
56
+ it "should return the most recently visited step" do
57
+ subject.steps[0].visited = true
58
+ subject.steps[1].visited = true
59
+ subject.steps[3].visited = true
60
+ subject.current_step = :step5
61
+ subject.previously_visited_step.to_sym.should == :step4
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,33 @@
1
+ ENV["RAILS_ENV"] ||= 'test'
2
+ require 'bundler/setup'
3
+ require 'logger'
4
+
5
+ module Rails
6
+ def self.root
7
+ Pathname.new(File.expand_path('../..', __FILE__))
8
+ end
9
+
10
+ def self.logger
11
+ @logger ||= ::Logger.new(STDOUT).tap { |l| l.level = ::Logger::ERROR }
12
+ end
13
+
14
+ class Engine
15
+ def self.initializer(*args, &block); end
16
+ end
17
+ end
18
+
19
+ module ActionController
20
+ class Base
21
+ end
22
+ end
23
+
24
+ require File.expand_path('../../lib/hat-trick', __FILE__)
25
+ require 'rspec/autorun'
26
+
27
+ # Requires supporting ruby files with custom matchers and macros, etc,
28
+ # in spec/support/ and its subdirectories.
29
+ Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
30
+
31
+ RSpec.configure do |config|
32
+ config.mock_with :mocha
33
+ end