Skip to content
This repository
Browse code

first import

  • Loading branch information...
commit bb190e568bf78465be898c1242cd9c0e9049521c 0 parents
authored

Showing 124 changed files with 11,013 additions and 0 deletions. Show diff stats Hide diff stats

  1. +12 0 .gitignore
  2. +28 0 CHANGELOG.rdoc
  3. +20 0 LICENSE
  4. +412 0 README.rdoc
  5. +108 0 Rakefile
  6. +10 0 app/controllers/application_controller.rb
  7. +37 0 app/controllers/callbacks_controller.rb
  8. +227 0 app/controllers/generated_controller.rb
  9. +7 0 app/controllers/macro_controller.rb
  10. +28 0 app/controllers/main_controller.rb
  11. +8 0 app/controllers/scaffold_test_controller.rb
  12. +3 0  app/helpers/application_helper.rb
  13. +14 0 app/helpers/callbacks_helper.rb
  14. +14 0 app/helpers/generated_helper.rb
  15. +14 0 app/helpers/scaffold_test_helper.rb
  16. +9 0 app/models/four_step_user.rb
  17. +22 0 app/models/user.rb
  18. +31 0 app/views/callbacks/finish.html.erb
  19. +26 0 app/views/callbacks/init.html.erb
  20. +26 0 app/views/callbacks/second.html.erb
  21. +26 0 app/views/callbacks/third.html.erb
  22. +8 0 app/views/generated/finish.html.erb
  23. +7 0 app/views/generated/init.html.erb
  24. +10 0 app/views/generated/second.html.erb
  25. +9 0 app/views/layouts/application.html.erb
  26. +15 0 app/views/layouts/callbacks.html.erb
  27. +15 0 app/views/layouts/scaffold_test.html.erb
  28. +8 0 app/views/macro/finish.html.erb
  29. +7 0 app/views/macro/init.html.erb
  30. +10 0 app/views/macro/second.html.erb
  31. +7 0 app/views/main/canceled.html.erb
  32. +7 0 app/views/main/finished.html.erb
  33. +3 0  app/views/main/index.html.erb
  34. +7 0 app/views/main/referrer_page.html.erb
  35. +31 0 app/views/scaffold_test/finish.html.erb
  36. +26 0 app/views/scaffold_test/init.html.erb
  37. +36 0 app/views/scaffold_test/second.html.erb
  38. +110 0 config/boot.rb
  39. +22 0 config/database.yml
  40. +45 0 config/environment.rb
  41. +17 0 config/environments/development.rb
  42. +28 0 config/environments/production.rb
  43. +30 0 config/environments/test.rb
  44. +7 0 config/initializers/backtrace_silencers.rb
  45. +10 0 config/initializers/inflections.rb
  46. +5 0 config/initializers/mime_types.rb
  47. +19 0 config/initializers/new_rails_defaults.rb
  48. +15 0 config/initializers/session_store.rb
  49. +5 0 config/locales/en.yml
  50. +44 0 config/routes.rb
  51. BIN  db/development.sqlite3
  52. +16 0 db/migrate/20090718171255_create_sessions.rb
  53. +20 0 db/migrate/20090718172749_create_users.rb
  54. 0  db/production.sqlite3
  55. +37 0 db/schema.rb
  56. BIN  db/test.sqlite3
  57. +1 0  init.rb
  58. +6 0 lib/generators/wizardly_app/USAGE
  59. +33 0 lib/generators/wizardly_app/templates/wizardly.rake
  60. +41 0 lib/generators/wizardly_app/wizardly_app_generator.rb
  61. +3 0  lib/generators/wizardly_controller/USAGE
  62. +45 0 lib/generators/wizardly_controller/templates/controller.rb.erb
  63. +14 0 lib/generators/wizardly_controller/templates/helper.rb.erb
  64. +54 0 lib/generators/wizardly_controller/wizardly_controller_generator.rb
  65. +4 0 lib/generators/wizardly_scaffold/USAGE
  66. +23 0 lib/generators/wizardly_scaffold/templates/form.html.erb
  67. +14 0 lib/generators/wizardly_scaffold/templates/helper.rb.erb
  68. +15 0 lib/generators/wizardly_scaffold/templates/layout.html.erb
  69. +54 0 lib/generators/wizardly_scaffold/templates/style.css
  70. +90 0 lib/generators/wizardly_scaffold/wizardly_scaffold_generator.rb
  71. +31 0 lib/wizardly.rb
  72. +49 0 lib/wizardly/action_controller.rb
  73. +116 0 lib/wizardly/validation_group.rb
  74. +15 0 lib/wizardly/wizard.rb
  75. +26 0 lib/wizardly/wizard/button.rb
  76. +168 0 lib/wizardly/wizard/configuration.rb
  77. +186 0 lib/wizardly/wizard/configuration/methods.rb
  78. +27 0 lib/wizardly/wizard/dsl.rb
  79. +57 0 lib/wizardly/wizard/page.rb
  80. +13 0 lib/wizardly/wizard/text_helpers.rb
  81. +11 0 lib/wizardly/wizard/utils.rb
  82. +30 0 public/404.html
  83. +30 0 public/422.html
  84. +30 0 public/500.html
  85. 0  public/favicon.ico
  86. BIN  public/images/rails.png
  87. +2 0  public/javascripts/application.js
  88. +963 0 public/javascripts/controls.js
  89. +973 0 public/javascripts/dragdrop.js
  90. +1,128 0 public/javascripts/effects.js
  91. +4,320 0 public/javascripts/prototype.js
  92. +5 0 public/robots.txt
  93. +54 0 public/stylesheets/scaffold.css
  94. +4 0 script/about
  95. +6 0 script/autospec
  96. +3 0  script/console
  97. +3 0  script/dbconsole
  98. +3 0  script/destroy
  99. +3 0  script/generate
  100. +3 0  script/performance/benchmarker
  101. +3 0  script/performance/profiler
  102. +3 0  script/plugin
  103. +3 0  script/runner
  104. +3 0  script/server
  105. +10 0 script/spec
  106. +9 0 script/spec_server
  107. +7 0 spec/fixtures/users.yml
  108. +77 0 spec/integrations/callbacks_spec.rb
  109. +43 0 spec/integrations/generated_spec.rb
  110. +34 0 spec/integrations/macro_spec.rb
  111. +12 0 spec/integrations/matchers.rb
  112. +41 0 spec/integrations/scaffold_test_spec.rb
  113. +157 0 spec/integrations/shared_examples.rb
  114. +12 0 spec/models/user_specd.rb
  115. +2 0  spec/rcov.opts
  116. +4 0 spec/spec.opts
  117. +41 0 spec/spec_helper.rb
  118. +21 0 test/fixtures/users.yml
  119. +62 0 test/functional/callbacks_controller_test.rb
  120. +9 0 test/performance/browsing_test.rb
  121. +47 0 test/test_helper.rb
  122. +4 0 test/unit/helpers/callbacks_helper_test.rb
  123. +8 0 test/unit/user_test.rb
  124. +27 0 wizardly.gemspec
