Skip to content

Instantly share code, notes, and snippets.

@isokcevic
Created March 7, 2012 16:19
Show Gist options
  • Save isokcevic/1994160 to your computer and use it in GitHub Desktop.
Save isokcevic/1994160 to your computer and use it in GitHub Desktop.
A matcher for whitelisted attributes
module WhitelistMatcher
# Checks if all of the specified attributes are mass-assignable, and if all the others are protected.
# If used with should_not, then ONLY the specified attributes should be protected from mass assignment, and for all other it should be allowed
# (i.e. behavior similar to :except )
# It also supports roles by chaining as()
#
# it { should whitelist(:email, :password, :password_confirmation) }
# it { should_not whitelist(:admin, :balance)
# it { should whitelist(:owner).as(:superuser)
#
def whitelist(*attributes)
Whitelist.new(attributes)
end
class Whitelist
def initialize(attributes)
@attributes = attributes.collect{|a| a.to_s}
end
def as(role)
@role = role
self
end
# all the specified attributes must satisfy the following conditions:
# - writer method must exist
# - mass assignment must be allowed
# all the other attributes must be protected from mass assignment
def matches?(target)
setup_defaults(target)
@attributes.each do |attr|
if ! @writers.include?(attr)
@errors_for_should << "#{target.class.name} does not have the '#{attr}' attribute, but it was expected to be allowed for mass assignment"
@passed = false
elsif @authorizer.deny?(attr)
@errors_for_should << "#{target.class.name} does not allow mass assignment of '#{attr}' for role #{@role}, but it should"
@passed = false
end
end
@unspecified_attributes.each do |attr|
if ! @authorizer.deny?(attr)
@errors_for_should << "#{target.class.name} allows mass assignment of '#{attr}' for role #{@role}, but it is not expected"
@passed = false
end
end
@passed
end
# all the specified attributes must satisfy the following conditions:
# - writer method must exist
# - mass assignment must NOT be allowed
# all the other attributes must allow mass assignment
def does_not_match?(target)
setup_defaults(target)
@attributes.each do |attr|
if ! @writers.include?(attr)
@errors_for_should_not << "#{target.class.name} does not have the '#{attr}' attribute, but it was expected to be protected against mass assignment"
@passed = false
elsif ! @authorizer.deny?(attr)
@errors_for_should_not << "#{target.class.name} allows mass assignment of '#{attr}' for role #{@role}, but it should not"
@passed = false
end
end
@unspecified_attributes.each do |attr|
if @authorizer.deny?(attr)
@errors_for_should_not << "#{target.class.name} does not allow mass assignment of '#{attr}' for role #{@role}, but it is expected"
@passed = false
end
end
@passed
end
def failure_message_for_should
@errors_for_should.join("\n")
end
def failure_message_for_should_not
@errors_for_should_not.join("\n")
end
private
def setup_defaults(target)
@passed = true
@role ||= :default
@errors_for_should = []
@errors_for_should_not = []
@authorizer = target.class.active_authorizer[@role]
if !@authorizer
@errors_for_should << "Unknown role '#{@role}' for #{target.class.name}"
@errors_for_should_not << "Unknown role '#{@role}' for #{target.class.name}"
end
#get all the possible writers
@writers = ((target.class.instance_method_names - target.class.superclass.instance_method_names).select{|n| n=~ /=$/}.collect{|n| n[0..-2]} + target.class.column_names).uniq
#attributes not specified, which should behave opposite of those specified
@unspecified_attributes = @writers - @attributes
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment