Skip to content

Commit

Permalink
Add documentation.
Browse files Browse the repository at this point in the history
  • Loading branch information
Manfred committed Mar 27, 2010
1 parent e8ce1c7 commit 8033b92
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 4 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pkg
pkg
rdoc
73 changes: 73 additions & 0 deletions README.rdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
= Validation Sets

A Rails plugin to bundle validations in sets. You can turn entire sets of validations on or off on
an instance. This allows you to use different sets of validations for various user roles in the
application, or for different stages in the lifetime of the model.

validation_set_for(:activation) do |set|
set.validates_presence_of :fullname, :username, :email
set.validate :password_should_fit_requirements
end


== The Case of the Organization

Let's assume we have an organization in our application. The organization represents a company
using the application. Administrators add these organizations, but often they don't have all the
information about the company yet. It's up to the contact at the organization to complete it.

class Organization < ActiveRecord::Base
validates_presence_of :name

validation_set_for(:contact) do |set|
set.validates_presence_of :address, :zipcode, :city
end
end

Now we can have two controllers, one of the administrator and one for the contact. For the
administrator we don't run any validations except on name so she can choose to fill out any
of the field.

class Administrator::OrganizationsController < ActionController::Base
allow_access :administrator

def create
@organization = Organization.new(params[:organization])
if @organization.save
redirect_to [:administrator, @organization]
else
render :new
end
end
end

For the contact we turn on the extra set of validations for the contact so all the fields
need to be filled out.

class Contact::OrganizationsController < ActionController::Base
allow_access(:contact) { @organization = @authenticated.organization }

def update
@organization.attributes = params[:organization].slice(:address, :zipcode, :city)
@organization.use_validation_set(:contact)
if @organization.save
redirect_to [:contact, @organization]
else
render :edit
end
end
end

== Important Security Note

Validation sets uses an attribute on the model called <tt>_validation_set</tt> to set the active
validation set. Through mass attribute assignment this attribute can be set by anyone from outside
the application. YOU have to take care to either protect the attribute with <tt>attr_protected</tt> or
<tt>attr_accessible</tt>.

attr_protected :_validation_set

Another way to protect it is by slicing out the accessible params in the controller as done in the
example above.

@organization = params[:organization].slice(:address, :zipcode, :city)
29 changes: 26 additions & 3 deletions lib/validation_sets.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
module ValidationSets
autoload :ValidationSet, 'validation_sets/validation_set'

# Used to set the current validation set during definition of the model
attr_accessor :current_validation_set

# Returns the name of the validation callback method for a save method and the label of a
# validation set.
def validation_set_method(on, label)
case on
when :save then "validate_#{label}_set".to_sym
Expand All @@ -11,6 +14,7 @@ def validation_set_method(on, label)
end
end

# Returns the name of the validation callback method for a save method.
def validation_method(on)
if current_validation_set
validation_set_method(on, current_validation_set)
Expand All @@ -19,6 +23,13 @@ def validation_method(on)
end
end

# Add a validation set to the model.
#
# class Account < ActiveRecord::Base
# validation_set_for(:activation) do |set|
# set.validates_presence_of :username
# end
# end
def validation_set_for(label, &block)
[:save, :create, :update].each do |save_method|
callback_chain = validation_method(save_method)
Expand All @@ -39,15 +50,27 @@ def validation_set_for(label, &block)
end

module InstanceMethods
# Note that you want to protect this attribute to make sure people can't change the validation
# set from the form
attr_reader :_validation_set

# Set the active validation set on the model. After calling this both the global as well as
# the validations defined in the set will run.
#
# Example:
#
# account = Account.new
# account.use_validation_set(:admin) # Turns on the :admin validation set
# account.use_validation_set(nil) # Turns off all validation sets
def use_validation_set(set)
@_validation_set = set
end
end
end

class ActiveRecord::Base
extend ValidationSets
include ValidationSets::InstanceMethods
module ActiveRecord #:nodoc:
class Base #:nodoc:
extend ValidationSets
include ValidationSets::InstanceMethods
end
end
6 changes: 6 additions & 0 deletions lib/validation_sets/validation_set.rb
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
module ValidationSets
# A ValidationSet instance is used to redirect the original validation methods (validate,
# validate_on_create, and validate_on_update) to the callback chain for the set.
class ValidationSet
def initialize(model, label)
@model = model
@label = label
end

# Adds a validation method or proc that always runs
def validate(*params, &block)
send(validation_set_method(:save, @label), *params, &block)
end

# Adds a validation method or proc that runs on create
def validate_on_create(*params, &block)
send(validation_set_method(:create, @label), *params, &block)
end

# Adds a validation method or proc that runs on update
def validate_on_update(*params, &block)
send(validation_set_method(:update, @label), *params, &block)
end

# Forwards all other methods (ie. validates_presence_of) to the model class.
def method_missing(method, *attributes, &block)
@model.send(method, *attributes, &block)
end
Expand Down

0 comments on commit 8033b92

Please sign in to comment.