12 .gitignore
... ... @@ -0,0 +1,12 @@
  1 +.DS_Store
  2 +**/*.log
  3 +pkg
  4 +rdoc
  5 +coverage
  6 +doc
  7 +log
  8 +nbproject
  9 +tmp
  10 +vendor
  11 +rails_generators
  12 +lib/tasks
28 CHANGELOG.rdoc
Source Rendered
... ... @@ -0,0 +1,28 @@
  1 +== master
  2 +
  3 +== 0.0.1 / 2009-07-28
  4 +
  5 +* Created wizard implementation for submit_tag buttons
  6 +* Created WizardConfig, WizardPage and WizardButton
  7 +* Added wizard functions to validation_group (by alex kira)
  8 +* Created validation_group gem
  9 +* Added Implementation, Macro, Generated Controllers
  10 +* Refactored spec integration tests for the three controllers
  11 +* Created Wizardized_controller generator
  12 +
  13 +== 0.0.2 / 2009-07-29
  14 +
  15 +* script/generate wizardly_scaffold controller_name
  16 +
  17 +== 0.0.3 / 2009-07-30
  18 +
  19 +* added render_wizard_page for respond_to handling
  20 +* added wizard button and page callbacks
  21 +* tests for callbacks
  22 +
  23 +== 0.0.3 / 2009-08-01
  24 +
  25 +* refactored into library
  26 +* integrated validation_groups until later
  27 +* renamed to act_wizardly_for
  28 +
20 LICENSE
... ... @@ -0,0 +1,20 @@
  1 +Copyright (c) 2006-2009 Jeff Patmon
  2 +
  3 +Permission is hereby granted, free of charge, to any person obtaining
  4 +a copy of this software and associated documentation files (the
  5 +"Software"), to deal in the Software without restriction, including
  6 +without limitation the rights to use, copy, modify, merge, publish,
  7 +distribute, sublicense, and/or sell copies of the Software, and to
  8 +permit persons to whom the Software is furnished to do so, subject to
  9 +the following conditions:
  10 +
  11 +The above copyright notice and this permission notice shall be
  12 +included in all copies or substantial portions of the Software.
  13 +
  14 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  15 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  16 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  17 +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  18 +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  19 +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  20 +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
412 README.rdoc
Source Rendered
... ... @@ -0,0 +1,412 @@
  1 += wizardly
  2 +
  3 ++wizardly+ produces a working wizard from any ActiveRecord model in three steps.
  4 +
  5 +== Resources
  6 +
  7 +Development
  8 +
  9 +* http://github.com/jeffp/wizardly
  10 +
  11 +Source
  12 +
  13 +* git://github.com/jeffp/wizardly.git
  14 +
  15 +Install
  16 +
  17 +* sudo gem install jeffp-wizardly --source=http://gems.github.com
  18 +
  19 +== Description
  20 +
  21 +Wizards are a common and useful pattern in programming. Typically, in Ruby,
  22 +enumerated attributes are implemented with strings, symbols or constants. Often the
  23 +developer is burdened with repeatedly defining common methods in support of each
  24 +attribute. Such repetition coding unnecessarily increases
  25 +costs and wastes time.
  26 +
  27 ++enumerated_attribute+ simplifies the definition of enumerated attributes by emphasizing
  28 +convention and DRYing the implementation. Repetitive code such as initializers, accessors,
  29 +predicate and enumeration methods are automatically generated, resulting in better
  30 +encapsulation, quicker implementation and cleaner code.
  31 +
  32 +Features include:
  33 +* Short and simple definition
  34 +* Auto-defined attribute methods
  35 +* Dynamically-generated predicate methods
  36 +* Attribute initialization
  37 +* Method definition short-hand (DSL)
  38 +* ActiveRecord integration
  39 +
  40 +== Example
  41 +
  42 +Create a working controller wizard for any model in 3 steps. Here's how:
  43 +
  44 +Step 1: Define validation groups for your model.
  45 +
  46 + require 'validation_group'
  47 + class User < ActiveRecord::Base
  48 + validation_group :step1, :fields=>[:first_name, :last_name]
  49 + ...
  50 + end
  51 +
  52 +Step 2: Tell your controller to act 'wizardly'.
  53 +
  54 + require 'wizardly'
  55 + class SignupController < ActionController::Base
  56 + wizard_for_model :user
  57 + end
  58 +
  59 +Step 3: Generate 'wizardly' views for your controller.
  60 +
  61 + ./script/generate wizardly_scaffold :signup
  62 +
  63 +You are ready to go.
  64 +
  65 +
  66 +== Usage
  67 +
  68 +=== Defining the Attribute
  69 +
  70 +Defining an enumerated attribute is as simple as this:
  71 +
  72 + require 'enumerated_attribute'
  73 +
  74 + class Tractor
  75 + enumerated_attribute :gear, %w(reverse neutral first second over_drive)
  76 +
  77 + def initialize
  78 + @gear = :neutral
  79 + end
  80 + end
  81 +
  82 +Notice the plugin +enumerated_attribute+ is required at the top of the code.
  83 +The +require+ line must be added at least once at some point in the code.
  84 +It is not included in subsequent examples.
  85 +
  86 +The above code uses +enumerated_attribute+ to define an attribute named 'gear' with five enumeration values.
  87 +In general, +enumerated_attribute+ takes three parameters: the name of the attribute, an array of
  88 +enumeration values (either symbols or strings), and an optional hash of options (not shown above). The complete
  89 +form of +enumerated_attribute+ looks like this:
  90 +
  91 + enumerated_attribute :name, array_of_enumerations, hash_of_options
  92 +
  93 +Defining the attribute :gear has done a number things.
  94 +It has generated an instance variable '@gear', read/write accessors for the
  95 +attribute and support methods
  96 +for the enumeration, such as incrementor and decrementor methods. These methods are
  97 +demonstrated below using the Tractor class above:
  98 +
  99 + Tractor.instance_methods(false)
  100 + # =>["gear", "gear=", "gears", "gear_next", "gear_previous", ...
  101 +
  102 + t = Tractor.new
  103 + t.gear # => :neutral
  104 + t.gear = :reverse # => :reverse
  105 + t.gear # => :reverse
  106 + t.gear = :third
  107 + # => ArgumentError: 'third' is not an enumerated value for gear attribute
  108 +
  109 + t.gears # => [:reverse, :neutral, :first, :second, :over_drive]
  110 + t.gear_next # => :neutral
  111 + t.gear_previous # => :reverse
  112 + t.gear_previous # => :over_drive
  113 +
  114 +The plugin has defined +gear+ and gear= accessors for the attribute. They can be used
  115 +to set the attribute to one of the defined enumeration values. Attempting to set the
  116 +attribute to something besides a defined enumeration value raises an ArgumentError.
  117 +
  118 ++gear_next+ and +gear_previous+ are incrementors and decrementors of the attribute.
  119 +The increment order is based on the order of the enumeration values in the attribute definition.
  120 +Both the incrementor and decrementor will wrap when reaching the boundary elements
  121 +of the enumeration array. For example:
  122 +
  123 + t.gear = :second
  124 + t.gear_next # => :over_drive
  125 + t.gear_next # => :reverse
  126 +
  127 +
  128 +==== Dynamically-Generating Predicates Methods
  129 +
  130 +Predicate methods are methods that query the state of the attribute,
  131 +for instance, gear_is_neutral? is a predicate method that returns 'true' if
  132 +the gear attribute is in the :neutral state.
  133 +By default, predicate methods are not predefined, instead, they are dynamically generated.
  134 +The plugin will evaluate and respond to methods adhering to a format that it
  135 +can associate with an attribute name and one of the attribute's enumeration values.
  136 ++enumerated_attribute+ recognizes predicate methods of the following format:
  137 +
  138 + {attribute name}_{anything}_{enumeration value}?
  139 +
  140 +The predicate method must satisfy three requirements: it must begin with the name
  141 +of the attribute,
  142 +it must end with a question mark, and the question mark must be preceded with
  143 +a valid enumeration value (all connected by underscores without colons).
  144 +So we can write the following two predicate methods without any prior definition and
  145 +the plugin will recognize, define and respond to them as demonstrated here:
  146 +
  147 + t.gear= :neutral
  148 + t.gear_is_in_neutral? # => true
  149 + t.gear_is_in_reverse? # => false
  150 +
  151 +The '_is_in_' part of the methods above is merely semantic but enhances
  152 +readability. The contents of the {anything} portion is completely
  153 +at the discretion of the developer. However, there is one twist.
  154 +The evaluation of a predicate method can be negated
  155 +by including 'not' in the the middle {anything} section, such as here:
  156 +
  157 + t.gear_is_not_in_neutral? # => false
  158 + t.gear_is_not_in_reverse? # => true
  159 +
  160 +Basically, the shortest acceptable form of a predicate method is:
  161 +
  162 + t.gear_neutral? # => true
  163 + t.gear_not_neutral? # => false
  164 +
  165 +
  166 +==== Abbreviating Predicate Methods
  167 +
  168 +In the case that an enumeration value is associated with only one
  169 +attribute, the attribute name can be left out of the predicate method name.
  170 +The plugin will infer the attribute from the enum value in the method name.
  171 +The abbreviate format can be written like this:
  172 +
  173 + {anything}{_}{enumeration value}?
  174 +
  175 +And results in the following possibilities:
  176 +
  177 + t.gear = :neutral
  178 + t.neutral? # => true
  179 + t.is_neutral? # => true
  180 + t.not_neutral? # => false
  181 + t.is_not_neutral? # => false
  182 +
  183 +Calling the abbreviated form of the method containing an enumeration value
  184 +belonging to two or more attributes throws an AmbiguousMethod error.
  185 +
  186 +
  187 +==== Initializing Attributes
  188 +
  189 +The plugin provides a few ways to eliminate setting the initial value of the attribute in
  190 +the +initialize+ method. Two ways are demonstrated here:
  191 +
  192 + class Tractor
  193 + enum_attr :gear, %w(reverse ^neutral first second third)
  194 + enum_attr :front_light, %w(off low high), :init=>:off
  195 + end
  196 +
  197 + t = Tractor.new
  198 + t.gear # => :neutral
  199 + t.front_light # => :off
  200 +
  201 +*Note* +enumerated_attribute+ can be abbreviated to +enum_attr+. The abbreviated
  202 +form will be used in subsequent examples.
  203 +
  204 +The first and simplest way involves designating the initial value by
  205 +prepending a carot '^' to one of the enumeration values in the definition.
  206 +The plugin recognizes that the gear attribute is to be initialized to :neutral.
  207 +Alternatively, the :init option can be used to indicate the initial value of the attribute.
  208 +
  209 +
  210 +==== Setting Attributes to nil
  211 +
  212 +By default, the attribute setter does not allow nils unless the :nil option is set to true
  213 +in the definition as demonstrated here:
  214 +
  215 + class Tractor
  216 + enum_attr :plow, %w(^up down), :nil=>true
  217 + end
  218 +
  219 + t = Tractor.new
  220 + t.plow # => :up
  221 + t.plow_nil? # => :false
  222 + t.plow = nil # => nil
  223 + t.plow_is_nil? # => true
  224 + t.plow_is_not_nil? # => false
  225 +
  226 +Regardless of the :nil option setting, the plugin can dynamically recognize and define
  227 +predicate methods for testing 'nil' values.
  228 +
  229 +
  230 +==== Changing Method Names
  231 +
  232 +The plugin provides options for changing the method names of the enumeration accessor, incrementor
  233 +and decrementor (ie, +gears+, +gear_next+, +gear_previous+):
  234 +
  235 + class Tractor
  236 + enum_attr :lights, %w(^off low high), :plural=>:lights_values,
  237 + :inc=>'lights_inc', :dec=>'lights_dec'
  238 + end
  239 +
  240 + t = Tractor.new
  241 + t.lights_values # => [:off, :low, :high]
  242 + t.lights_inc # => :low
  243 + t.lights_dec # => :off
  244 +
  245 +By default, the plugin uses the plural of the attribute for the accessor method name of the enumeration
  246 +values. The pluralization uses a simple algorithm which does not support irregular forms. In
  247 +the case of 'lights' as an
  248 +attribute, the default pluralization does not work, so the accessor can be changed using
  249 +the :plural option. Likewise, the decrementor
  250 +and incrementor have options :decrementor and :incrementor, or :inc and :dec, for changing
  251 +their method names.
  252 +
  253 +
  254 +=== Defining Other Methods
  255 +
  256 +In the case that other methods are required to support the attribute,
  257 +the plugin provides a short-hand for defining these methods in the
  258 ++enumerated_attribute+ block.
  259 +
  260 + class Tractor
  261 + enum_attr :gear, %w(reverse ^neutral first second over_drive) do
  262 + parked? :neutral
  263 + driving? [:first, :second, :over_drive]
  264 + end
  265 + end
  266 +
  267 + t = Tractor.new
  268 + t.parked? # => true
  269 + t.driving? # => false
  270 +
  271 +Two predicate methods are defined for the 'gear' attribute in the above example using
  272 +the plugin's short-hand.
  273 +The first method, parked?, defines a method which evaluates
  274 +the code {@gear == :neutral}. The second method, driving?, evaluates
  275 +to true if the attribute is set to one of the enumeration values defined in the array
  276 +[:first, :second, :over_drive].
  277 +
  278 +The same short-hand can be used to define methods where the attribute 'is not' equal to the
  279 +indicated value or 'is not' included in the array of values.
  280 +
  281 + class Tractor
  282 + enum_attr :gear, %w(reverse ^neutral first second over_drive) do
  283 + not_parked? is_not :neutral
  284 + not_driving? is_not [:first, :second, :over_drive]
  285 + end
  286 + end
  287 +
  288 +
  289 +==== Defining Other Methods With Blocks
  290 +
  291 +For predicate methods requiring fancier logic,
  292 +a block can be used to define the method body.
  293 +
  294 + class Tractor
  295 + enum_attr :gear, %w(reverse ^neutral first second over_drive) do
  296 + parked? :neutral
  297 + driving? [:first, :second, :over_drive]
  298 + end
  299 + enum_attr :plow, %w(^up down), :plural=>:plow_values do
  300 + plowing? { self.gear_is_in_first? && @plow == :down }
  301 + end
  302 + end
  303 +
  304 +Here, a method plowing? is true if the gear attribute equates to :first
  305 +and the plow attribute is set to :down. There is
  306 +no short-hand for the block. The code must be complete and evaluate in
  307 +the context of the instance.
  308 +
  309 +Method definitions are not limited to predicate methods. Other methods
  310 +can be defined to manipulate the attributes. Here, two methods are defined acting
  311 +as bounded incrementor and decrementor of the gear attribute.
  312 +
  313 + class Tractor
  314 + enum_attr :gear, %w(reverse ^neutral first second over_drive) do
  315 + parked? :neutral
  316 + driving? [:first, :second, :over_drive]
  317 + upshift { self.gear_is_in_over_drive? ? self.gear : self.gear_next }
  318 + downshift { self.driving? ? self.gear_previous : self.gear }
  319 + end
  320 + end
  321 +
  322 + t = Tractor.new
  323 + t.gear # => :neutral
  324 + 10.times { t.upshift }
  325 + t.gear # => :over_drive
  326 + 10.times { t.downshift }
  327 + t.gear # => :neutral
  328 +
  329 +Methods +upshift+ and +downshift+ use the automatically generated
  330 +incrementor and decrementor as
  331 +well as a couple predicate methods. +upshift+ increments the gear attribute until
  332 +it reaches over_drive and does not allow a wrap around. +downshift+ decrements
  333 +until the attribute reaches neutral.
  334 +
  335 +=== Integration
  336 +
  337 +==== ActiveRecord integration
  338 +
  339 +The plugin can be used with ActiveRecord. Enumerated attributes may be declared on
  340 +column attributes or as independent enumerations. Declaring an enumerated attribute
  341 +on a column attribute will enforce the enumeration using symbols. The
  342 +enumerated column attribute must be declared as a STRING in the database schema.
  343 +The enumerated attribute will be stored as a string but retrieved in code as a symbol. The
  344 +enumeration functionality is consistent across integrations.
  345 +
  346 + require 'enumerated_attribute'
  347 + require 'active_record'
  348 +
  349 + class Order < ActiveRecord::Base
  350 + enum_attr :status, %w(^hold, processing, delayed, shipped)
  351 + enum_attr :billing_status,
  352 + %w(^unauthorized, authorized, auth_failed, captured, capture_failed, closed)
  353 + end
  354 +
  355 + o = Order.new
  356 + o.status # => :hold
  357 + o.billing_status # => :unauthorized
  358 + o.save!
  359 +
  360 + o = Order.new(:invoice=>'43556334-W84', :status=>:processing, :billing=>:authorized)
  361 + o.save!
  362 + o.status # => :processing
  363 + o.invoice # => "43556334-W84"
  364 +
  365 +
  366 +=== Implementation Notes
  367 +
  368 +==== New and Method_missing methods
  369 +
  370 +The plugin chains both the 'new' and the 'method_missing' methods. Any 'new' and 'method_missing'
  371 +implementations in the same class declaring an enumerated_attribute should come before the
  372 +declaration; otherwise, the 'new' and 'method_missing' implementations must chain in order to avoid
  373 +overwriting the plugin's methods. The best approach is shown here:
  374 +
  375 + class Soup
  376 + def self.new(*args)
  377 + ...
  378 + end
  379 +
  380 + private
  381 + def method_missing(methId, *args, &blk)
  382 + ...
  383 + end
  384 +
  385 + enum_attr temp:, %w(cold warm hot boiling)
  386 + end
  387 +
  388 +
  389 +== Testing
  390 +
  391 +The plugin uses RSpec for testing. Make sure you have the RSpec gem installed:
  392 +
  393 + gem install rspec
  394 +
  395 +To test the plugin for regular ruby objects, run:
  396 +
  397 + rake spec
  398 +
  399 +Testing ActiveRecord integration requires the install of Sqlite3 and the
  400 +sqlite3-ruby gem. To test ActiveRecord, run:
  401 +
  402 + rake spec:active_record
  403 +
  404 +To test all specs:
  405 +
  406 + rake spec:all
  407 +
  408 +
  409 +== Dependencies
  410 +
  411 +* ActiveRecord
  412 +* Sqlite3 and sqlite3-ruby gem (for testing)
108 Rakefile
... ... @@ -0,0 +1,108 @@
  1 +$: << 'lib'
  2 +# Add your own tasks in files placed in lib/tasks ending in .rake, for example
  3 +# lib/tasks/capistrano.rake, and they will automatically be available to Rake.
  4 +
  5 +require(File.join(File.dirname(__FILE__), 'config', 'boot'))
  6 +
  7 +require 'rake'
  8 +require 'rake/testtask'
  9 +require 'rake/rdoctask'
  10 +require 'rake/gempackagetask'
  11 +
  12 +require 'tasks/rails'
  13 +require 'fileutils'
  14 +
  15 +spec = Gem::Specification.new do |s|
  16 + s.name = 'wizardly'
  17 + s.version = '0.1.0'
  18 + s.platform = Gem::Platform::RUBY
  19 + s.description = 'Create wizards from any model in three steps'
  20 + s.summary = 'Produces controllers and wizard scaffolding for models with validation_groups'
  21 +
  22 + #move all files in lib/generators -> rails_generators
  23 + FileUtils.rm_rf "rails_generators"
  24 + FileUtils.mkdir "rails_generators"
  25 + FileUtils.cp_r "lib/generators/.", "rails_generators"
  26 + s.files = FileList['{lib,rails_generators}/**/*'] + %w(CHANGELOG.rdoc init.rb LICENSE README.rdoc) - FileList['**/*.log','lib/generators','lib/tasks']
  27 + s.require_path = 'lib'
  28 + s.has_rdoc = true
  29 + #s.test_files = Dir['spec/*_spec.rb']
  30 +
  31 + s.author = 'Jeff Patmon'
  32 + s.email = 'jpatmon@yahoo.com'
  33 + s.homepage = 'http://github.com/jeffp/wizardly/tree/master'
  34 +end
  35 +
  36 +require 'spec/version'
  37 +require 'spec/rake/spectask'
  38 +
  39 +
  40 +
  41 +namespace :spec do
  42 + desc "Run all specs"
  43 + task :all=>[:macro, :gen, :scaffold, :callbacks]
  44 + desc "Test the MacroController"
  45 + Spec::Rake::SpecTask.new(:macro) do |t|
  46 + t.spec_files = FileList['spec/integrations/macro_spec.rb']
  47 + t.libs << 'lib' << 'spec' << 'spec/integrations'
  48 + t.spec_opts = ['--options', 'spec/spec.opts']
  49 + t.rcov = false
  50 + end
  51 + desc "Test the GeneratedController"
  52 + Spec::Rake::SpecTask.new(:gen=>[:generate_controller]) do |t|
  53 + t.spec_files = FileList['spec/integrations/generated_spec.rb']
  54 + t.libs << 'lib' << 'spec' << 'spec/integrations'
  55 + t.spec_opts = ['--options', 'spec/spec.opts']
  56 + t.rcov = false
  57 + end
  58 + desc "Generate GeneratedController for spec test"
  59 + task :generate_controller=>[:environment] do
  60 + require 'rails_generator'
  61 + require 'rails_generator/scripts/generate'
  62 + gen_argv = []
  63 + gen_argv << "wizardly_controller" << "generated" << "user" << "/main/finished" << "/main/canceled" << "--force"
  64 + Rails::Generator::Scripts::Generate.new.run(gen_argv)
  65 + end
  66 + desc "Test the ScaffoldTestController"
  67 + Spec::Rake::SpecTask.new(:scaffold) do |t|
  68 + t.spec_files = FileList['spec/integrations/scaffold_test_spec.rb']
  69 + t.libs << 'lib' << 'spec' << 'spec/integrations'
  70 + t.spec_opts = ['--options', 'spec/spec.opts']
  71 + t.rcov = false
  72 + end
  73 + desc "Test the CallbacksController"
  74 + Spec::Rake::SpecTask.new(:callback) do |t|
  75 + t.spec_files = FileList['spec/integrations/callbacks_spec.rb']
  76 + t.libs << 'lib' << 'spec' << 'spec/integrations'
  77 + t.spec_opts = ['--options', 'spec/spec.opts']
  78 + t.rcov = false
  79 + end
  80 +end
  81 +
  82 +
  83 +desc "Generate documentation for the #{spec.name} plugin."
  84 +Rake::RDocTask.new(:rdoc) do |rdoc|
  85 + rdoc.rdoc_dir = 'rdoc'
  86 + rdoc.title = spec.name
  87 + # #rdoc.template = '../rdoc_template.rb'
  88 + rdoc.options << '--line-numbers' << '--inline-source'
  89 + rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG.rdoc', 'LICENSE', 'lib/wizardly.rb', 'lib/wizardly/**/*.rb')
  90 +end
  91 +
  92 +desc 'Generate a gemspec file.'
  93 +task :gemspec do
  94 + File.open("#{spec.name}.gemspec", 'w') do |f|
  95 + f.write spec.to_ruby
  96 + end
  97 +end
  98 +
  99 +Rake::GemPackageTask.new(spec) do |p|
  100 + FileUtils.rm_rf "rails_generators"
  101 + FileUtils.mkdir "rails_generators"
  102 + FileUtils.cp_r "lib/generators/.", "rails_generators"
  103 + p.gem_spec = spec
  104 + p.need_tar = false
  105 + p.need_zip = true
  106 +end
  107 +
  108 +Dir['tasks/**/*.rake'].each {|rake| load rake}
