diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc index fe462b9..0d14e37 100644 --- a/CHANGELOG.rdoc +++ b/CHANGELOG.rdoc @@ -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} diff --git a/README.rdoc b/README.rdoc index a625c0f..ef4ad5c 100644 --- a/README.rdoc +++ b/README.rdoc @@ -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 @@ -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. @@ -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 @@ -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, @@ -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 @@ -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 @@ -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 diff --git a/Rakefile b/Rakefile index 03cd3d8..e68115d 100644 --- a/Rakefile +++ b/Rakefile @@ -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' @@ -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' diff --git a/lib/validation_group.rb b/lib/validation_group.rb index 9598d1f..83ce4c5 100644 --- a/lib/validation_group.rb +++ b/lib/validation_group.rb @@ -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 @@ -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 @@ -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 diff --git a/lib/wizardly/wizard/configuration.rb b/lib/wizardly/wizard/configuration.rb index 1334a69..4d4191c 100644 --- a/lib/wizardly/wizard/configuration.rb +++ b/lib/wizardly/wizard/configuration.rb @@ -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) @@ -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 @@ -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) diff --git a/lib/wizardly/wizard/configuration/methods.rb b/lib/wizardly/wizard/configuration/methods.rb index 21b48c4..1c73ff7 100644 --- a/lib/wizardly/wizard/configuration/methods.rb +++ b/lib/wizardly/wizard/configuration/methods.rb @@ -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) ] @@ -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? @@ -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 @@ -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 diff --git a/wizardly.gemspec b/ps-wizardly.gemspec similarity index 89% rename from wizardly.gemspec rename to ps-wizardly.gemspec index 60b9962..b01625c 100644 --- a/wizardly.gemspec +++ b/ps-wizardly.gemspec @@ -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}