Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,20 @@
* README.rdoc initial version
* gem version 0.1.0

* 0.1.9/2010-11-18

* added on_put callbacks

* 0.1.10/2010-12-25

* fixed deprecation warnings on #returning calls (thanks, Aaron Todd)
* added support for accepts_nested_attributes_for


* 0.1.11/2011-09-03

* added suppress_duplicate_warnings_for option

* 0.1.12/2012-03-06

* suppress_duplicate_warnings_for now takes a hash of {:table => :column}
63 changes: 55 additions & 8 deletions README.rdoc
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
= wizardly
= wizardly_gt

+wizardly+ creates a multi-step wizard for any ActiveRecord model in three steps.
+wizardly+ by Jeff Patmon creates a multi-step wizard for any ActiveRecord model in three steps.

I fixed an error with a deprecated item in #add_with_validation_group method. See the instructions in the original README below, except add the following line to your environment.rb:

config.gem 'wizardly_gt', :lib => 'wizardly'

Instead of:

config.gem 'wizardly'

== Resources

Expand Down Expand Up @@ -197,6 +205,7 @@ Here's a list of options for the +act_wizardly_for+ controller macro
:mask_fields => [:password, :password_confirmation] (by default)
:persist_model => {:per_page|:once}
:form_data => {:session|:sandbox}
:suppress_duplicate_warnings_for => {:foo => :bar, :animals => :fish}

Setting the :skip option to +true+ tells the scaffold helpers to include a skip button on each page.
The :cancel option set to +false+ removes the 'cancel' button from the wizard views.
Expand Down Expand Up @@ -300,8 +309,9 @@ Here's the list of action callbacks macros that the developer can use for any ac
on_finish(:step) # called when the :finish button is pressed for a valid form (post only)

on_post(:step) # called at the beginning of a POST request
on_put(:step) # called at the beginning of a PUT request
on_get(:step) # called before rendering a GET request
on_errors(:step) # called before re-rendering the form after form invalidation (on a POST request)
on_errors(:step) # called before re-rendering the form after form invalidation (on a POST/PUT request)

The first five callbacks are related to the wizard buttons. Each
callback gives the developer a chance to intervene before the impending render or
Expand All @@ -327,6 +337,20 @@ controller variables and methods including the model instance, controller method
like +redirect_to+, and controller variables like params, request, response and session.
The model instance variable is available for all action callback macros.

==== Suppressing Unique Key violation errors

Because the wizard saves automatically at each step, you may run into uncaught ActiveRecord::StatementInvalid exceptions which you cannot catch with a begin/rescue/end block.

The errors would look like this:

Mysql2::Error: Duplicate entry '' for key 'index_foo_on_bar': UPDATE `foo` SET `updated_at` = '2011-09-02 16:56:24', `bar` = '' WHERE `id` = 12345)

To prevent crashes, list the columns with unique key constrainst on them. For example:

:suppress_duplicate_warnings_for => {:foo => :bar, :animals => :fish}

Note that this will not work if you have a nonstandard table or index name. This requires MySQL 5.1 or newer (5.0.xx and older use ER_DUP_ENTRY instead of ER_DUP_ENTRY_WITH_KEY_NAME).

==== The Wizard's Action Request Cycle

The wizard page is first requested through a GET to an action. In this GET request,
Expand All @@ -349,7 +373,7 @@ presented to the user with a selection of fields and
wizard buttons for posting the form.

When the form data is returned by a POST request, the action creates the instance
variables and builds the model instance using the form data. The on_post callback
variables and builds the model instance using the form data. The on_post or on_put callback
is called at the beginning of the post, then the wizard checks for back, skip and
cancel buttons. If neither of those buttons were pressed, it proceeds to validate
the form, calling the on_errors callback if form validation fails, re-rendering and
Expand All @@ -361,13 +385,36 @@ the model has been committed to the database)

#POST request callback order

on_post
on_post (on_put)
on_back, on_skip, on_cancel
on_errors
render_wizard_form # only if errors
on_next
on_finish
on_completed # only if completed