10 app/controllers/application_controller.rb
... ... @@ -0,0 +1,10 @@
  1 +# Filters added to this controller apply to all controllers in the application.
  2 +# Likewise, all the methods added will be available for all controllers.
  3 +
  4 +class ApplicationController < ActionController::Base
  5 + helper :all # include all helpers, all the time
  6 + protect_from_forgery # See ActionController::RequestForgeryProtection for details
  7 +
  8 + # Scrub sensitive parameters from your log
  9 + # filter_parameter_logging :password
  10 +end
37 app/controllers/callbacks_controller.rb
... ... @@ -0,0 +1,37 @@
  1 +class CallbacksController < ApplicationController #< WizardForModelController
  2 + before_filter :init_flash_notice
  3 +
  4 +# wizard_for_model :four_step_user, :skip=>true, :mask_passwords=>[:password, :password_confirmation],
  5 +# :completed=>{:controller=>:main, :action=>:finished},
  6 +# :canceled=>{:controller=>:main, :action=>:canceled}
  7 +
  8 + def init_flash_notice; flash[:notice] = ''; end
  9 +
  10 + def flag(val)
  11 + instance_variable_set("@#{val}", true)
  12 + puts "--<<#{val}>>--"
  13 +# flash[:notice] = flash[:notice] + "[#{val}]"
  14 + end
  15 +
  16 + def on_get_init_page; flag :on_get_init_page; end
  17 + def on_init_page_errors; flag :on_init_page_errors; end
  18 + def on_init_page_next; flag :on_init_page_next; end
  19 + def on_init_page_skip; flag :on_init_page_skip; end
  20 + def on_init_page_cancel; flag :on_init_page_cancel; end
  21 +
  22 + def on_get_second_page; flag :on_get_second_page; end
  23 + def on_second_page_back; flag :on_second_page_back; end
  24 + def on_second_page_next; flag :on_second_page_next; end
  25 + def on_second_page_errors; flag :on_second_page_errors; end
  26 + def on_second_page_skip; flag :on_second_age_skip; end
  27 + def on_second_page_cancel; flag :on_second_page_cancel; end
  28 +
  29 + def wizard_render_page; flag :wizard_render_page; end
  30 +
  31 + def on_finish_page_errors
  32 + flag :on_finish_page_errors
  33 + @four_step_user[:password] = ''
  34 + @four_step_user[:password_confirmation] = ''
  35 + end
  36 +
  37 +end
