Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Allow allow_values_for and allow_mass_assignment_of to be used in the…

… negative form, checking all values [#85 status:resolved]
  • Loading branch information...
commit a1a529ce8cabc4f6af5b9bec39c5c9ad6de92078 1 parent de72281
@josevalim josevalim authored
View
1  remarkable/lib/remarkable.rb
@@ -9,6 +9,7 @@
require File.join(dir, 'remarkable', 'base')
require File.join(dir, 'remarkable', 'macros')
require File.join(dir, 'remarkable', 'pending')
+require File.join(dir, 'remarkable', 'negative')
require File.join(dir, 'remarkable', 'core_ext', 'array')
if defined?(Spec)
View
23 remarkable/lib/remarkable/base.rb
@@ -11,6 +11,14 @@ def spec(binding)
self
end
+ def positive?
+ !negative?
+ end
+
+ def negative?
+ false
+ end
+
protected
# Returns the subject class unless it's a class object.
@@ -28,11 +36,20 @@ def subject_name
# Iterates over the collection given yielding the block and return false
# if any of them also returns false.
- def assert_matcher_for(collection) #:nodoc:
+ def assert_collection(key, collection, inspect=true) #:nodoc:
collection.each do |item|
- return false unless yield(item)
+ value = yield(item)
+
+ if positive? == !value
+ if key
+ output = inspect ? item.inspect : item
+ return negative?, key => output
+ else
+ return negative?
+ end
+ end
end
- true
+ positive?
end
# Asserts that the given collection contains item x. If x is a regular
View
42 remarkable/lib/remarkable/dsl/assertions.rb
@@ -282,11 +282,13 @@ def matches?(subject)
run_before_assert_callbacks
- send_methods_and_generate_message(self.class.matcher_single_assertions) &&
- assert_matcher_for(instance_variable_get("@#{self.class.matcher_arguments[:collection]}") || []) do |value|
- instance_variable_set("@#{self.class.matcher_arguments[:as]}", value)
- send_methods_and_generate_message(self.class.matcher_collection_assertions)
+ assertions = self.class.matcher_single_assertions
+ unless assertions.empty?
+ value = send_methods_and_generate_message(assertions)
+ return negative? if positive? == !value
end
+
+ matches_collection_assertions?
end
protected
@@ -368,19 +370,41 @@ def send_methods_and_generate_message(methods) #:nodoc:
methods.each do |method|
bool, hash = send(method)
- unless bool
+ if positive? == !bool
parent_scope = matcher_i18n_scope.split('.')
matcher_name = parent_scope.pop
- lookup = :"expectations.#{method.to_s.gsub(/(\?|\!)$/, '')}"
+ method_name = method.to_s.gsub(/(\?|\!)$/, '')
+
+ lookup = []
+ lookup << :"#{matcher_name}.negative_expectations.#{method_name}" if negative?
+ lookup << :"#{matcher_name}.expectations.#{method_name}"
+ lookup << :"negative_expectations.#{method_name}" if negative?
+ lookup << :"expectations.#{method_name}"
hash = { :scope => parent_scope, :default => lookup }.merge(hash || {})
- @expectation ||= Remarkable.t "#{matcher_name}.#{lookup}", default_i18n_options.merge(hash)
+ @expectation ||= Remarkable.t lookup.shift, default_i18n_options.merge(hash)
- return false
+ return negative?
end
end
- return true
+ return positive?
+ end
+
+ def matches_single_assertions? #:nodoc:
+ assertions = self.class.matcher_single_assertions
+ send_methods_and_generate_message(assertions)
+ end
+
+ def matches_collection_assertions? #:nodoc:
+ arguments = self.class.matcher_arguments
+ assertions = self.class.matcher_collection_assertions
+ collection = instance_variable_get("@#{self.class.matcher_arguments[:collection]}") || []
+
+ assert_collection(nil, collection) do |value|
+ instance_variable_set("@#{arguments[:as]}", value)
+ send_methods_and_generate_message(assertions)
+ end
end
View
24 remarkable/lib/remarkable/negative.rb
@@ -0,0 +1,24 @@
+module Remarkable
+ # Allows Remarkable matchers to work on the negative way. Your matcher has to
+ # follow some conventions to allow this to work by default.
+ #
+ # In negative cases, expectations can also be found under negative_expectations
+ # keys, falling back to expectations. This allows to set customized failure
+ # messages.
+ #
+ module Negative
+ def matches?(subject)
+ @negative ||= false
+ super
+ end
+
+ def does_not_match?(subject)
+ @negative = true
+ !matches?(subject)
+ end
+
+ def negative?
+ @negative
+ end
+ end
+end
View
5 remarkable/spec/base_spec.rb
@@ -28,6 +28,11 @@
matcher.instance_variable_get('@spec').class.ancestors.should include(Spec::Example::ExampleGroup)
end
+ it 'should be positive' do
+ contain(1).should be_positive
+ contain(1).should_not be_negative
+ end
+
it { should contain(1) }
it { should_not contain(10) }
View
2  remarkable/spec/matchers/contain_matcher.rb
@@ -9,7 +9,7 @@ def initialize(*values)
def matches?(subject)
@subject = subject
- assert_matcher_for(@values) do |value|
+ assert_collection(nil, @values) do |value|
@value = value
included?
end
View
3  remarkable_activerecord/CHANGELOG
@@ -1,3 +1,6 @@
+* Allow allow_values_for and allow_mass_assignment_of matchers to be executed in
+ the negative form by including Remarkable::Negative module [#85]
+
* Ensure quick subject bypass protected attributes [#87]
* Added :token and :separator to deal with :tokenizer in validates_length_of [#77]
View
16 remarkable_activerecord/lib/remarkable_activerecord/base.rb
@@ -25,14 +25,12 @@ def with_options(opts={})
# and an @options key where the message to search for is.
#
def assert_bad_or_good_if_key(key, value, message_key=:message) #:nodoc:
- return true unless @options.key?(key)
+ return positive? unless @options.key?(key)
if @options[key]
- return true if bad?(value, message_key)
- return false, :not => not_word
+ return bad?(value, message_key), :not => not_word
else
- return true if good?(value, message_key)
- return false, :not => ''
+ return good?(value, message_key), :not => ''
end
end
@@ -43,14 +41,12 @@ def assert_bad_or_good_if_key(key, value, message_key=:message) #:nodoc:
# and an @options key where the message to search for is.
#
def assert_good_or_bad_if_key(key, value, message_key=:message) #:nodoc:
- return true unless @options.key?(key)
+ return positive? unless @options.key?(key)
if @options[key]
- return true if good?(value, message_key)
- return false, :not => ''
+ return good?(value, message_key), :not => ''
else
- return true if bad?(value, message_key)
- return false, :not => not_word
+ return bad?(value, message_key), :not => not_word
end
end
View
18 ...ble_activerecord/lib/remarkable_activerecord/matchers/allow_mass_assignment_of_matcher.rb
@@ -2,10 +2,11 @@ module Remarkable
module ActiveRecord
module Matchers
class AllowMassAssignmentOfMatcher < Remarkable::ActiveRecord::Base #:nodoc:
+ include Remarkable::Negative
arguments :collection => :attributes, :as => :attribute
assertion :allows?
- collection_assertions :is_protected?, :is_accessible?
+ collection_assertions :is_accessible?, :is_protected?
protected
@@ -13,20 +14,27 @@ class AllowMassAssignmentOfMatcher < Remarkable::ActiveRecord::Base #:nodoc:
# otherwise it fails.
#
def allows?
- !@attributes.empty? || protected_attributes.empty?
+ return positive? unless @attributes.empty?
+ protected_attributes.empty?
end
def is_protected?
- protected_attributes.empty? || !protected_attributes.include?(@attribute.to_s)
+ return positive? if protected_attributes.empty?
+ !protected_attributes.include?(@attribute.to_s)
end
def is_accessible?
- accessible_attributes.empty? || accessible_attributes.include?(@attribute.to_s)
+ return positive? if accessible_attributes.empty?
+ accessible_attributes.include?(@attribute.to_s)
end
def interpolation_options
if @subject
- { :protected_attributes => array_to_sentence(protected_attributes.to_a, false, '[]') }
+ if positive?
+ { :protected_attributes => array_to_sentence(protected_attributes.to_a, false, '[]') }
+ else
+ { :accessible_attributes => array_to_sentence(accessible_attributes.to_a, false, '[]') }
+ end
else
{}
end
View
11 remarkable_activerecord/lib/remarkable_activerecord/matchers/allow_values_for_matcher.rb
@@ -2,6 +2,7 @@ module Remarkable
module ActiveRecord
module Matchers
class AllowValuesForMatcher < Remarkable::ActiveRecord::Base #:nodoc:
+ include Remarkable::Negative
arguments :collection => :attributes, :as => :attribute
optional :message
@@ -28,17 +29,15 @@ class AllowValuesForMatcher < Remarkable::ActiveRecord::Base #:nodoc:
protected
def is_valid?
- valid_values.each do |value|
- return false, :value => value.inspect unless good?(value)
+ assert_collection :value, valid_values do |value|
+ good?(value)
end
- true
end
def is_invalid?
- invalid_values.each do |value|
- return false, :value => value.inspect unless bad?(value)
+ assert_collection :value, invalid_values do |value|
+ bad?(value)
end
- true
end
def valid_values
View
2  ...rkable_activerecord/lib/remarkable_activerecord/matchers/validate_exclusion_of_matcher.rb
@@ -4,6 +4,8 @@ module Remarkable
module ActiveRecord
module Matchers
class ValidateExclusionOfMatcher < AllowValuesForMatcher #:nodoc:
+ # Don't allow it to behave in the negative way.
+ undef_method :does_not_match?
default_options :message => :exclusion
View
2  ...rkable_activerecord/lib/remarkable_activerecord/matchers/validate_inclusion_of_matcher.rb
@@ -4,6 +4,8 @@ module Remarkable
module ActiveRecord
module Matchers
class ValidateInclusionOfMatcher < AllowValuesForMatcher #:nodoc:
+ # Don't allow it to behave in the negative way.
+ undef_method :does_not_match?
default_options :message => :inclusion
View
4 remarkable_activerecord/locale/en.yml
@@ -45,6 +45,10 @@ en:
allows: "{{subject_name}} to allow mass assignment ({{subject_name}} is protecting {{protected_attributes}})"
is_protected: "{{subject_name}} to allow mass assignment of {{attribute}} ({{subject_name}} is protecting {{attribute}})"
is_accessible: "{{subject_name}} to allow mass assignment of {{attribute}} ({{subject_name}} has not made {{attribute}} accessible)"
+ negative_expectations:
+ allows: "{{subject_name}} to allow mass assignment ({{subject_name}} made {{accessible_attributes}} accessible)"
+ is_protected: "{{subject_name}} to allow mass assignment of {{attribute}} ({{subject_name}} is not protecting {{attribute}})"
+ is_accessible: "{{subject_name}} to allow mass assignment of {{attribute}} ({{subject_name}} has made {{attribute}} accessible)"
association:
belongs_to: belong to
View
19 remarkable_activerecord/spec/allow_mass_assignment_of_matcher_spec.rb
@@ -75,7 +75,24 @@ def define_and_validate(options={})
should_allow_mass_assignment_of :title, :category
should_not_allow_mass_assignment_of :another
- should_not_allow_mass_assignment_of :title, :another
+ end
+
+ describe 'failures' do
+ it "should fail if not all attributes are accessible on should not" do
+ define_and_validate(:accessible => true)
+
+ lambda {
+ should_not allow_mass_assignment_of :title, :another
+ }.should raise_error(Spec::Expectations::ExpectationNotMetError, /Product has made title accessible/)
+ end
+
+ it "should fail if accessible when protecting" do
+ define_and_validate(:accessible => true)
+
+ lambda {
+ should_not allow_mass_assignment_of
+ }.should raise_error(Spec::Expectations::ExpectationNotMetError, /Product made category and title accessible/)
+ end
end
end
View
10 remarkable_activerecord/spec/allow_values_for_matcher_spec.rb
@@ -57,5 +57,15 @@ def define_and_validate(options={})
should_not_validate_format_of :title, 'A'
end
end
+
+ describe 'failures' do
+ it "should fail if any of the values are valid on invalid cases" do
+ define_and_validate(:with => /X|Y|Z/)
+
+ lambda {
+ should_not allow_values_for :title, 'A', 'X', 'B'
+ }.should raise_error(Spec::Expectations::ExpectationNotMetError, /Did not expect Product to be valid/)
+ end
+ end
end
Please sign in to comment.
Something went wrong with that request. Please try again.