==== The different between on_post and on_put

If your form_for tag looks like:
<% form_for :person, :url=>{:action=>:stuff} do |f| %>
then you will always see an on_post callback. However, if your form_for tag looks like:
<% form_for @person, :url=>{:action=>:stuff} do |f| %>
then you will see an on_put callback. This is because
(a) Rails treats editing an existing object as a PUT request, and,
(b) Wizardly creates objects right away, so you're almost always editing an existing object

The reason you would use @person instead of :person is so you can use
accepts_nested_attributes_for with related objects. See: http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for

==== Nested attributes example with wizardly

<% form_for @person, :url=>{:action=>:stuff} do |f| %>
<%- f.fields_for :majors do |m| -%>
<%= m.text_field :name %>
<%- end -%>
<% end %>



==== Rendering with on_get and on_errors

Expand All @@ -389,14 +436,14 @@ callbacks are a good place to declare extra variables needed to render the form.
If you have a variable that goes in every page, render_wizard_form is called
for every page.

==== Modifying form data with on_post
==== Modifying form data with on_post (on_put)

The on_post callback is the first callback in the chain of a POST request and
The on_post or on_put callback is the first callback in the chain of a POST request and
is a good place to modify form input such as adding capitalization to a form.
Modification should happen through the model instance variable and not the
controller's params variable.

Redirecting and rendering are not allowed in the on_post callback. Doing so will
Redirecting and rendering are not allowed in the on_post and on_put callbacks. Doing so will
raise an error.

==== Modifying Flow with on_next
Expand Down
8 changes: 4 additions & 4 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ require 'fileutils'

spec = Gem::Specification.new do |s|
s.name = 'wizardly'
s.version = '0.1.8.9'
s.version = '0.1.12'
s.platform = Gem::Platform::RUBY
s.description = 'Create wizards from any model in three steps'
s.summary = 'Produces controllers and wizard scaffolding for models with validation_groups'
Expand All @@ -29,9 +29,9 @@ spec = Gem::Specification.new do |s|
s.has_rdoc = true
#s.test_files = Dir['spec/*_spec.rb']

s.author = 'Jeff Patmon'
s.email = 'jpatmon@yahoo.com'
s.homepage = 'http://github.com/jeffp/wizardly/tree/master'
s.author = 'Paul Schreiber'
s.email = 'paulschreiber@gmail.com'
s.homepage = 'http://github.com/paulschreiber/wizardly/'
end

require 'spec/version'
Expand Down
15 changes: 9 additions & 6 deletions lib/validation_group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def self.validation_group_order; @validation_group_order; end
def self.validation_groups(all_classes = false)
return (self.validation_group_classes[self] || {}) unless all_classes
klasses = ValidationGroup::Util.current_and_ancestors(self).reverse
returning Hash.new do |hash|
{}.tap do |hash|
klasses.each do |klass|
hash.merge! self.validation_group_classes[klass]
end
Expand Down Expand Up @@ -103,11 +103,14 @@ def valid_with_validation_group?(group=nil)
end

module Errors # included in ActiveRecord::Errors
def add_with_validation_group(attribute,
msg = @@default_error_messages[:invalid], *args,
&block)
# gaveeno: modified this method to fix error associated with deprecated method per this comment:
# http://alexkira.blogspot.com/2007/09/rails-validation-using-validation.html?showComment=1235667300000#c1858075936669114503
# def add_with_validation_group(attribute, msg = @@default_error_messages[:invalid], *args, &block)
def add_with_validation_group(attribute, msg = I18n.translate('activerecord.errors.messages')[:invalid], *args, &block)
# jeffp: setting @current_validation_fields and use of should_validate? optimizes code
add_error = @base.respond_to?(:should_validate?) ? @base.should_validate?(attribute.to_sym) : true
# gaveeno: modified this method to add error if the error is on the base object and not on an attribute.
# this is necessary to work with the advanced_exceptions plugin.
add_error = @base.respond_to?(:should_validate?) ? (@base.should_validate?(attribute.to_sym) || attribute == :base) : true
add_without_validation_group(attribute, msg, *args, &block) if add_error
end