227 app/controllers/generated_controller.rb
... ... @@ -0,0 +1,227 @@
  1 +#
  2 +# GeneratedController class generated by wizardly_controller
  3 +#
  4 +
  5 +class GeneratedController < ApplicationController
  6 + before_filter :guard_entry
  7 +
  8 +
  9 + # init action method
  10 + def init
  11 + @step = :init
  12 + @wizard = wizard_config
  13 + @title = 'Init'
  14 + @description = ''
  15 + h = (flash[:wizard_model]||{}).merge(params[:user] || {})
  16 + @user = User.new(h)
  17 + flash[:wizard_model] = h
  18 + button_id = check_action_for_button
  19 + return if performed?
  20 + if request.get?
  21 + return if callback_performs_action?(:on_get_init_page)
  22 + render_wizard_page
  23 + return
  24 + end
  25 +
  26 + @user.enable_validation_group :init
  27 + unless @user.valid?
  28 + return if callback_performs_action?(:on_init_page_errors)
  29 + render_wizard_page
  30 + return
  31 + end
  32 +
  33 + return _on_wizard_finish if button_id == :finish
  34 + session[:progression] = [:init]
  35 + return if callback_performs_action?(:on_init_page_next)
  36 + redirect_to :action=>:second
  37 + end
  38 +
  39 +
  40 + # finish action method
  41 + def finish
  42 + @step = :finish
  43 + @wizard = wizard_config
  44 + @title = 'Finish'
  45 + @description = ''
  46 + h = (flash[:wizard_model]||{}).merge(params[:user] || {})
  47 + @user = User.new(h)
  48 + flash[:wizard_model] = h
  49 + button_id = check_action_for_button
  50 + return if performed?
  51 + if request.get?
  52 + return if callback_performs_action?(:on_get_finish_page)
  53 + render_wizard_page
  54 + return
  55 + end
  56 +
  57 + @user.enable_validation_group :finish
  58 + unless @user.valid?
  59 + return if callback_performs_action?(:on_finish_page_errors)
  60 + render_wizard_page
  61 + return
  62 + end
  63 +
  64 + return if _on_wizard_finish
  65 + redirect_to '/main/finished'
  66 + end
  67 +
  68 +
  69 + # second action method
  70 + def second
  71 + @step = :second
  72 + @wizard = wizard_config
  73 + @title = 'Second'
  74 + @description = ''
  75 + h = (flash[:wizard_model]||{}).merge(params[:user] || {})
  76 + @user = User.new(h)
  77 + flash[:wizard_model] = h
  78 + button_id = check_action_for_button
  79 + return if performed?
  80 + if request.get?
  81 + return if callback_performs_action?(:on_get_second_page)
  82 + render_wizard_page
  83 + return
  84 + end
  85 +
  86 + @user.enable_validation_group :second
  87 + unless @user.valid?
  88 + return if callback_performs_action?(:on_second_page_errors)
  89 + render_wizard_page
  90 + return
  91 + end
  92 +
  93 + return _on_wizard_finish if button_id == :finish
  94 + session[:progression].push(:second)
  95 + return if callback_performs_action?(:on_second_page_next)
  96 + redirect_to :action=>:finish
  97 + end
  98 +
  99 + def index
  100 + redirect_to :action=>:init
  101 + end
  102 +
  103 +
  104 + protected
  105 + def _on_wizard_finish
  106 + @user.save_without_validation!
  107 + flash.discard(:wizard_model)
  108 + initial_referer = reset_wizard_session_vars
  109 + return redirect_to(wizard_config.completed_redirect || initial_referer)
  110 + end
  111 + def _on_wizard_skip
  112 + redirect_to :action=>wizard_config.next_page(@step)
  113 + true
  114 + end
  115 + def _on_wizard_back
  116 + redirect_to :action=>((session[:progression]||[]).pop || :init)
  117 + true
  118 + end
  119 + def _on_wizard_cancel
  120 + initial_referer = reset_wizard_session_vars
  121 + return (false) unless (wizard_config.canceled_redirect || initial_referer)
  122 + redirect_to(wizard_config.canceled_redirect || initial_referer)
  123 + true
  124 + end
  125 + hide_action :_on_wizard_finish, :_on_wizard_skip, :_on_wizard_back, :_on_wizard_cancel
  126 +
  127 +
  128 + protected
  129 + def guard_entry
  130 + if (r = request.env['HTTP_REFERER'])
  131 + h = ::ActionController::Routing::Routes.recognize_path(URI.parse(r).path)
  132 + return if (h[:controller]||'') == 'generated'
  133 + session[:initial_referer] = h
  134 + else
  135 + session[:initial_referer] = nil
  136 + end
  137 + flash.discard(:wizard_model)
  138 + redirect_to :action=>:init unless (params[:action] || '') == 'init'
  139 + end
  140 + hide_action :guard_entry
  141 +
  142 + def render_wizard_page
  143 + end
  144 + hide_action :render_wizard_page
  145 +
  146 + def performed?; super; end
  147 + hide_action :performed?
  148 +
  149 + def check_action_for_button
  150 + button_id = nil
  151 + #check if params[:commit] has returned a button from submit_tag
  152 + unless (params[:commit] == nil)
  153 + button_name = methodize_button_name(params[:commit])
  154 + unless [:next, :finish].include?(button_id = button_name.to_sym)
  155 + action_method_name = "on_" + params[:action].to_s + "_page_" + button_name
  156 + unless callback_performs_action?(action_method_name)
  157 + method_name = "_on_wizard_" + button_name
  158 + if (method = self.method(method_name))
  159 + method.call
  160 + else
  161 + raise MissingCallbackError, "Callback method either '" + action_method_name + "' or '" + method_name + "' not defined", caller
  162 + end
  163 + end
  164 + end
  165 + end
  166 + #add other checks here or above
  167 + button_id
  168 + end
  169 + hide_action :check_action_for_button
  170 +
  171 + @wizard_callbacks ||= {}
  172 + class << self
  173 + attr_reader :wizard_callbacks
  174 + end
  175 +
  176 + def callback_performs_action?(methId)
  177 + wc = self.class.wizard_callbacks
  178 + case wc[methId]
  179 + when :none
  180 + return false
  181 + when :found
  182 + else #nil
  183 + unless self.class.method_defined?(methId)
  184 + wc[methId] = :none
  185 + return false
  186 + end
  187 + wc[methId] = :found
  188 + end
  189 + self.__send__(methId)
  190 + return self.performed?
  191 + end
  192 + hide_action :callback_performs_action?
  193 +
  194 +
  195 +
  196 + private
  197 + def methodize_button_name(value)
  198 + value.to_s.strip.squeeze(' ').gsub(/ /, '_').downcase
  199 + end
  200 +
  201 + def reset_wizard_session_vars
  202 + session[:progression] = nil
  203 + init = session[:initial_referer]
  204 + session[:initial_referer] = nil
  205 + init
  206 + end
  207 + hide_action :methodize_button_name, :reset_wizard_session_vars
  208 +
  209 + public
  210 + def wizard_config; self.class.wizard_config; end
  211 + hide_action :wizard_config
  212 +
  213 + private
  214 +
  215 + def self.wizard_config; @wizard_config; end
  216 + @wizard_config = Wizardly::Wizard::Configuration.create(:generated, :user, :allow_skip=>true) do
  217 + when_completed_redirect_to '/main/finished'
  218 + when_canceled_redirect_to '/main/canceled'
  219 +
  220 + # other things you can configure
  221 + # change_button(:next).to('Next One')
  222 + # change_button(:back).to('Previous')
  223 + # create_button('Help')
  224 + # set_page(:init).buttons_to :next_one, :previous, :cancel, :help #this removes skip
  225 + end
  226 +
  227 +end
