hat-trick 0.0.1 → 0.1.0

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.
@@ -1,8 +1,10 @@
1
1
  require 'hat_trick/model_methods'
2
+ require 'hat_trick/controller_helpers'
2
3
 
3
4
  module HatTrick
4
5
  module ControllerHooks
5
6
  extend ActiveSupport::Concern
7
+ include ControllerHelpers
6
8
 
7
9
  included do
8
10
  alias_method_chain :render, :hat_trick
@@ -10,12 +12,12 @@ module HatTrick
10
12
 
11
13
  def self.def_action_method_aliases(action_methods)
12
14
  action_methods.each do |meth|
13
- Rails.logger.info "Aliasing #{meth}"
15
+ # Rails.logger.info "Aliasing #{meth}"
14
16
  module_eval <<-RUBY_EVAL
15
17
  def #{meth}_with_hat_trick(*args)
16
18
  Rails.logger.info "#{meth}_with_hat_trick called"
17
19
  #{meth}_hook(*args) if respond_to?("#{meth}_hook", :include_private)
18
- common_hook(*args)
20
+ common_hook(*args) if respond_to?(:common_hook, :include_private)
19
21
  #{meth}_without_hat_trick(*args)
20
22
  end
21
23
  private "#{meth}_with_hat_trick"
@@ -26,53 +28,6 @@ module HatTrick
26
28
 
27
29
  private
28
30
 
29
- def params_model_name
30
- params.each do |k,v|
31
- return class_name(k) if v.is_a?(Hash) && is_model?(k)
32
- end
33
- nil
34
- end
35
-
36
- def is_model?(model_name)
37
- begin
38
- class_name(model_name).constantize
39
- rescue NameError
40
- return false
41
- end
42
- true
43
- end
44
-
45
- def class_name(hash_key)
46
- hash_key.to_s.camelize
47
- end
48
-
49
- def model_class
50
- model_name = params_model_name
51
- return nil if model_name.nil?
52
- begin
53
- model_class = params_model_name.constantize
54
- rescue NameError
55
- Rails.logger.error "Could not find model class #{params_model_name.camelize}"
56
- nil
57
- else
58
- model_class
59
- end
60
- end
61
-
62
- def setup_validation_group_for(wizard_step)
63
- klass = model_class
64
- return if klass.nil?
65
- step_name = wizard_step.name
66
- validation_groups = ::ActiveRecord::Base.validation_group_classes[klass] || []
67
- unless validation_groups.include?(step_name)
68
- klass.validation_group(step_name, :fields => params.keys)
69
- end
70
- HatTrick::ModelMethods.set_current_validation_group_for(model_class, step_name)
71
- unless klass.included_modules.include?(HatTrick::ModelMethods)
72
- klass.send(:include, HatTrick::ModelMethods)
73
- end
74
- end
75
-
76
31
  def create_hook(*args)
77
32
  setup_validation_group_for(ht_wizard.current_step)
78
33
  end
@@ -81,13 +36,10 @@ module HatTrick
81
36
  setup_validation_group_for(ht_wizard.current_step)
82
37
  end
83
38
 
84
- def common_hook(*args)
85
- # nothing here for now
86
- end
87
-
88
- def render_with_hat_trick(*args)
89
- if args.first.has_key?(:json)
90
- model = args[0][:json]
39
+ def render_with_hat_trick(*args, &block)
40
+ rendered = args.first
41
+ if rendered && rendered.has_key?(:json)
42
+ model = rendered[:json]
91
43
  ht_wizard.model = model
92
44
  end
93
45
 
@@ -102,17 +54,21 @@ module HatTrick
102
54
  :currentStep => ht_wizard.current_step,
103
55
  }
104
56
 
105
- # this sets the wizard_metadata for the initial page load
106
- gon.wizard_metadata = wizard_metadata
57
+ include_data = ht_wizard.include_data
58
+
59
+ # this sets the gon data (JS hatTrick object) for the initial page load
60
+ gon.metadata = wizard_metadata
61
+ gon.data = include_data
62
+ gon.model = ht_wizard.model
107
63
 
