module Stepper # This module is automatically included into all models. module ActiveRecordAdditions def self.included(base) base.extend ClassMethods end module ClassMethods # Sets up methods and define steps. # For example, you have model +Company+ and you want to fill it fields in few steps description, kind and address: # class Company < ActiveRecord::Base # has_steps :steps => %w{ description kind address } # end # # Model should have current step column, by default it name is +current_step+. # It should be added by migration: # add_column :companies, :current_step, :string # add_index :companies, :current_step # # The column name can be set up with option +current_step_column+. # # Options: # [:+steps+] # It is required option. Define steps for multistep form. # # [:+current_step_column+] # Define what field use for save current step of form. Default +current_step+ # def has_steps(options = {}) #check options raise Stepper::StepperException.new("Options for has_steps must be in a hash.") unless options.is_a? Hash options.each do |key, value| unless [:current_step_column, :steps].include? key raise Stepper::StepperException.new("Unknown option for has_steps: #{key.inspect} => #{value.inspect}.") end end raise Stepper::StepperException.new(":steps condition can't be blank") if options[:steps].blank? #set current step column class_attribute :stepper_current_step_column, :instance_writer => false self.stepper_current_step_column = options[:current_step_column] || :current_step class_attribute :stepper_options, :instance_writer => false self.stepper_options = options self.validate :current_step_validation include InstanceMethods end end module InstanceMethods def stepper_steps self.stepper_options[:steps] end unless self.respond_to? :steps define_method :steps do self.stepper_steps end end # returns index of current step in steps array def stepper_current_step_index stepper_steps.index(stepper_current_step) end # returns name of current step def stepper_current_step self.send(self.stepper_current_step_column) end # sets up name of current step def stepper_current_step=(step) self.send("#{self.stepper_current_step_column.to_s}=", step) end # Use to check current step or given step is last step # last_step?("address") def last_step?(step = stepper_current_step) step == self.stepper_steps.last end # Use to check current step or given step is first step # first_step?("address") def first_step?(step = stepper_current_step) (step == stepper_steps.first) or stepper_current_step.blank? && step.blank? end # returns previous step of current step def previous_step return nil if (first_step? or stepper_current_step.blank?) stepper_steps[stepper_steps.index(stepper_current_step) - 1] end # set previous step as current step def previous_step! self.stepper_current_step = self.previous_step self end # returns next step of current step def next_step return stepper_steps.first if self.stepper_current_step.blank? return nil if self.last_step? stepper_steps[stepper_steps.index(stepper_current_step) + 1] end # set next step as current step def next_step! self.stepper_current_step = self.next_step self end protected # Executes validation methods for current step and all previous steps if its exists. # You can set up what fields should be validated in methods for steps. For example: # # def validate_description # self.validates_presence_of :name # self.validates_presence_of :desc # end # # def validate_address # self.validates_presence_of :city # self.validates_presence_of :country # self.validates_presence_of :address # end # # def validate_kind # self.validates_presence_of :kind # end def current_step_validation return if stepper_current_step.blank? for i in 0..stepper_current_step_index do self.send("validate_#{stepper_steps[i]}") if self.respond_to?("validate_#{stepper_steps[i]}", true) end end end end end