7 app/controllers/macro_controller.rb
... ... @@ -0,0 +1,7 @@
  1 +class MacroController < ApplicationController
  2 +
  3 + act_wizardly_for :user,
  4 + :completed=>{:controller=>:main, :action=>:finished},
  5 + :canceled=>{:controller=>:main, :action=>:canceled}
  6 +
  7 +end
28 app/controllers/main_controller.rb
... ... @@ -0,0 +1,28 @@
  1 +class MainController < ApplicationController
  2 + def index
  3 + @links = {
  4 + :macro=>'/macro',
  5 + :generated=>'/generated',
  6 + :scaffold_test=>'/scaffold_test',
  7 + :callbacks=>'/callbacks'
  8 + }
  9 + end
  10 +
  11 + def finished
  12 + @referring_controller = referring_controller
  13 + end
  14 +
  15 + def canceled
  16 + @referring_controller = referring_controller
  17 + end
  18 +
  19 + def referrer_page
  20 + end
  21 +
  22 + private
  23 + def referring_controller
  24 + referer = request.env['HTTP_REFERER']
  25 + ActionController::Routing::Routes.recognize_path(URI.parse(referer).path)[:controller]
  26 + end
  27 +
  28 +end
8 app/controllers/scaffold_test_controller.rb
... ... @@ -0,0 +1,8 @@
  1 +class ScaffoldTestController < ApplicationController #< WizardForModelController
  2 +
  3 + wizard_for_model :user, :skip=>true, :mask_passwords=>[:password, :password_confirmation],
  4 + :completed=>{:controller=>:main, :action=>:finished},
  5 + :canceled=>{:controller=>:main, :action=>:canceled}
  6 +
  7 +
  8 +end
3  app/helpers/application_helper.rb
... ... @@ -0,0 +1,3 @@
  1 +# Methods added to this helper will be available to all templates in the application.
  2 +module ApplicationHelper
  3 +end
14 app/helpers/callbacks_helper.rb
... ... @@ -0,0 +1,14 @@
  1 +module CallbacksHelper
  2 +
  3 + def wizardly_submit
  4 + @@wizardly_submit ||= {}
  5 + unless @@wizardly_submit[@step]
  6 + buttons = @wizard.pages[@step].buttons
  7 + @@wizardly_submit[@step] = buttons.inject(StringIO.new) do |io, button|
  8 + io << submit_tag(button.name)
  9 + end.string
  10 + end
  11 + @@wizardly_submit[@step]
  12 + end
  13 +
  14 +end
14 app/helpers/generated_helper.rb
... ... @@ -0,0 +1,14 @@
  1 +module GeneratedHelper
  2 +
  3 + def wizardly_submit
  4 + @@wizardly_submit ||= {}
  5 + unless @@wizardly_submit[@step]
  6 + buttons = @wizard.pages[@step].buttons
  7 + @@wizardly_submit[@step] = buttons.inject(StringIO.new) do |io, button|
  8 + io << submit_tag(button.name)
  9 + end.string
  10 + end
  11 + @@wizardly_submit[@step]
  12 + end
  13 +
  14 +end
14 app/helpers/scaffold_test_helper.rb
... ... @@ -0,0 +1,14 @@
  1 +module ScaffoldTestHelper
  2 +
  3 + def wizardly_submit
  4 + @@wizardly_submit ||= {}
  5 + unless @@wizardly_submit[@step]
  6 + buttons = @wizard.pages[@step].buttons
  7 + @@wizardly_submit[@step] = buttons.inject(StringIO.new) do |io, button|
  8 + io << submit_tag(button.name)
  9 + end.string
  10 + end
  11 + @@wizardly_submit[@step]
  12 + end
  13 +
  14 +end
9 app/models/four_step_user.rb
... ... @@ -0,0 +1,9 @@
  1 +class FourStepUser < User
  2 +
  3 + validation_group :init, :fields=>[:first_name, :last_name]
  4 + validation_group :second, :fields=>[:age, :gender]
  5 + validation_group :third, :fields=>[:programmer, :status]
  6 + validation_group :finish, :fields=>[:username, :password, :password_confirmation]
  7 +
  8 +
  9 +end
22 app/models/user.rb
... ... @@ -0,0 +1,22 @@
  1 +class User < ActiveRecord::Base
  2 +=begin
  3 + t.string :first_name
  4 + t.string :last_name
  5 + t.string :username
  6 + t.string :password
  7 + t.integer :age
  8 + t.string :gender
  9 + t.boolean :programmer
  10 + t.string :status
  11 +=end
  12 +
  13 + validates_confirmation_of :password
  14 + validates_presence_of :first_name, :last_name, :username, :password, :age, :gender, :status
  15 + #validates_numercality_of :age
  16 + #validates_uniqueness_of :username
  17 +
  18 + wizardly_page :init, :fields=>[:first_name, :last_name]
  19 + wizardly_page :second, :fields=>[:age, :gender, :programmer, :status]
  20 + wizardly_page :finish, :fields=>[:username, :password, :password_confirmation]
  21 +
  22 +end