Expand All @@ -129,7 +132,7 @@ module Util
# Return array consisting of current and its superclasses down to and
# including base_class.
def self.current_and_ancestors(current)
returning [] do |klasses|
[].tap do |klasses|
klasses << current
root = current.base_class
until current == root
Expand Down
5 changes: 3 additions & 2 deletions lib/wizardly/wizard/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module Wizardly
module Wizard
class Configuration
include TextHelpers
attr_reader :pages, :completed_redirect, :canceled_redirect, :controller_path, :controller_class_name, :controller_name, :page_order
attr_reader :pages, :completed_redirect, :canceled_redirect, :controller_path, :controller_class_name, :controller_name, :page_order, :suppress_duplicate_warnings_for

#enum_attr :persistance, %w(sandbox session database)

Expand All @@ -22,6 +22,7 @@ def initialize(controller, opts) #completed_redirect = nil, canceled_redirect =
@include_skip_button = opts[:skip] || opts[:allow_skip] || opts[:allow_skipping] || false
@include_cancel_button = opts.key?(:cancel) ? opts[:cancel] : true
@guard_entry = opts.key?(:guard) ? opts[:guard] : true
@suppress_duplicate_warnings_for = opts.key?(:suppress_duplicate_warnings_for) ? opts[:suppress_duplicate_warnings_for] : []
@password_fields = opts[:mask_fields] || opts[:mask_passwords] || [:password, :password_confirmation]
@persist_model = opts[:persist_model] || :per_page
@form_data = opts[:form_data] || :session
Expand All @@ -40,7 +41,7 @@ def model; @wizard_model_sym; end
def model_instance_variable; "@#{@wizard_model_sym.to_s}"; end
def model_class_name; @wizard_model_class_name; end
def model_const; @wizard_model_const; end