108
- if ht_wizard.model && args[0].has_key?(:json)
109
- # this sets the wizard metadata for subsequent AJAX requests
110
- args[0][:json] = { :formModel => ht_wizard.model,
111
- :wizardMetadata => wizard_metadata }
112
- args[0][:json].merge! ht_wizard.include_data
64
+ # this sets the wizard metadata for subsequent AJAX requests
65
+ if ht_wizard.model && rendered.has_key?(:json)
66
+ args[0][:json] = { :model => ht_wizard.model,
67
+ :metadata => wizard_metadata }
68
+ args[0][:json].merge!( :data => include_data )
113
69
  end
114
70
 
115
- render_without_hat_trick(*args)
71
+ render_without_hat_trick(*args, &block)
116
72
  end
117
73
  end
118
74
  end
data/lib/hat_trick/dsl.rb CHANGED
@@ -1,75 +1,74 @@
1
1
  require 'hat_trick/wizard_definition'
2
2
  require 'hat_trick/controller_hooks'
3
+ require 'hat_trick/config'
3
4
 
4
5
  module HatTrick
5
6
  module DSL
6
7
  extend ActiveSupport::Concern
7
8
 
8
- module ClassMethods
9
- def wizard(&block)
10
- if block_given?
11
- include HatTrick::DSL::ControllerInstanceMethods
12
- include HatTrick::ControllerHooks
9
+ attr_accessor :ht_wizard, :configure_callback, :_ht_config
13
10
 
14
- @wizard_def = HatTrick::WizardDefinition.new
11
+ delegate :model, :previously_visited_step, :to => :ht_wizard
15
12
 
16
- @wizard_dsl = HatTrick::DSL::WizardContext.new(@wizard_def)
17
- @wizard_dsl.instance_eval &block
13
+ included do
14
+ alias_method_chain :initialize, :hat_trick
15
+ end
18
16
 
19
- else
20
- raise ArgumentError, "wizard called without a block"
21
- end
17
+ def initialize_with_hat_trick(*args)
18
+ @_ht_config = HatTrick::Config.new(self.class.wizard_def)
19
+ if configure_callback.is_a?(Proc)
20
+ ht_wizard.controller.instance_exec(@_ht_config, &configure_callback)
22
21
  end
22
+ initialize_without_hat_trick(*args)
23
23
  end
24
24
 
25
- module ControllerInstanceMethods
26
- extend ActiveSupport::Concern
27
-
28
- included do
29
- before_filter :setup_wizard
25
+ def next_step(name=nil)
26
+ if name.nil?
27
+ # getter
28
+ ht_wizard.next_step
29
+ else
30
+ # setter
31
+ step = ht_wizard.find_step(name)
32
+ # explicitly set steps should not be skipped
33
+ step.skipped = false
34
+ ht_wizard.current_step.next_step = step
30
35
  end
36
+ end
31
37
 
32
- private
38
+ def skip_this_step
39
+ ht_wizard.skip_step(ht_wizard.current_step)
40
+ end
33
41
 
34
- attr_reader :ht_wizard
42
+ module ClassMethods
43
+ attr_reader :wizard_def
35
44
 
36
- def setup_wizard
37
- wizard_def = self.class.instance_variable_get("@wizard_def")
38
- @ht_wizard = wizard_def.get_wizard(self)
45
+ def wizard(&block)
46
+ if block_given?
47
+ include HatTrick::DSL::ControllerInstanceMethods
48
+ include HatTrick::ControllerHooks
39
49
 
40
- if params.has_key?('_ht_meta')
41
- step_name = params['_ht_meta']['step']
42
- @ht_wizard.current_step = step_name if step_name
43
- end
44
- end
45
- end
50
+ @wizard_def = HatTrick::WizardDefinition.new
46
51
 
47
- class WizardContext
48
- attr_reader :wizard_def
49
- attr_accessor :wizard
52
+ yield
50
53
 
51
- delegate :model, :previously_visited_step, :to => :wizard
54
+ else
55
+ raise ArgumentError, "wizard called without a block"
56
+ end
57
+ end
52
58
 