31 app/views/callbacks/finish.html.erb
... ... @@ -0,0 +1,31 @@
  1 +<h1>Finish</h1>
  2 +
  3 +
  4 +<%= "<p style=\"color: green\">#{flash[:notice]}<p>" if flash[:notice] %>
  5 +
  6 +<% form_for :four_step_user, :url=>{:action=>:finish} do |f| %>
  7 + <%= f.error_messages %>
  8 +
  9 +
  10 + <p>
  11 + <%= f.label :username %><br />
  12 + <%= f.text_field :username %>
  13 + </p>
  14 +
  15 + <p>
  16 + <%= f.label :password %><br />
  17 + <%= f.password_field :password %>
  18 + </p>
  19 +
  20 + <p>
  21 + <%= f.label :password_confirmation %><br />
  22 + <%= f.password_field :password_confirmation %>
  23 + </p>
  24 + <p>
  25 + <%= wizardly_submit %>
  26 + </p>
  27 +<% end %>
  28 +
  29 +<script type='text/javascript'>
  30 + document.getElementById('four_step_user_username').focus()
  31 +</script>
26 app/views/callbacks/init.html.erb
... ... @@ -0,0 +1,26 @@
  1 +<h1>Init</h1>
  2 +
  3 +
  4 +<%= "<p style=\"color: green\">#{flash[:notice]}<p>" if flash[:notice] %>
  5 +
  6 +<% form_for :four_step_user, :url=>{:action=>:init} do |f| %>
  7 + <%= f.error_messages %>
  8 +
  9 +
  10 + <p>
  11 + <%= f.label :first_name %><br />
  12 + <%= f.text_field :first_name %>
  13 + </p>
  14 +
  15 + <p>
  16 + <%= f.label :last_name %><br />
  17 + <%= f.text_field :last_name %>
  18 + </p>
  19 + <p>
  20 + <%= wizardly_submit %>
  21 + </p>
  22 +<% end %>
  23 +
  24 +<script type='text/javascript'>
  25 + document.getElementById('four_step_user_first_name').focus()
  26 +</script>
26 app/views/callbacks/second.html.erb
... ... @@ -0,0 +1,26 @@
  1 +<h1>Second</h1>
  2 +
  3 +
  4 +<%= "<p style=\"color: green\">#{flash[:notice]}<p>" if flash[:notice] %>
  5 +
  6 +<% form_for :four_step_user, :url=>{:action=>:second} do |f| %>
  7 + <%= f.error_messages %>
  8 +
  9 +
  10 + <p>
  11 + <%= f.label :age %><br />
  12 + <%= f.text_field :age %>
  13 + </p>
  14 +
  15 + <p>
  16 + <%= f.label :gender %><br />
  17 + <%= f.text_field :gender %>
  18 + </p>
  19 + <p>
  20 + <%= wizardly_submit %>
  21 + </p>
  22 +<% end %>
  23 +
  24 +<script type='text/javascript'>
  25 + document.getElementById('four_step_user_age').focus()
  26 +</script>
26 app/views/callbacks/third.html.erb
... ... @@ -0,0 +1,26 @@
  1 +<h1>Third</h1>
  2 +
  3 +
  4 +<%= "<p style=\"color: green\">#{flash[:notice]}<p>" if flash[:notice] %>
  5 +
  6 +<% form_for :four_step_user, :url=>{:action=>:third} do |f| %>
  7 + <%= f.error_messages %>
  8 +
  9 +
  10 + <p>
  11 + <%= f.label :programmer %><br />
  12 + <%= f.check_box :programmer %>
  13 + </p>
  14 +
  15 + <p>
  16 + <%= f.label :status %><br />
  17 + <%= f.text_field :status %>
  18 + </p>
  19 + <p>
  20 + <%= wizardly_submit %>
  21 + </p>
  22 +<% end %>
  23 +
  24 +<script type='text/javascript'>
  25 + document.getElementById('four_step_user_programmer').focus()
  26 +</script>
8 app/views/generated/finish.html.erb
... ... @@ -0,0 +1,8 @@
  1 +<div>
  2 + <% form_for :user do |f| -%>
  3