def first_page?(name); @page_order.first == name; end
def last_page?(name); @page_order.last == name; end
def next_page(name)
Expand Down
31 changes: 30 additions & 1 deletion lib/wizardly/wizard/configuration/methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class Configuration
def print_callback_macros
macros = [
%w(on_post _on_post_%s_form),
%w(on_put _on_put_%s_form),
%w(on_get _on_get_%s_form),
%w(on_errors _on_invalid_%s_form)
]
Expand Down Expand Up @@ -97,6 +98,9 @@ def #{page.name}
if request.post? && callback_performs_action?(:_on_post_#{id}_form)
raise CallbackError, "render or redirect not allowed in :on_post(:#{id}) callback", caller
end
if request.put? && callback_performs_action?(:_on_put_#{id}_form)
raise CallbackError, "render or redirect not allowed in :on_put(:#{id}) callback", caller
end
button_id = check_action_for_button
return if performed?
if request.get?
Expand Down Expand Up @@ -143,7 +147,22 @@ def #{page.name}

mb << <<-ENSURE
ensure
_preserve_wizard_model
begin
_preserve_wizard_model
##
## If @suppress_duplicate_warnings_for is set, you can suppress
## warnings caused by a unique key violation
rescue ActiveRecord::StatementInvalid => e
matched = false
if !wizard_config.suppress_duplicate_warnings_for.empty? and e.to_s =~ /Mysql2?::Error: Duplicate/
wizard_config.suppress_duplicate_warnings_for.each_pair do |table, column|
if e.to_s =~ /index_\#{table}_on_\#{column}/
matched = true
end
end # each
end
raise e if matched == false
end
end
end
ENSURE
Expand Down Expand Up @@ -290,6 +309,16 @@ def _build_wizard_model
h = self.wizard_form_data
if (h && model_id = h['id'])
_model = #{self.model_class_name}.find(model_id)
## PJS: 2010-09-10
## Hack so that relationships with accepts_nested_attributes_for will work with wizardly
_model.class.reflect_on_all_associations(:has_many).each do |a|
_model.send(a.name).to_s
end
## PJS: 2010-11-17
## Added HABTM associations
_model.class.reflect_on_all_associations(:has_and_belongs_many).each do |a|
_model.send(a.name).to_s
end
_model.attributes = params[:#{self.model}]||{}
@#{self.model} = _model
return
Expand Down
15 changes: 7 additions & 8 deletions wizardly.gemspec → ps-wizardly.gemspec
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
# -*- encoding: utf-8 -*-

Gem::Specification.new do |s|
s.name = %q{wizardly}
s.version = "0.1.8.9"

s.name = %q{ps-wizardly}
s.version = "0.1.12"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Jeff Patmon"]
s.date = %q{2009-10-07}
s.description = %q{Create wizards from any model in three steps}
s.email = %q{jpatmon@yahoo.com}
s.authors = ["Jeff Patmon","Gavin Todes","Paul Schreiber"]
s.date = %q{2012-03-06}
s.description = %q{Create wizards from any model in three steps. Slightly tweaked version of jeffp's gem to fix a deprecated method error'}
s.email = %q{paulschreiber@gmail.com}
s.files = ["lib/generators", "lib/wizardly.rb", "lib/validation_group.rb", "lib/jeffp-wizardly.rb", "lib/wizardly", "lib/wizardly/wizard.rb", "lib/wizardly/wizard", "lib/wizardly/wizard/page.rb", "lib/wizardly/wizard/configuration.rb", "lib/wizardly/wizard/button.rb", "lib/wizardly/wizard/utils.rb", "lib/wizardly/wizard/dsl.rb", "lib/wizardly/wizard/configuration", "lib/wizardly/wizard/configuration/methods.rb", "lib/wizardly/wizard/text_helpers.rb", "lib/wizardly/action_controller.rb", "rails_generators/wizardly_app", "rails_generators/wizardly_app/USAGE", "rails_generators/wizardly_app/wizardly_app_generator.rb", "rails_generators/wizardly_app/templates", "rails_generators/wizardly_app/templates/wizardly.rake", "rails_generators/wizardly_scaffold", "rails_generators/wizardly_scaffold/wizardly_scaffold_generator.rb", "rails_generators/wizardly_scaffold/USAGE", "rails_generators/wizardly_scaffold/templates", "rails_generators/wizardly_scaffold/templates/style.css", "rails_generators/wizardly_scaffold/templates/form.html.haml.erb", "rails_generators/wizardly_scaffold/templates/form.html.erb", "rails_generators/wizardly_scaffold/templates/layout.html.haml.erb", "rails_generators/wizardly_scaffold/templates/layout.html.erb", "rails_generators/wizardly_scaffold/templates/images", "rails_generators/wizardly_scaffold/templates/images/next.png", "rails_generators/wizardly_scaffold/templates/images/finish.png", "rails_generators/wizardly_scaffold/templates/images/back.png", "rails_generators/wizardly_scaffold/templates/images/cancel.png", "rails_generators/wizardly_scaffold/templates/images/skip.png", "rails_generators/wizardly_scaffold/templates/helper.rb.erb", "rails_generators/wizardly_controller", "rails_generators/wizardly_controller/USAGE", "rails_generators/wizardly_controller/wizardly_controller_generator.rb", "rails_generators/wizardly_controller/templates", "rails_generators/wizardly_controller/templates/controller.rb.erb", "rails_generators/wizardly_controller/templates/helper.rb.erb", "CHANGELOG.rdoc", "init.rb", "LICENSE", "README.rdoc"]
s.homepage = %q{http://github.com/jeffp/wizardly/tree/master}
s.homepage = %q{http://github.com/paulschreiber/wizardly/}
s.require_paths = ["lib"]
s.rubygems_version = %q{1.3.5}
s.summary = %q{Produces controllers and wizard scaffolding for models with validation_groups}
Expand Down