53
- def initialize(wizard_def)
54
- @wizard_def = wizard_def
59
+ def configure(&block)
60
+ raise "Must pass a block to configure" unless block_given?
61
+ @config_callback = block
55
62
  end
56
63
 
57
64
  def step(name, args={}, &block)
65
+ raise "step must be called from within a wizard block" unless wizard_def
58
66
  wizard_def.add_step(name, args)
59
67
  instance_eval &block if block_given?
60
68
  end
61
69
 
62
- def next_step(name=nil)
63
- if name.nil?
64
- wizard.next_step
65
- else
66
- raise "next_step should only be called from an after block" if wizard.nil?
67
- step = wizard.find_step(name)
68
- wizard.current_step.next_step = step
69
- end
70
- end
71
-
72
70
  def repeat_step(name)
71
+ raise "repeat_step must be called from within a wizard block" unless wizard_def
73
72
  repeated_step = wizard_def.find_step(name)
74
73
  raise ArgumentError, "Couldn't find step named #{name}" unless repeated_step
75
74
  new_step = repeated_step.dup
@@ -77,47 +76,72 @@ module HatTrick
77
76
  new_step.fieldset = repeated_step.fieldset
78
77
  # but use the current step's name
79
78
  new_step.name = wizard_def.last_step.name
80
- if wizard
81
- # TODO: Might turn all these into run-time methods; which would get
82
- # rid of this wizard / wizard_def distinction
83
- else
84
- # replace the step we're in the middle of defining w/ new_step
85
- wizard_def.replace_step(wizard_def.last_step, new_step)
86
- end
79
+ # replace the step we're in the middle of defining w/ new_step
80
+ wizard_def.replace_step(wizard_def.last_step, new_step)
87
81
  end
88
82
 
89
83
  def skip_this_step
90
- if wizard
91
- wizard.skip_step(wizard.current_step)
92
- else
93
- # skip_this_step in wizard definition context means the step
94
- # can be explicitly jumped to, but won't be in the normal flow
95
- wizard_def.last_step.skipped = true
96
- end
84
+ # skip_this_step in wizard definition (class) context means the step
85
+ # can be explicitly jumped to, but won't be visited in the normal flow
86
+ raise "skip_this_step must be called from within a wizard block" unless wizard_def
87
+ wizard_def.last_step.skipped = true
97
88
  end
98
89
 
99
90
  def button_to(name, options=nil)
100
- if wizard
101
- raise "button_to not yet supported in before/after blocks"
102
- end
91
+ raise "button_to must be called from within a wizard block" unless wizard_def
103
92
  label = options[:label] if options
104
93
  label ||= name.to_s.humanize
94
+
95
+ id = options[:id] if options
96
+
105
97
  step = wizard_def.last_step
106
- step.buttons = step.buttons.merge(name => label)
98
+ button = { :label => label }
99
+ button[:id] = id unless id.nil?
100
+ step.buttons = step.buttons.merge(name => button)
107
101
  end
108
102
 
109
103
  def before(&block)
104
+ raise "before must be called from within a wizard block" unless wizard_def
110
105
  wizard_def.last_step.before_callback = block
111
106
  end
112
107
 
113
108
  def after(&block)
109
+ raise "after must be called from within a wizard block" unless wizard_def
114
110
  wizard_def.last_step.after_callback = block
115
111
  end
116
112
 
117
113
  def include_data(key, &block)
114
+ raise "include_data must be called from within a wizard block" unless wizard_def
118
115
  wizard_def.last_step.include_data = { key.to_sym => block }
119
116
  end
117
+
118
+ def set_contents(&block)
119
+ raise "set_contents must be called from within a wizard block" unless wizard_def
120
+ current_step_name = wizard_def.last_step.to_sym
121
+ include_data "hat_trick_step_contents" do |wiz, model|
122
+ { current_step_name => instance_exec(wiz, model, &block) }
123
+ end
124
+ end
125
+ end
126
+
127
+ module ControllerInstanceMethods
128
+ extend ActiveSupport::Concern
129
+
130
+ included do
131
+ before_filter :setup_wizard
132
+ end
133
+
134
+ private
135
+
136
+ def setup_wizard
137
+ wizard_def = self.class.instance_variable_get("@wizard_def")
138
+ @ht_wizard = wizard_def.get_wizard(self)
139
+
140
+ if params.has_key?('_ht_meta')
141
+ step_name = params['_ht_meta']['step']
142
+ @ht_wizard.current_step = step_name if step_name
143
+ end
144
+ end
120
145
  end
121
146
  end
122
147
  end
123
-
@@ -1,7 +1,7 @@
1
1
  require 'hat_trick/form_helper'
2
2
 
3
3
  module HatTrick
4
- class RailsEngine < ::Rails::Engine
4
+ class Engine < ::Rails::Engine
5
5
  # just defining this causes Rails to look for assets inside this gem
6
6
 
7
7
  initializer 'hat-trick.form_helpers' do
@@ -7,10 +7,6 @@ module HatTrick
7
7
  wizard = controller.send(:ht_wizard)
8
8
  wizard.start unless wizard.started?
9
9
 
10
- # Do we still need these 2 lines?
11
- wizard.model = record
12
- controller.gon.form_model = record
13
-
14
10
  options[:url] = wizard.current_form_url
15
11
  options[:method] = wizard.current_form_method.to_sym
16
12
 
@@ -1,24 +1,46 @@
1
1
  module HatTrick
2
2
  module ModelMethods
3
3
  extend ActiveSupport::Concern
4
- mattr_accessor :current_validation_group
4
+ mattr_accessor :validation_groups
5
+
6
+ attr_accessor :_dummy # so the dummy field will have something to set
5
7
 
6
8
  included do
7
- alias_method_chain :save, :hat_trick
9
+ alias_method_chain :perform_validations, :hat_trick if instance_methods.include?(:perform_validations)
10
+ alias_method_chain :as_json, :model_name if instance_methods.include?(:as_json)
8
11
  end
9
12
 
10
13
  def self.set_current_validation_group_for(klass, validation_group_name)
11
- self.current_validation_group ||= {}
12
- current_validation_group[klass.to_s.underscore] = validation_group_name
14
+ self.validation_groups ||= {}
15
+ validation_groups[klass.to_s.underscore] = validation_group_name
13
16
  end
14
17
 
15
18
  def self.current_validation_group_for(klass)
16
- current_validation_group[klass.to_s.underscore]
19
+ return nil unless validation_groups
20
+ validation_groups[klass.to_s.underscore]
21
+ end
22
+
23
+ def perform_validations_with_hat_trick(*args, &block)
24
+ enable_current_validation_group
25
+ perform_validations_without_hat_trick(*args, &block)
26
+ end
27
+
28
+ def as_json_with_model_name(*args, &block)
29
+ json = as_json_without_model_name(*args, &block)
30
+ json.merge! :__name__ => self.class.to_s.underscore if json.respond_to?(:merge!)
31
+ end
32
+
33
+ private
34
+
35
+ # don't call this method 'current_validation_group', it conflicts with
36
+ # the gem
37
+ def current_step_validation_group
38
+ HatTrick::ModelMethods.current_validation_group_for(self.class)
17
39
  end
18
40
 
19
- def save_with_hat_trick(*args)
20
- enable_validation_group HatTrick::ModelMethods.current_validation_group_for(self.class)
21
- save_without_hat_trick(*args)
41
+ def enable_current_validation_group
42
+ validation_group = current_step_validation_group
43
+ enable_validation_group validation_group if validation_group
22
44
  end
23
45
  end
24
46
  end
@@ -17,7 +17,7 @@ module HatTrick
17
17
  end
18
18
 
19
19
  def initialize_copy(source)
20
- @callbacks = source.callbacks.dup
20
+ @callbacks = {}
21
21
  @buttons = source.buttons.dup
22
22
  @skipped = false
23
23
  @repeat_of = source
@@ -74,27 +74,27 @@ module HatTrick
74
74
  @include_data_key = hash.keys.first
75
75
  end
76
76
 
77
- def run_before_callback(context, wizard_dsl, model)
78
- run_callback(:before, context, wizard_dsl, model)
77
+ def run_before_callback(context, model)
78
+ run_callback(:before, context, model)
79
79
  end
80
80
 
81
- def run_include_data_callback(context, wizard_dsl, model)
82
- run_callback(:include_data, context, wizard_dsl, model)
81
+ def run_include_data_callback(context, model)
82
+ run_callback(:include_data, context, model)
83
83
  end
84
84
 
85
- def run_after_callback(context, wizard_dsl, model)
86
- run_callback(:after, context, wizard_dsl, model)
85
+ def run_after_callback(context, model)
86
+ run_callback(:after, context, model)
87
87
  end
88
88
 
89
89
  protected
90
90
 
91
- def run_callback(type, context, *args)
91
+ def run_callback(type, context, model)
92
92
  callback = callbacks[type.to_sym]
93
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
94
+ if callback.arity > 0
95
+ unless model.is_a?(ActiveModel::Errors)
96
+ context.instance_exec model, &callback
97
+ end
98
98
  else
99
99
  context.instance_eval &callback
100
100
  end
@@ -1,3 +1,3 @@
1
1
  module HatTrick
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -1,17 +1,16 @@
1
1
  require 'hat_trick/controller_hooks'
2
2
  require 'hat_trick/wizard_steps'
3
+ require 'hat_trick/dsl'
3
4
 
4
5
  module HatTrick
5
6
  class Wizard
6
7
  include WizardSteps
7
8
 
8
9
  attr_accessor :controller, :model
9
- attr_reader :current_step, :wizard_def, :wizard_dsl_context, :steps
10
+ attr_reader :current_step, :wizard_def, :steps
10
11
 
11
12
  def initialize(wizard_def)
12
13
  @wizard_def = wizard_def
13
- @wizard_dsl_context = DSL::WizardContext.new(@wizard_def)
14
- @wizard_dsl_context.wizard = self
15
14
  @steps = @wizard_def.steps.map { |s| HatTrick::Step.new(s, self) }
16
15
  end
17
16
 
@@ -19,6 +18,13 @@ module HatTrick
19
18
  @controller = new_controller
20
19
  end
21
20
 
21
+ def model=(new_model)
22
+ @model = new_model
23
+ unless @model.class.included_modules.include?(HatTrick::ModelMethods)
24
+ @model.class.send(:include, HatTrick::ModelMethods)
25
+ end
26
+ end
27
+
22
28
  def current_step=(_step)
23
29
  raise "Don't set current_step to nil" if _step.nil?
24
30
  step = find_step(_step)
@@ -27,7 +33,11 @@ module HatTrick
27
33
  end
28
34
 
29
35
  def session
30
- controller.session unless controller.nil?
36
+ if controller.nil?
37
+ fake_session
38
+ else
39
+ controller.session
40
+ end
31
41
  end
32
42
 
33
43
  def model_created?
@@ -43,11 +53,13 @@ module HatTrick
43
53
  end
44
54
 
45
55
  def create_url
56
+ wizard_def.configured_create_url or
46
57
  controller.url_for(:controller => controller.controller_name,
47
58
  :action => 'create', :only_path => true)
48
59
  end
49
60
 
50
61
  def update_url
62
+ wizard_def.configured_update_url or
51
63
  if model_created?
52
64
  controller.url_for(:controller => controller.controller_name,
53
65
  :action => 'update', :id => model,
@@ -80,28 +92,16 @@ module HatTrick
80
92
 
81
93
  def start
82
94
  session["hat-trick.steps_visited"] = []
83
- self.current_step = first_step
95
+ self.current_step ||= first_step
84
96
  run_before_callback
85
97
  end
86
98
 
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
99
  def run_before_callback(step=current_step)
94
- step.run_before_callback(controller, wizard_dsl_context, model)
100
+ step.run_before_callback(controller, model)
95
101
  end
96
102
 
97
103
  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
104
+ step.run_after_callback(controller, model)
105
105
  end
106
106
 
107
107
  def advance_step(next_step_name=nil)
@@ -114,7 +114,7 @@ module HatTrick
114
114
 
115
115
  # finish if we're on the last step
116
116
  if current_step == last_step && !requested_next_step
117
- finish
117
+ raise "Tried to advance beyond the last step of the wizard"
118
118
  else # we're not on the last step
119
119
  if requested_next_step
120
120
  Rails.logger.info "Force advancing to step: #{requested_next_step}"
@@ -135,10 +135,20 @@ module HatTrick
135
135
  end
136
136
 
137
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) }
138
+ return {} if model.nil?
139
+ inc_data = {}
140
+ include_data_steps = steps_before(current_step) << current_step
141
+ include_data_steps.each do |step|
142
+ step_data = step.run_include_data_callback(controller, model)
143
+ next if step_data.nil? || !step_data.respond_to?(:as_json)
144
+ step_key = step.include_data_key.to_s.camelize(:lower)
145
+ begin
146
+ inc_data[step_key] = camelize_hash_keys(step_data)
147
+ rescue NoMethodError => e
148
+ Rails.logger.error "Unable to serialize data for step #{step}: #{e}"
149
+ end
150
+ end
151
+ inc_data
142
152
  end
143
153
 
144
154
  def alias_action_methods
@@ -155,6 +165,10 @@ module HatTrick
155
165
 
156
166
  private
157
167
 
168
+ def fake_session
169
+ @fake_session ||= {}
170
+ end
171
+
158
172
  def find_next_step
159
173
  find_step(current_step.next_step) or find_step_after(current_step)
160
174
  end
@@ -165,10 +179,13 @@ module HatTrick
165
179
  end
166
180
 
167
181
  def camelize_hash_keys(_hash)
168
- return nil if _hash.nil?
169
182
  hash = {}
170
- _hash.each do |k,v|
171
- hash[k.to_s.camelize(:lower)] = v
183
+ if _hash.respond_to?(:each)
184
+ _hash.each do |k,v|
185
+ hash[k.to_s.camelize(:lower)] = v
186
+ end
187
+ else
188
+ hash = _hash
172
189
  end
173
190
  hash
174
191
  end
@@ -6,6 +6,8 @@ module HatTrick
6
6
  class WizardDefinition
7
7
  include WizardSteps
8
8
 
9
+ attr_accessor :configured_create_url, :configured_update_url
10
+
9
11
  def initialize
10
12
  @steps = []
11
13
  end
@@ -26,7 +26,9 @@ module HatTrick
26
26
  def steps_after(_step)
27
27
  step = find_step(_step)
28
28
  return [] unless step
29
- after_index = steps.index(step) + 1
29
+ step_index = steps.index(step)
30
+ max_index = steps.count - 1
31
+ after_index = step_index < max_index ? step_index + 1 : step_index
30
32
  steps[after_index .. -1]
31
33
  end
32
34
 
@@ -37,7 +39,8 @@ module HatTrick
37
39
  def steps_before(_step)
38
40
  step = find_step(_step)
39
41
  return [] unless step
40
- before_index = steps.index(step) - 1
42
+ step_index = steps.index(step)
43
+ before_index = step_index > 0 ? step_index - 1 : step_index
41
44
  steps[0 .. before_index]
42
45
  end
43
46
 
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ # will automatically get HatTrick::DSL included
4
+ class FakeController < ActionController::Base; end
5
+
6
+ describe HatTrick::DSL do
7
+ let(:controller_class) { FakeController }
8
+
9
+ # save some typing
10
+ def dsl(&block)
11
+ controller_class.instance_eval &block
12
+ end
13
+
14
+ describe HatTrick::DSL::ClassMethods do
15
+ describe "#step" do
16
+ it "should call Wizard#add_step" do
17
+ HatTrick::WizardDefinition.any_instance.expects(:add_step).with(:foo, {})
18
+ dsl do
19
+ wizard do
20
+ step :foo
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end